use shape_ast::error::{ErrorCode, ShapeError, SourceLocation};
use shape_runtime::type_schema::{FieldType, TypeSchema, TypeSchemaRegistry};
use crate::bytecode::BytecodeProgram;
#[derive(Debug, Clone, Copy)]
pub(crate) struct WhitelistEntry {
pub(crate) rule: WhitelistRule,
pub(crate) section: &'static str,
pub(crate) permanent: bool,
pub(crate) reason: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum WhitelistRule {
SchemaName(&'static str),
SchemaNamePrefix(&'static str),
EnumPayloadField,
}
pub(crate) const WHITELIST: &[WhitelistEntry] = &[
WhitelistEntry {
rule: WhitelistRule::SchemaName("Row"),
section: "§4.D.11",
permanent: true,
reason: "stdlib heterogeneous-column carrier; \
registry.rs:236/280/356; \
parallel-`field_kinds` track per ADR-006 §2.7.26",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("FrameState"),
section: "§4.D.15",
permanent: true,
reason: "VM-state introspection (locals/args/upvalues); \
state_builtins/core.rs:65-67; \
parallel-`field_kinds` per ADR-006 §2.7.7/Q9",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("VmState"),
section: "§4.D.15",
permanent: true,
reason: "VM-state introspection (frames/module_bindings); \
state_builtins/core.rs:74-75",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("ModuleState"),
section: "§4.D.15",
permanent: true,
reason: "VM-state introspection (module bindings); \
state_builtins/core.rs:82",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("CallPayload"),
section: "§4.D.15",
permanent: true,
reason: "VM-state introspection (call payload args); \
state_builtins/core.rs:89",
},
WhitelistEntry {
rule: WhitelistRule::SchemaNamePrefix("std::core::state::"),
section: "§4.D.15",
permanent: true,
reason: "namespaced VM-state introspection surface \
(FrameState/VmState/ModuleState/CallPayload/Delta/\
FunctionRef); state_builtins/core.rs:46-91 + Delta via \
ConcreteType::Named at core.rs:194 + register_predeclared \
fallback; parallel-`field_kinds` per ADR-006 §2.7.7/Q9 \
+ §2.7.26",
},
WhitelistEntry {
rule: WhitelistRule::SchemaNamePrefix("__mod_"),
section: "§4.D.6",
permanent: true,
reason: "module-export synthetic schema (statements.rs:1485 + \
comptime.rs:479); parallel-`field_kinds` carrier-tier \
per ADR-006 §2.7.26 (W17-comptime-vm-dispatch)",
},
WhitelistEntry {
rule: WhitelistRule::SchemaNamePrefix("__predecl_"),
section: "§4.D.12",
permanent: true,
reason: "predeclared-schema carrier-tier \
(register_predeclared_any_schema / \
mirror_predeclared_any_schema); \
registry.rs:489/535; ADR-006 §2.7.26 binds the kind \
resolution to the storage's `field_kinds` track",
},
WhitelistEntry {
rule: WhitelistRule::SchemaNamePrefix("__annotation_ctx_"),
section: "§4.D.10",
permanent: true,
reason: "annotation-handler ctx schemas \
(functions_annotations.rs:228-1555 + \
expressions/mod.rs:309-667); heterogeneous-by-design \
(@before/@after handler ABI receives arbitrary \
user state); parallel-`field_kinds` per ADR-006 §2.7.26",
},
WhitelistEntry {
rule: WhitelistRule::EnumPayloadField,
section: "§4.D.14",
permanent: true,
reason: "enum __payload_N storage carrier (schema.rs:203); \
compiler-side parallel-kind track lives at \
`enum_struct_variant_fields` (`compiler/mod.rs:813-822`) \
per ADR-006 §2.3 named-carrier discipline",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__AnyError"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; \
builtin_schemas.rs:114; \
parallel-`field_kinds` track + heap_mask per \
ADR-006 §2.7.7/Q9 + §2.7.26",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__TraceFrame"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__TraceInfoFull"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__TraceInfoSingle"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__ReflectAnnotation"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__ReflectField"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__ReflectResult"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__GroupResult"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__EventLogEntry"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__SimulateReturn"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__EmptyObject"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__ComptimeBuildConfig"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__ComptimeTargetField"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__ComptimeTargetParam"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaName("__ComptimeTarget"),
section: "§4.D.11",
permanent: true,
reason: "stdlib runtime-builtin schema; builtin_schemas.rs",
},
WhitelistEntry {
rule: WhitelistRule::SchemaNamePrefix("__inline_obj_"),
section: "§4.D.3 + §4.D.5 + §4.D.10-emission",
permanent: false,
reason: "TRANSITIONAL (NARROWED at W17.2-C from empty-prefix \
catch-all to `__inline_obj_*` prefix-only) — \
§4.D.1+§4.D.2+§4.D.4+§4.D.7+§4.D.8+§4.D.9 CLOSED at \
W17.2-B+W17.2-C via FieldType::Option PROPAGATE rebuild \
+ collections.rs:975/1004 structured E#### compile \
error + helpers.rs:4901 4-name narrowing + helpers.rs:4905 \
explicit per-variant arms + register_inline_object_schema \
deprecation+caller migration. Surviving territory: \
§4.D.3 (inline-object inference at \
`expressions/collections.rs:509/518` — `__inline_obj_N` \
emission with FieldType::Any per field when RHS is \
non-literal) + §4.D.5 carrier (`type_tracking.rs:1024` \
deprecated untyped variant routes through typed-with-Any) \
+ §4.D.10 emission-rename \
(`functions_annotations.rs/expressions/mod.rs` schemas \
use `__inline_obj_*` instead of `__annotation_ctx_*` \
whitelist convention) — R5b/R6 follow-up per audit \
§4.D.10 explicit cite. Take-both at W17.3/R5b/R6 close \
strips this row.",
},
];
fn match_whitelist(
schema_name: &str,
field_name: &str,
is_enum_schema: bool,
) -> Option<&'static WhitelistEntry> {
WHITELIST.iter().find(|entry| match entry.rule {
WhitelistRule::SchemaName(name) => name == schema_name,
WhitelistRule::SchemaNamePrefix(prefix) => schema_name.starts_with(prefix),
WhitelistRule::EnumPayloadField => {
is_enum_schema && field_name.starts_with("__payload_")
}
})
}
fn verify_schema(schema: &TypeSchema, errors: &mut Vec<ShapeError>) {
let is_enum_schema = schema.is_enum();
let whole_schema_match = WHITELIST.iter().find(|entry| match entry.rule {
WhitelistRule::SchemaName(name) => name == schema.name,
WhitelistRule::SchemaNamePrefix(prefix) => {
!prefix.is_empty() && schema.name.starts_with(prefix)
}
WhitelistRule::EnumPayloadField => false,
});
if whole_schema_match.is_some() {
return;
}
for field in &schema.fields {
if !field_type_contains_any(&field.field_type) {
continue;
}
match match_whitelist(&schema.name, &field.name, is_enum_schema) {
Some(entry) if entry.permanent => {
continue;
}
Some(entry) => {
let _ = entry;
continue;
}
None => {
errors.push(emit_e0900(schema, &field.name, &field.field_type));
}
}
}
}
fn field_type_contains_any(ft: &FieldType) -> bool {
match ft {
FieldType::Any => true,
FieldType::Array(inner) => field_type_contains_any(inner),
FieldType::Option(inner) => field_type_contains_any(inner),
FieldType::HashMap { key, value } => {
field_type_contains_any(key) || field_type_contains_any(value)
}
FieldType::Set(inner) => field_type_contains_any(inner),
FieldType::F64
| FieldType::I64
| FieldType::Bool
| FieldType::String
| FieldType::Timestamp
| FieldType::Decimal
| FieldType::Object(_)
| FieldType::I8
| FieldType::U8
| FieldType::I16
| FieldType::U16
| FieldType::I32
| FieldType::U32
| FieldType::U64 => false,
}
}
fn emit_e0900(schema: &TypeSchema, field_name: &str, ft: &FieldType) -> ShapeError {
let location = SourceLocation {
file: None,
line: 0,
column: 0,
length: None,
source_line: None,
hints: vec![format!(
"consult `docs/cluster-audits/v0.3-w17-fieldtype-any-boundary-audit.md` \
§4.D for per-site disposition; permanent named exceptions are \
whitelisted in `crates/shape-vm/src/compiler/post_inference_verify.rs`"
)],
notes: vec![],
is_synthetic: true,
};
let _ = ErrorCode::E0900; let message = format!(
"[E0900] post-inference FieldType::Any in user-facing schema \
`{}` at field `{}` (resolved type: {}). User-introduced \
FieldType::Any outside the named-exception classes is the \
schema-side analogue of the deleted dynamic-slot-kind variants \
per CLAUDE.md Forbidden Patterns (strict-typing plan). See \
ADR-006 §2.7.5 + §2.7.26 + audit §5 for the binding rule.",
schema.name, field_name, ft
);
ShapeError::SemanticError {
message,
location: Some(location),
}
}
pub fn verify_no_post_inference_any(
program: &BytecodeProgram,
) -> Result<(), ShapeError> {
let mut errors: Vec<ShapeError> = Vec::new();
verify_registry(&program.type_schema_registry, &mut errors);
finalize(errors)
}
pub(crate) fn verify_registry(
registry: &TypeSchemaRegistry,
errors: &mut Vec<ShapeError>,
) {
let names: Vec<String> = registry.type_names().map(|s| s.to_string()).collect();
for name in &names {
if let Some(schema) = registry.get(name) {
verify_schema(schema, errors);
}
}
}
fn finalize(errors: Vec<ShapeError>) -> Result<(), ShapeError> {
match errors.len() {
0 => Ok(()),
1 => Err(errors.into_iter().next().expect("len==1")),
_ => Err(ShapeError::MultiError(errors)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use shape_runtime::type_schema::{EnumVariantInfo, TypeSchema, TypeSchemaRegistry};
fn run_on(registry: &TypeSchemaRegistry) -> Result<(), ShapeError> {
let mut errors = Vec::new();
verify_registry(registry, &mut errors);
finalize(errors)
}
#[test]
fn positive_row_carrier_passes() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"Row",
vec![
("timestamp".to_string(), FieldType::Timestamp),
("fields".to_string(), FieldType::Any),
],
);
reg.register(schema);
assert!(run_on(®).is_ok(), "Row schema must pass §4.D.11");
}
#[test]
fn positive_vm_state_introspection_passes() {
let mut reg = TypeSchemaRegistry::new();
for name in &["FrameState", "VmState", "ModuleState", "CallPayload"] {
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
*name,
vec![("contents".to_string(), FieldType::Any)],
);
reg.register(schema);
}
assert!(
run_on(®).is_ok(),
"VM-state introspection schemas must pass §4.D.15"
);
}
#[test]
fn positive_annotation_handler_ctx_passes() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"__annotation_ctx_logging",
vec![
("state".to_string(), FieldType::Any),
(
"event_log".to_string(),
FieldType::Array(Box::new(FieldType::Any)),
),
],
);
reg.register(schema);
assert!(
run_on(®).is_ok(),
"annotation-handler ctx schemas must pass §4.D.10"
);
}
#[test]
fn positive_predeclared_any_schema_passes() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"__predecl_a_b_c",
vec![
("a".to_string(), FieldType::Any),
("b".to_string(), FieldType::Any),
("c".to_string(), FieldType::Any),
],
);
reg.register(schema);
assert!(
run_on(®).is_ok(),
"predeclared schemas must pass §4.D.12"
);
}
#[test]
fn positive_enum_payload_fields_pass() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::new_enum_with_id(
id,
"MyEnum",
vec![
EnumVariantInfo::new("A", 0, 1),
EnumVariantInfo::new("B", 1, 2),
],
);
reg.register(schema);
assert!(
run_on(®).is_ok(),
"enum __payload_N fields must pass §4.D.14"
);
}
fn verify_registry_permanent_only(
registry: &TypeSchemaRegistry,
errors: &mut Vec<ShapeError>,
) {
let names: Vec<String> = registry.type_names().map(|s| s.to_string()).collect();
for name in &names {
if let Some(schema) = registry.get(name) {
verify_schema_permanent_only(schema, errors);
}
}
}
fn verify_schema_permanent_only(schema: &TypeSchema, errors: &mut Vec<ShapeError>) {
let is_enum_schema = schema.is_enum();
let whole_match = WHITELIST
.iter()
.filter(|e| e.permanent)
.find(|entry| match entry.rule {
WhitelistRule::SchemaName(name) => name == schema.name,
WhitelistRule::SchemaNamePrefix(prefix) => {
!prefix.is_empty() && schema.name.starts_with(prefix)
}
WhitelistRule::EnumPayloadField => false,
});
if whole_match.is_some() {
return;
}
for field in &schema.fields {
if !field_type_contains_any(&field.field_type) {
continue;
}
let matched = WHITELIST.iter().filter(|e| e.permanent).find(|entry| {
match entry.rule {
WhitelistRule::SchemaName(name) => name == schema.name,
WhitelistRule::SchemaNamePrefix(prefix) => {
!prefix.is_empty() && schema.name.starts_with(prefix)
}
WhitelistRule::EnumPayloadField => {
is_enum_schema && field.name.starts_with("__payload_")
}
}
});
if matched.is_none() {
errors.push(emit_e0900(schema, &field.name, &field.field_type));
}
}
}
#[test]
fn negative_user_any_annotation_fires_e0900() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"T",
vec![("x".to_string(), FieldType::Any)],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert_eq!(errors.len(), 1, "exactly one E0900 expected");
match &errors[0] {
ShapeError::SemanticError { message, .. } => {
assert!(message.contains("E0900"), "message must cite E0900");
assert!(message.contains("`T`"), "message must name schema");
assert!(message.contains("`x`"), "message must name field");
}
other => panic!("expected SemanticError, got {:?}", other),
}
}
#[test]
fn negative_untyped_inline_object_fires_e0900() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"__inline_obj_42",
vec![
("f".to_string(), FieldType::Any),
("g".to_string(), FieldType::I64),
],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert_eq!(errors.len(), 1, "exactly one E0900 expected");
match &errors[0] {
ShapeError::SemanticError { message, .. } => {
assert!(message.contains("E0900"));
assert!(message.contains("__inline_obj_42"));
assert!(message.contains("`f`"));
}
other => panic!("expected SemanticError, got {:?}", other),
}
}
#[test]
fn negative_struct_literal_schema_miss_fires_e0900() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"Person",
vec![
("name".to_string(), FieldType::Any),
("age".to_string(), FieldType::Any),
],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert_eq!(errors.len(), 2, "two E0900 expected (name + age)");
for err in &errors {
match err {
ShapeError::SemanticError { message, .. } => {
assert!(message.contains("E0900"));
assert!(message.contains("Person"));
}
other => panic!("expected SemanticError, got {:?}", other),
}
}
}
#[test]
fn post_w17_2_c_user_named_any_fires_via_primary_entry() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"MyUserType",
vec![("dynamic_field".to_string(), FieldType::Any)],
);
reg.register(schema);
let result = run_on(®);
assert!(
result.is_err(),
"user-named schema with Any must surface E0900 post-W17.2-C narrowing"
);
match result.unwrap_err() {
ShapeError::SemanticError { message, .. } => {
assert!(message.contains("E0900"));
assert!(message.contains("MyUserType"));
assert!(message.contains("dynamic_field"));
}
other => panic!("expected SemanticError, got {:?}", other),
}
}
#[test]
fn post_w17_2_c_inline_obj_still_absorbed() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"__inline_obj_99",
vec![("dynamic_field".to_string(), FieldType::Any)],
);
reg.register(schema);
assert!(
run_on(®).is_ok(),
"narrowed transitional row must still absorb __inline_obj_* schemas"
);
}
#[test]
fn post_w17_2_c_helpers_4901_narrowed_to_4_names() {
let names = ["HashMap", "Map", "Result", "Set"];
assert!(!names.contains(&"Option"));
assert_eq!(names.len(), 4, "TRANSITIONAL list MUST be 4 names");
}
#[test]
fn post_w17_2_b_option_concrete_inner_passes() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"OptionalIntField",
vec![(
"x".to_string(),
FieldType::Option(Box::new(FieldType::I64)),
)],
);
reg.register(schema);
assert!(
run_on(®).is_ok(),
"Option<int> field on user-named schema must pass post-W17.2-B"
);
}
#[test]
fn post_w17_2_b_option_any_inner_rejected_under_permanent_only() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"OptionalAnyField",
vec![(
"x".to_string(),
FieldType::Option(Box::new(FieldType::Any)),
)],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert_eq!(
errors.len(),
1,
"Option<Any> inner Any must surface E0900 under permanent-only \
verification per W17.2-B `field_type_contains_any` recursion"
);
}
#[test]
fn w17_3_4_2_hashmap_concrete_passes() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"TaggedTable",
vec![(
"tags".to_string(),
FieldType::HashMap {
key: Box::new(FieldType::String),
value: Box::new(FieldType::I64),
},
)],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert!(
errors.is_empty(),
"HashMap<string, int> field must pass verification post-W17.3-4.2; \
got {} error(s)",
errors.len()
);
}
#[test]
fn w17_3_4_2_set_concrete_passes() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"TaggedTable",
vec![("ids".to_string(), FieldType::Set(Box::new(FieldType::I64)))],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert!(
errors.is_empty(),
"Set<int> field must pass verification post-W17.3-4.2; got {} error(s)",
errors.len()
);
}
#[test]
fn w17_3_4_2_hashmap_value_any_rejected_under_permanent_only() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"TaggedTableWithAnyValue",
vec![(
"tags".to_string(),
FieldType::HashMap {
key: Box::new(FieldType::String),
value: Box::new(FieldType::Any),
},
)],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert_eq!(
errors.len(),
1,
"HashMap<string, Any> inner Any must surface E0900 under \
permanent-only verification per W17.3-4.2 recursion"
);
}
#[test]
fn w17_3_4_2_hashmap_key_any_rejected_under_permanent_only() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"TaggedTableWithAnyKey",
vec![(
"tags".to_string(),
FieldType::HashMap {
key: Box::new(FieldType::Any),
value: Box::new(FieldType::I64),
},
)],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert_eq!(
errors.len(),
1,
"HashMap<Any, int> inner Any in key must surface E0900 under \
permanent-only verification per W17.3-4.2 recursion"
);
}
#[test]
fn w17_3_4_2_set_any_inner_rejected_under_permanent_only() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"SetOfAny",
vec![("ids".to_string(), FieldType::Set(Box::new(FieldType::Any)))],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert_eq!(
errors.len(),
1,
"Set<Any> inner Any must surface E0900 under permanent-only \
verification per W17.3-4.2 recursion"
);
}
#[test]
fn w17_3_4_2_nested_containers_inner_any_rejected() {
let mut reg = TypeSchemaRegistry::new();
let id = reg.allocate_id();
let schema = TypeSchema::with_id(
id,
"NestedContainerAnyField",
vec![(
"x".to_string(),
FieldType::Array(Box::new(FieldType::HashMap {
key: Box::new(FieldType::String),
value: Box::new(FieldType::Set(Box::new(FieldType::Any))),
})),
)],
);
reg.register(schema);
let mut errors = Vec::new();
verify_registry_permanent_only(®, &mut errors);
assert_eq!(
errors.len(),
1,
"deeply-nested Any inside Array<HashMap<string, Set<Any>>> \
must surface E0900 per W17.3-4.2 recursion-composition"
);
}
}