diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 index a8b7e32..6f8ed6b 100644 --- a/MANIFEST.sha256 +++ b/MANIFEST.sha256 @@ -9,6 +9,7 @@ 04e67c89d88d0f33d8e898ec3396359f48da372fd7ab28c1fc70483a679b7fe6 conformance/validation/006_model_version_inside_object/object.yaml 052458dca6bed674aec47f25561534ce72cda99a62faf33e2e947474400c7374 tools/vault-rootCA-sign-agent.sh 06b8bd2d88fa048c5ee235eea47af951aa782691635c77ad616dab0abe3a27ed conformance/invalid/003_closure_undeclared/expected-error.yaml +07dea1fcabc03fcec5648b8834202db6d505d625c07cc0d94db40cb13ffae61b conformance/materialization/015_nested_sealed_template/meta.yaml 08565714b7e2064ac0f9c691ed50215f80363adb08f8976589bebdcbb717f54e renovate.json 085f2e3cbd9cb9a6a7bc53a42cfa8be8fa51ca27cadd56edfdd21dda0d34e15b docs/external-review.md 090194d2cbde04322fd68bed8ce5427b4b0ad5daba1b26017958fed8ef29c964 tools/release_subject.py @@ -51,6 +52,7 @@ 225e266950c6f85bfd2be20cacb3de0ac011414f92010b831136640ffe9a46be conformance/invalid/007_sealed_missing_path/expected-error.yaml 229376bac93d6b5d81c8c3015b4981999cf3a3c82bf8a8e1d63eecaf61d1e35d conformance/validation/007_origin_duplicate_term/expected-error.yaml 24219339b219898ecb69d09495bc078745cef2c1b5065eedfff886dd95032881 conformance/invalid/010_required_value_missing/expected-error.yaml +25330918bb1de80bf3db79532e39dd87e2752df46e402434c0044de476046f86 README.md 2758d20cf5aa0688060bce28a52786eee272986714a8376d7f190eb2dc90a43e reviews/README.md 275acc4de6f4d5fcd87330039777ceb8f8d8a7933c4e4924eccabe21a9f28da2 conformance/materialization/002_origin_schema/meta.yaml 27fc384a0d7987e4cb311648408b9b5a3995c0b58871ed55389ae154061209c9 conformance/validation/011_more_than_one_document/object.yaml @@ -59,16 +61,13 @@ 2ab20175d0b7d0f2a7ebe960903a08dc7fe69620615f253ae1fa3e584747edaa pytest.ini 2b1268f3aba4ac61ee21ea6a558fb8b01d2ce4964b8993b0e0b0aae583303b08 go/objectmodel/primitives.yaml 2b5c7c2028ce8d8fa9ec385fed586a85d22f30903373b511111011c6e6a34044 go/objectmodel/emit.go -2be51eae75514d81524b44089859b25fa7282a962475cf9bf196b87ebb96cbd9 rust/src/materialize.rs 2bf4423a50b568d1aa9bf938a233f9584e2c6ef19f5dd3ebf458c9a1dd093a30 go/objectmodel/schema.yaml -2cafa2e6c89efdc947b2dc5206041dd3640892c6cdcb8a92f9852eff5f8e9413 docs/spec-vector-map.md 2d28d83b74f3b137a2e2d02d21178ad751b9b59ea81133ec42c9704ca535c986 tools/init_from_template.sh 2d66d76a7399d07f6dc504d64a22e81dfcaa49f6bcee9ab4362c15dcb82b2475 conformance/validation/006_model_version_inside_object/expected-error.yaml 2d763d2e7243d988b6f107432e14f4ac45d10a642ea72abd150ed30d8c3261c2 tools/check_spec_vectors.py 2e19ebdd9db8f56f6dc5fb006f8bb23037595794d0c288e72901b9f628d2d5b5 tools/git_hook_commit-msg.sh 2e83c8f6100c847450f90a1f593db97b7ad2e7cc7798bb762823b61ebcdeff57 go/objectmodel/refs.go 2e85d74791ed8db0bee2e473f3d5906c351c1c962901e12dc4278198a44d07a3 docs/hu/concept/declarative_ecosystem_integration.meta.yaml -2f7e223bf0e58d18a8705d175d0c0845df050c4429e72f22f54eb84e11f4022d docs/en/architecture.md 3057e97eafa5993ad6761b7c1b25076432fc5b1998dc7c4d569926bc37a974b2 tests/test_compiler.yaml 306916d0bc25ffb21038fa8ac7421dddec5dfae2483f3a802d5d0970194ebfbb conformance/materialization/001_origin_yaml/expected.yaml 308a0bc08020a9fe5fc59e2310af16c0aebafcfb3fa9c1b4ac8e585da1967274 conformance/materialization/010_normalize_empty_object/meta.yaml @@ -131,6 +130,7 @@ 60a7ef1423b335eab7c75d37e0be6d1ce517b50d1a7c3fae95ceefae57cc46f3 docs/spec-defects.md 60f0024efc017a5d11dd4a16fd95e8ae01ab3b5a520082ac401b034616ce1f6f go/cmd/cic-materialize/main.go 61abb4260dac215d5b246dfb2459b9fbb3c6edb55c4ca972ea81763524d95f8e rust/deny.toml +62f72bcff9b4da95f5fe2bdd98408f6fdc6bcea0d030b7ef0bde9836869ee540 rust/src/materialize.rs 631a400349d4bdecf455e7f95d400a5bf27e93621c58a9d1886a7f66d55c4f26 conformance/invalid/004_schema_declares_values_child/input.yaml 63beeb3172fdd17b9f232ac1194a364e58b3f66ce63c177f7da877181ab340d9 conformance/invalid/010_required_value_missing/schema.yaml 63c33f2fba73bf59d5e881b0a7c8d6cb958f58b2de0fab22044a8eba50666f2b go/objectmodel/canonical.yaml @@ -138,9 +138,9 @@ 66181e4c6a0b2735b5a8b3088b37e6a844e2c692399a5b72b036059da0188e0a tools/releaselib/__init__.py 66c65cbf1c8a094275f1ec49420f2bed3c3841fca11ddba4080ae62059532aed conformance/materialization/002_origin_schema/expected.yaml 66e962cd93fcc33b1dd43760f78d7de6b7e718923536d814d36ff0da8ce10223 conformance/validation/010_primitive_member_is_not_a_node/meta.yaml +6734fec32b2c899e9e945fb42c40d199da37830a62ff36fa550c43ab717b53d3 rust/tests/conformance.rs 68a4754be7f4971ea34eb5277b054692d6582edece24fa50b279682c69380d2f conformance/materialization/003_origin_sealed/meta.yaml 6a9d33a921b501cfea3f229df86d6f26e6cbbfddfd9dde90d66451d1c25ae5f3 conformance/validation/010_primitive_member_is_not_a_node/object.yaml -6b78afd099dcc9b830c51b1da97ab491374167491f3f3b5c0c34129511f73750 go/conformance/conformance_test.go 6d9c5cce2c2ce964f0add1b4aeb97a9bce828ad46dd76b22929e284741273fc4 schemas/index.yaml 6db64092fb2c2dc8bcbd3e321faf04f6cc40b16e890c8dec3e56e4178df01402 LICENSE.yaml 6dbe5368b6622c9caa171419a4c8efce9c423960df4f45e69176ba4cba201981 go/objectmodel/template.yaml @@ -163,7 +163,6 @@ 76dfd3a8e0b9c8b5b2741e10cdefb64bbf24986a9fc3b448d53e2f7adedeb7f4 go/objectmodel/node.go 7758cf44c1eb621c310a5badf0222395851b1e4186ad143b19dd01e5e570949b conformance/materialization/009_normalize_map/expected.yaml 77eff1b8af60b8c502d01faa46c224449cf7f1d1f3e0d970d9614f22b3478f69 conformance/validation/003_origin_not_terminal/expected-error.yaml -78d6a00f5cb07f9675bce32f27fc0bb565858b203d2867bedb1bd3a5f31300cf project.yaml 78ddec6239d07162d53b5962e195055387d8b936e33c000ebeb75fba1242ef6a conformance/materialization/009_normalize_map/meta.yaml 791e0fac77217234c5d02a0c1e4bbc6e770a4f3b9805fffd013e3669fc81c2fb docs/en/architecture.yaml 7b0579d52d41811c7e6faa50bcebf0bb61084b7babffdc13996740a884aaccfa conformance/invalid/006_unknown_primitive/meta.yaml @@ -195,8 +194,8 @@ 8ac2a6cb77025dfe7eaf732e15887a28e0563a81a444d465a535372ca115cd6e conformance/materialization/002_origin_schema/input.yaml 8ac2a6cb77025dfe7eaf732e15887a28e0563a81a444d465a535372ca115cd6e conformance/materialization/003_origin_sealed/input.yaml 8ac2a6cb77025dfe7eaf732e15887a28e0563a81a444d465a535372ca115cd6e conformance/materialization/004_origin_sealed_schema/input.yaml +8ac2a6cb77025dfe7eaf732e15887a28e0563a81a444d465a535372ca115cd6e conformance/materialization/015_nested_sealed_template/input.yaml 8bc9462d24b8665180327f10566c7f3f963d36ba32792a06ab889bd0a58b245d conformance/validation/004_documentation_member/expected-error.yaml -8c8825a7cbc9348e2605ab6e37f55015df8d7fa485556f6ffbadfbc40aa77280 README.md 8d2c9c9396590aefcb54aaad5c9eccca0a41cc4ecafb5b473dbbb0e8f49c8e3a go/objectmodel/errors.yaml 8d90f4c93ede5d116093856ddc214346e8b4188d7c7fe456ad1b955658ab511e conformance/validation/010_primitive_member_is_not_a_node/expected-error.yaml 8d9adb32decde0cc3abe606560882a6c36cd15f095501d3f4e43ab9c7cfae03c conformance/invalid/001_sealed_yaml_conflict/input.yaml @@ -210,12 +209,15 @@ 93460d0514f94a64d51eff7fa538f78517aca3bafaf58ea15c6b9fc93f33bc7f conformance/validation/002_origin_empty/expected-error.yaml 94905a94361ac37947c3f5c6fb01b55d431a7459e8e8291ff6bbc25042c37fba conformance/materialization/010_normalize_empty_object/expected.yaml 94dabea4d0bdb7a81c750c4b2830f096c9ee2e59952fd1b9f7d4e235cddeb2f9 mk/golang.mk +96a0612d7f3d7d3faf96bbc408da0b32979eaee2599962e8d6d9e97c15b38d40 conformance/materialization/015_nested_sealed_template/schema.yaml 992423396d17250586acefe1ca589c21d650fb75c7f578bdff9cbb42fa1351fa conformance/invalid/012_scalar_position_sequence_payload/expected-error.yaml 9c11fd643268e578437b015dc5a4ea6e5a3f290f0303ac04ea4d429768208617 tools/schemalib/__init__.py +9c2c853fb20be48e4ebbfdf70a98e6890df116fd77fd85a6be2b010674afcaab go/conformance/conformance_test.go 9d0d594b86072a88bbda400992cb62d0b4faf71176a76c8f30c2e632c8eb5fb1 conformance/validation/011_more_than_one_document/meta.yaml 9da86d5094602943eeb486dae2ad45dec5f918b62db8d1783fb70a3bad3223d2 tests/test_tools/test_infra.yaml 9e78d6bee3ce3d4f6f73b296feb1de14dea12909e82220c8b405b359d0c0be84 conformance/materialization/001_origin_yaml/input.yaml 9e78d6bee3ce3d4f6f73b296feb1de14dea12909e82220c8b405b359d0c0be84 conformance/materialization/013_access_inherit_injection/input.yaml +9e8da6ff0cf2e6a54c8df92c1dce1cf7d66b0a4d22115ea41906b5055c00b058 docs/spec-vector-map.md 9ea1b819377debe7ec47bc8d487a104f5bb991608c4863cda24060264506711d go/objectmodel/schema.go 9f61f29a58d467eb8eb1c40fedb3b5e46342b19eed91fd4fd7233d7da83f510d conformance/invalid/005_origin_in_authoring_input/meta.yaml a03b3fccfc1f8eb7f76833fdff8a50ea84c80c039db87bc0e41109e96f64420b docs/en/concept/declarative_ecosystem_integration.md @@ -242,6 +244,7 @@ b3d0d0dd71c294b5fa8bad4fcdd9784feae35141aeb71e1755abd8bbd0cfca58 pyproject.toml b608bfc35f2bcb3d0bda472b13f3f1480c5e94c6ddeeacf5922c8fa0e5f86b15 conformance/invalid/002_sealed_yaml_schema_conflict/schema.yaml b608bfc35f2bcb3d0bda472b13f3f1480c5e94c6ddeeacf5922c8fa0e5f86b15 conformance/materialization/004_origin_sealed_schema/schema.yaml b614b3b83716f77c3f14216a59bb13c7205554ad802d0aea364c01f0e6c0af67 go/objectmodel/node.yaml +b6940b9faba73a0a5ff208868258c183d11efe9e90b0ff77268598c1f726d00b docs/en/architecture.md b6bda230e34be59f295ef57a8ba98469fcff5fd0a717f111ec4fc317f9de4a9f go/objectmodel/document.go b6c301c0695c34e7304717dcf3aeca7f022a7dec68b5e42303fad3e0d0aa57e1 conformance/invalid/006_unknown_primitive/expected-error.yaml b75db4a2182e6ebd8b246deedb6af40900dbc24520eaaec02c4738e9dc98d47a reviews/cbaf928.claim.md @@ -250,7 +253,6 @@ b7c2102b45f5be699a817f2b0e5010d03dddfb67bb74a471994dcb0c70d1e167 spec/origin.sc b7cc2d3d36c6fef33a7eaa1d13b35465ab293f7c22b6edeb1a86dd2ca834bc87 go/objectmodel/fuzz_test.go b92e00b843b3423caab27477ac02305bdd077a1c454a5f0dd142e0a90e8188a4 mk/ci.mk b9325f58a95216b240f3715a773e34fe8cdfa4d5fe48a5e7b32002a40be5cc2e tests/test_tools/test_finalize_release.py -b9d89d1f2d541eb9d523bc3f9b3939a0bdebabd5558038b86cb3ec19a8370af0 rust/tests/conformance.rs ba6a2c483d594433ee00f2cc64eec463a68ef2e47babdef5103dff597e3e1773 go/objectmodel/origin.go ba714d37d58ba80b5a99fe675c9dc1f6ace9fc48a2031846f90e1641f78faef1 conformance/invalid/005_origin_in_authoring_input/input.yaml ba83e549fe8de95a493a67b30279205fb01be02565d3a508bd2742bd7274be35 tests/test_tools/test_finalize_release.yaml @@ -262,6 +264,7 @@ bebbdc2c67e4aba1ca6dbb65f8673ea4e443ad986c94f85346d5bcebfbe4db8c reviews/cbaf92 bfccbd3cd778ac6f0458b7d45d7b25e2cbdc0a789f40e81c1de476f8aab44363 tools/schemalib/loader.py c077fdb2c25a5db12197280a20de96a1a5b7d59697f26275e0569bd9edf71cbd mk/rust.mk c4471dc9ac37e73e99045561799f7d9b71e284f77144f31cf80e23794032cb5b tests/test_tools/__init__.yaml +c49098a5392223d55ae4b4dcedebbbf3b3af02ba72f7bf1bcf5553f849ff245d conformance/materialization/015_nested_sealed_template/expected.yaml c4bbe4491584f47fe89a58a88f5a7a49deeac1cad1f488cb6c15b7c7a72962b7 conformance/invalid/011_list_position_scalar_payload/expected-error.yaml c54659558693be5238ff020322eb00ca80ce7d14aea461ee1ae24361e80fe84f tools/releaselib/vault_service.py c55c74bb201c91330aa8bda11dd256fe35f84b3a3a0e24a1cebe3e487bd60135 requirements.in @@ -270,6 +273,7 @@ c874c3372e425b2255c6eb1771e13f77fe6025923d06ced7b5a91259e28e1d22 go/objectmodel c8daecc8e4b81241d10c642c6c051712022bb8950a7855fd31d7805534de9586 conformance/materialization/006_closure_opaque/expected.yaml cadf923c73fdea6b9e1478f822f30d3f4d6c7463b15a12df709f0ae5d56743a2 conformance/validation/007_origin_duplicate_term/meta.yaml cc2109e262ef7d2c0ae924af62ae6b794934a37c9be09e35f1a175b22faa2483 go/objectmodel/construct.go +cc22d2735173aaf85a9944852f4522df726185bd7f8b023085fd7d0c5d7fe6d4 project.yaml cc94b0652d20afaf3b85ee72e50913f0277b6441642309670818355847f1e721 spec/node.schema.yaml ccc12224c9262ebed8b7bedb3046e9a359fa817e9c08659765d62d63355d0e3a conformance/materialization/014_hostile_keys/input.yaml cdc268dd275143f3cbad6b84078f397367c40951812143d227b5490cd007ce82 tools/infra.py diff --git a/README.md b/README.md index 7b47266..81b3f7e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ written into SPEC.md and propagated nowhere else. | Component | Status | |---|---| | `SPEC.md` — 46 numbered invariants | written, normative | -| `conformance/` — 37 vectors | executed against Go on every CI run | +| `conformance/` — 38 vectors | executed against Go on every CI run | | `docs/spec-vector-map.md` | 37 invariants vector-covered, 5 declared unvectorizable with a reason each | | `tools/check_spec_vectors.py` | runs and passes; negative-tested | | `go/` | implemented — corpus, fuzz, mutation, adversarial and CLI golden tests | @@ -26,7 +26,7 @@ written into SPEC.md and propagated nowhere else. | `mk/rust.mk` | present; digest-pinned toolchain, `make rust.quality` | | Docker build / CI | runs; `make ci` is the same pipeline locally and in Actions | -Both implementations pass all 37 vectors, and neither was written from the +Both implementations pass all 38 vectors, and neither was written from the other: the Rust one was written from `SPEC.md` and the corpus, deliberately not from `go/`. Two implementations that share an author's reading share that reading's mistakes, and their agreement then proves nothing. diff --git a/conformance/materialization/015_nested_sealed_template/expected.yaml b/conformance/materialization/015_nested_sealed_template/expected.yaml new file mode 100644 index 0000000..9b24137 --- /dev/null +++ b/conformance/materialization/015_nested_sealed_template/expected.yaml @@ -0,0 +1,21 @@ +--- +values: + a: + values: from-inner + origin: [{sealed: {template: $inner, path: $.leaf}}, schema] + shape: + values: + type: + values: scalar + origin: [schema] + scalar_type: + values: string + origin: [schema] + origin: [schema] +origin: [yaml] +shape: + values: + type: + values: object + origin: [schema] + origin: [schema] diff --git a/conformance/materialization/015_nested_sealed_template/input.yaml b/conformance/materialization/015_nested_sealed_template/input.yaml new file mode 100644 index 0000000..d9e1a25 --- /dev/null +++ b/conformance/materialization/015_nested_sealed_template/input.yaml @@ -0,0 +1,2 @@ +--- +{} diff --git a/conformance/materialization/015_nested_sealed_template/meta.yaml b/conformance/materialization/015_nested_sealed_template/meta.yaml new file mode 100644 index 0000000..456811e --- /dev/null +++ b/conformance/materialization/015_nested_sealed_template/meta.yaml @@ -0,0 +1,26 @@ +--- +id: materialization/015_nested_sealed_template +title: A sealed_from chain expands to the template it ultimately names +invariants: [INV-015, INV-005, INV-044] +description: >- + A template entry may itself be sealed from another template. One + implementation expanded the chain; the other resolved exactly one level, and + because a `sealed_from` node declares no shape of its own the node defaulted + to object and materialized as `{}`. Two different objects from one schema: + + go: values: from-inner + origin: [{sealed: {template: $inner, path: $.leaf}}, schema] + rust: values: {} + origin: [{sealed: {template: $outer, path: $.mid}}] + + The corpus had no nested template, so neither runner could see it. Vector 003 + and 004 cover a single level only. + + Note what the expansion also settles, which the specification does not state: + the origin names the INNERMOST template and path — the place the value came + from — not the mount point where the chain was entered. This vector pins that + answer; SPEC 5 should say it (audit semantic F-06). + + Expansion is bounded at 64 levels in both implementations. Without a bound a + chain of templates referring to each other is an unbounded loop over input a + caller supplies (audit semantic F-07). diff --git a/conformance/materialization/015_nested_sealed_template/schema.yaml b/conformance/materialization/015_nested_sealed_template/schema.yaml new file mode 100644 index 0000000..0b9ee36 --- /dev/null +++ b/conformance/materialization/015_nested_sealed_template/schema.yaml @@ -0,0 +1,20 @@ +--- +model: "0.2" +templates: + $inner: + $.leaf: + shape: scalar + scalar_type: string + default: from-inner + $outer: + $.mid: + sealed_from: + template: $inner + path: $.leaf +root: + shape: object + children: + a: + sealed_from: + template: $outer + path: $.mid diff --git a/docs/en/architecture.md b/docs/en/architecture.md index ed6acf0..d7b5608 100644 --- a/docs/en/architecture.md +++ b/docs/en/architecture.md @@ -8,7 +8,7 @@ model, and everything else here exists to keep that document honest. ```mermaid graph TD - S[SPEC.md — 46 numbered invariants] --> V[conformance/ — 37 vectors] + S[SPEC.md — 46 numbered invariants] --> V[conformance/ — 38 vectors] S --> M[docs/spec-vector-map.md] V --> G[go/ — reference implementation] V --> R[rust/ — reference implementation] diff --git a/docs/spec-vector-map.md b/docs/spec-vector-map.md index 46f3d5a..1ec6043 100644 --- a/docs/spec-vector-map.md +++ b/docs/spec-vector-map.md @@ -34,7 +34,7 @@ for months and "nothing else is" never was. | INV-012 | No `default` member on a canonical node | 4.5 | `validation/005_default_member` | | INV-013 | Origin grammar — exactly four forms | 5.2 | `materialization/001_origin_yaml`
`materialization/002_origin_schema`
`materialization/003_origin_sealed`
`materialization/004_origin_sealed_schema`
`materialization/009_normalize_map`
`materialization/010_normalize_empty_object`
`validation/001_origin_yaml_schema_conflict`
`validation/007_origin_duplicate_term`
`validation/008_origin_terms_out_of_order`
`validation/009_origin_sealed_schema_repeated` | | INV-014 | Origin is classification, not history | 5.2 | `invalid/005_origin_in_authoring_input` | -| INV-015 | `sealed` MUST carry `template` and `path` | 5.2 | `invalid/007_sealed_missing_path`
`materialization/003_origin_sealed`
`materialization/004_origin_sealed_schema` | +| INV-015 | `sealed` MUST carry `template` and `path` | 5.2 | `invalid/007_sealed_missing_path`
`materialization/003_origin_sealed`
`materialization/004_origin_sealed_schema`
`materialization/015_nested_sealed_template` | | INV-016 | `sealed` + `yaml` → invalid | 5.3 | `invalid/001_sealed_yaml_conflict`
`invalid/002_sealed_yaml_schema_conflict` | | INV-017 | `yaml` + `schema` → invalid | 5.3 | `validation/001_origin_yaml_schema_conflict` | | INV-018 | Empty origin → invalid | 5.3 | `validation/002_origin_empty` | diff --git a/go/conformance/conformance_test.go b/go/conformance/conformance_test.go index 9d9839d..667b9ba 100644 --- a/go/conformance/conformance_test.go +++ b/go/conformance/conformance_test.go @@ -92,7 +92,10 @@ var expectedCounts = map[string]int{ // 14 since materialization/014_hostile_keys: mapping keys were written // verbatim while the quoting rule applied only to values, so a child named // `a: b` made this package return bytes that are not YAML. - "materialization": 14, + // 15 since materialization/015_nested_sealed_template: a sealed_from chain + // expanded in one implementation and stopped after one level in the other, + // producing two different objects from one schema. + "materialization": 15, // 7 since model 0.2: `invalid/008_cyclic_primitive_declaration` was // removed. It asserted that a repeated primitive name is a cycle, which // INV-005 no longer treats as one — under INV-035 a primitive's payload diff --git a/project.yaml b/project.yaml index 3472eb7..f8811db 100644 --- a/project.yaml +++ b/project.yaml @@ -49,7 +49,7 @@ metadata: # # Recompute with `make release.subject`; check with `make release.verify`, # which a third party can run with a clone and a Python and nothing else. - buildHash: 'b419bbddff3053f3899c418015276de11f1f6874bb78fbe987426817bc235312' + buildHash: '830f62fda35e03d00836602fe9ab7d6237209f1c83277c5b79c10cf691605d4f' cicSign: 'TBD' cicSignedCA: certificate: "TBD — filled by the release process with the CIC Root CA certificate" diff --git a/rust/src/materialize.rs b/rust/src/materialize.rs index ba567d9..ab73d33 100644 --- a/rust/src/materialize.rs +++ b/rust/src/materialize.rs @@ -184,7 +184,28 @@ impl<'a> Ctx<'a> { Ok(node) } - /// Resolve a `sealed_from` to the template entry it names. + /// Resolve a `sealed_from` chain to the entry it ultimately names. + /// + /// A template entry may itself be sealed from another template, and this + /// used to resolve exactly ONE level: the entry was returned as the + /// effective schema, and since a `sealed_from` node declares no shape of + /// its own the node defaulted to `object` and materialized as `{}`. + /// + /// Measured against the Go implementation on a two-level chain: + /// + /// go: values: from-inner + /// origin: [{sealed: {template: $inner, path: $.leaf}}, schema] + /// rust: values: {} + /// origin: [{sealed: {template: $outer, path: $.mid}}] + /// + /// Two different objects from one schema, and the corpus had no nested + /// template, so neither runner could see it. Note what the expansion also + /// settles: the origin names the INNERMOST template and path, not the + /// mount point — the place the value actually came from. + /// + /// The depth bound matches Go's. It is not decoration: without it a chain + /// of templates referring to each other is an unbounded loop over input a + /// caller supplies. fn resolve_sealed<'s>( &self, sn: &'s SchemaNode, @@ -193,26 +214,42 @@ impl<'a> Ctx<'a> { where 'a: 's, { - let Some(ref r) = sn.sealed_from else { - return Ok((sn, None, None)); - }; - let Some(entry) = self.schema.template_entry(&r.template, &r.path) else { - return Err(Error::new( - code::TEMPLATE_NOT_FOUND, - "INV-015", - Stage::SchemaLoad, - path, - format!("no template `{}` declares `{}`", r.template, r.path), - )); - }; - Ok(( - entry, - Some(Sealed { + const MAX_TEMPLATE_DEPTH: usize = 64; + + let mut node = sn; + let mut sealed = None; + let mut content = None; + + for depth in 0..=MAX_TEMPLATE_DEPTH { + let Some(ref r) = node.sealed_from else { + return Ok((node, sealed, content)); + }; + if depth == MAX_TEMPLATE_DEPTH { + return Err(Error::new( + code::TEMPLATE_NOT_FOUND, + "INV-005", + Stage::SchemaLoad, + path, + "template expansion exceeded the maximum depth", + )); + } + let Some(entry) = self.schema.template_entry(&r.template, &r.path) else { + return Err(Error::new( + code::TEMPLATE_NOT_FOUND, + "INV-015", + Stage::SchemaLoad, + path, + format!("no template `{}` declares `{}`", r.template, r.path), + )); + }; + sealed = Some(Sealed { template: r.template.clone(), path: r.path.clone(), - }), - entry.content.as_ref(), - )) + }); + content = entry.content.as_ref(); + node = entry; + } + unreachable!("the loop returns or errors on every iteration") } /// A declared position takes a payload of its own arity (INV-008). diff --git a/rust/tests/conformance.rs b/rust/tests/conformance.rs index 3a42b7c..1388c87 100644 --- a/rust/tests/conformance.rs +++ b/rust/tests/conformance.rs @@ -29,7 +29,7 @@ const CORPUS: &str = "../conformance"; // things the serialization can do to an address before the model sees the // document. Both exist because the two implementations DISAGREED on real input // and the corpus could not see it. -const EXPECTED: [(&str, usize); 3] = [("materialization", 14), ("invalid", 12), ("validation", 11)]; +const EXPECTED: [(&str, usize); 3] = [("materialization", 15), ("invalid", 12), ("validation", 11)]; fn vectors(group: &str) -> Vec { let dir = Path::new(CORPUS).join(group);