diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 index a5ce1ec..a8b7e32 100644 --- a/MANIFEST.sha256 +++ b/MANIFEST.sha256 @@ -9,10 +9,10 @@ 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 -06bee6c4d4fc1902e0ddf778ac500892734c30dd7dd31d1e0ee84a72f785f960 rust/src/value.rs 08565714b7e2064ac0f9c691ed50215f80363adb08f8976589bebdcbb717f54e renovate.json 085f2e3cbd9cb9a6a7bc53a42cfa8be8fa51ca27cadd56edfdd21dda0d34e15b docs/external-review.md 090194d2cbde04322fd68bed8ce5427b4b0ad5daba1b26017958fed8ef29c964 tools/release_subject.py +093032b02fd12689869e1b765790be170479ccf2a2730d1d5aa1a7dcdc2dfd8b project.schema.yaml 0ac298f7c02da6032de189c7931958e6184aa4458e548339e5d4072ce5964717 conformance/validation/004_documentation_member/object.yaml 0ad89412a6fdc0a474993c5a43fc97f6fe74f79e517bdba509301eade72b71ca go/module/version_test.go 0bf03d8b25e93057ce99d461372fcb12646f120930ad89cc3ba1f566481bf97e tools/releaselib/__init__.yaml @@ -23,7 +23,6 @@ 0efb7a50d35b61684f8bb590bbfc0aeabb4b29950446e149c7114e39d3e69cf2 go/cmd/cic-materialize/cli_golden_test.go 10e336831076a5c7bc5069ae88c3110a896da814b41c57329ccdd1e48fcb2588 docker-compose.yml 110e9b4d3c721507eed06b35e740e7793b30f559afc1dd44a6b29ce0633e2339 tests/test_tools/test_releaselib/test_git_service.yaml -1126b99a345f693bd4c98eedaa86397ad4251f34f89dc48079f33764af0804d8 docs/hu/makefile-cheatsheet.md 113f83b3ca0443799224e8f29396b90777539217771ecb6f1c932df1b5168389 LICENSE.meta.yaml 11a0fb2cf1ce82ed7df0fe05a6fceb8e20051c48c4e9c80925f34d68dace503d tools/schemalib/validator.py 128105e3ea5af19b4c25555babfbf399c4309d569e7a94390da631d850d99c4a go/objectmodel/surface_test.go @@ -40,13 +39,11 @@ 1b3e25bf2122e2d44a8cfc13620797fd129911c5c74d8e1c1db96cee6994f1ce docs/hu/concept/git-management.meta.yaml 1cbf2e8be1e4909a51180757ab9362abe0b8212025a4ea63d60d1f9a4c15ca6a tests/test_tools/test_compiler.yaml 1d446b9b82e3a4e641645bb34a371dd42063f8732044d88108dd1715e9238a83 conformance/validation/008_origin_terms_out_of_order/meta.yaml -1eb0a2e05da45fdcb15a74530a19d27d34678fa5be2a8e01a72b032d9bfdb16d project.schema.yaml 1efd01f6d0488d9a28d8c8632b0d8d0b86711cb82f2da9e2eb31b915369db9c0 conformance/materialization/008_normalize_list/meta.yaml 1fb21ac33f8dfbeab2fe887dd40a03aeb834e28508f69e036de88e83a431b55a tests/test_tools/test_infra_coverage.py 1fb7dd2afe2b230d7c335db5c5da4e72e0ad271ce2dcd1683f446945e0abb917 conformance/materialization/011_discriminator_envelope/input.yaml 2020cd3da7008987b81055f0ef1bc7481567202da7f8bf49d616b1018f52bc21 mk/infra.mk 2063bf8ac333858a9b5c96de552fc4879ca3c35e5d0e34b0dd4067cec94bb5da Dockerfile -20b03084c87f86f2ed3c27cb48ca6d9bc8d55fb53e45ee350687079074520615 docs/hu/workflow.md 21a2f539315cb8500c9d812776d0e777b2eca73d668b3079b694b651fe8539d8 conformance/materialization/012_discriminator_payload_keywords/input.yaml 21a9994fa283ffca9aa0d063526ba4bc8b7d53d781bfbbee0d0041dcc04d143a conformance/invalid/005_origin_in_authoring_input/schema.yaml 21a9994fa283ffca9aa0d063526ba4bc8b7d53d781bfbbee0d0041dcc04d143a conformance/invalid/006_unknown_primitive/schema.yaml @@ -58,6 +55,7 @@ 275acc4de6f4d5fcd87330039777ceb8f8d8a7933c4e4924eccabe21a9f28da2 conformance/materialization/002_origin_schema/meta.yaml 27fc384a0d7987e4cb311648408b9b5a3995c0b58871ed55389ae154061209c9 conformance/validation/011_more_than_one_document/object.yaml 289ba61b1eec45ea29706ab25d49c8321c537ee7b2a509485bce0d206ef5d808 conformance/validation/004_documentation_member/meta.yaml +2aad6e42d4f8d43ad227efe0817299a11e90f9ff4678ada8b66368c47eafd380 docs/en/workflow.md 2ab20175d0b7d0f2a7ebe960903a08dc7fe69620615f253ae1fa3e584747edaa pytest.ini 2b1268f3aba4ac61ee21ea6a558fb8b01d2ce4964b8993b0e0b0aae583303b08 go/objectmodel/primitives.yaml 2b5c7c2028ce8d8fa9ec385fed586a85d22f30903373b511111011c6e6a34044 go/objectmodel/emit.go @@ -94,8 +92,10 @@ 41c6f331dba1cf92af69710525ba09bd8d64259ce1261a8fdd1dc335bd1464f2 conformance/materialization/001_origin_yaml/schema.yaml 41c6f331dba1cf92af69710525ba09bd8d64259ce1261a8fdd1dc335bd1464f2 conformance/materialization/002_origin_schema/schema.yaml 41f3a840901c311eb318b3b43cb4b5a942743ebb8d4b42f8d22986e694b29b3b rust/src/lib.rs +43122bc32ad7dbdf677ff873b572689ca63b5d4648b3188d894369de1bfc7532 docs/hu/workflow.md 43d6ecd6d720352839a363dbe9f102e685fb6b45280f3609e23b565ae05d3c0d tools/check_spec_vectors.yaml 44ace71302a7b671eff441e1960a1d68963698a8d7d17f572480c69de8e63664 conformance/validation/009_origin_sealed_schema_repeated/expected-error.yaml +4532163f1237656374c1cbca3038e91c459349f48798ae7462deb953f04ce4b2 go/objectmodel/validate.go 454f88b2e7e9f18293de540f4d2bee2821ae1d6b862eec4973399f353ab04499 go/objectmodel/yamlutil.go 475c277c385ff0cb55248bbfaf1888a4dcd86c0d9f2e8661eaffe6616c0a0e60 rust/src/canonical.rs 477943bac345e5da068ae948891595f6c3659961a144dc0f8495836a9bdc84f8 conformance/materialization/005_closure_structured/input.yaml @@ -106,7 +106,6 @@ 4fe73a4abf70523602e1fded96685b014d67e098f63c9fbdde1b3c26f97a8dcf conformance/invalid/008_duplicate_mapping_key/input.yaml 4fffcdac095c0623fefb8515ab80b3e9629c0febc454456cd08b3cdf3c941720 LICENSE.md 5028d0500342d09e11c1771e2c3500d0710576d82cb08d42273d8d64568d1b91 conformance/materialization/005_closure_structured/expected.yaml -50b2d2020539cbf68c490f905b86e297684b755678c3bfbe27a245d05a4ab440 go/objectmodel/validate.go 51b2d42be6a1e6e2f7449f3c0bf29f5a151910ef79db7d67bfcbe257a3642831 docs/decision-delta.md 528112e3921c47f13c7ec8ef080dd6f59d0b6d1610d17047fba7fb46c69f2e05 tools/mutate.py 534ca57fb760f1e30ec4d790a4e9ab71a228b4d0dbe8ac48c4c0ddf5dbcb37b9 conformance/materialization/006_closure_opaque/meta.yaml @@ -139,15 +138,14 @@ 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 -688dffd2264877f3a64fca1d3526c57bb34cb1c18f5a01aa906667311831a052 docs/en/workflow.md 68a4754be7f4971ea34eb5277b054692d6582edece24fa50b279682c69380d2f conformance/materialization/003_origin_sealed/meta.yaml -6903a0680e552da4f08e3525e1ff4420458d5829e9b447a98462c5c358910c8e project.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 6dd7b9100f38cead62d84545f2100f954f2a58186525a01c90ad80c62ed474c5 conformance/materialization/009_normalize_map/input.yaml +6f23abcefa29df01823be6698681f2e864638da20f804a230241550a4f882006 docs/en/makefile-cheatsheet.md 6f24ad15f6e31373a5e358090a48826cacb1cd034a6e5a71bba71915097beb5b tools/releaselib/vault_service.yaml 6f3655bf787efee284225bcb6096913d0ec556771dddb42845311e649c21e7ad docs/hu/concept/declarative_ecosystem_integration.md 6f3eb3a3c98c32ecda2d68a7c0bd13189f27db16294a83bdfc7cef0fadf688e5 go/objectmodel/origin.yaml @@ -156,8 +154,8 @@ 72216a3c233da96d69b16e2465e30b24169e7d6d3700cb6fe68463919076e95a reviews/cbaf928.adversarial.md 72c48fa014dea46236640cf52037631aa5db63a6f58807b3bf24577a247aa238 tools/check_doc_links.yaml 72e9b7c69632c1e1877e9f9d69812f3259003989b69f6b0fa7f23612d4514c94 tools/releaselib/git_service.yaml -735e9ac1e44c2a4926b59bbbdcf542888ce0be3a9e6a661496f8d1b7c76deb95 go/objectmodel/document.go 7426752feb1140b2023192728df35248e4f802617c150bb0523ab97dc1638c0b tools/init-hooks.sh +75b59a6cf6a2ce8af4fd04c8a0dcf7d33e5e5f132a48a981252ce61c05cf97c9 docs/hu/makefile-cheatsheet.md 75d086d878abe5943b2dd0c080c0ffb4fd88b517289c216ffa6bc5c1f12056f7 conformance/invalid/003_closure_undeclared/input.yaml 764d101d6bb896af8c4f082768f30f9f7cf77a6f0a0fed940b7b39805330d842 requirements.txt 76acd23cb8753e630beb9477b071c8e9c3234584db1a077e20bbcf47737cb23f tests/test_tools/test_releaselib/test_git_service.py @@ -165,6 +163,7 @@ 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 @@ -190,7 +189,6 @@ 87a06a94138975d7bcee23fd7e8b645f5aadfe728895c71da27d7b25ac2d2b0a go/inv032/testdata/forge/forge.go 8830ee9944b243af220aee813a4348c5435bf3abccdb57c0b9c9ad15ea6dadd1 conformance/invalid/002_sealed_yaml_schema_conflict/expected-error.yaml 885e419c23b97b7558a4f599267ef904a9f059ed3c455d3a95eda787ea7ab117 tools/releaselib/git_service.py -89f1fa5d8a2bcfab9015b51dfe0f60013d92f59595323e74d2cecd24e69962a2 go/objectmodel/branches_test.go 8a3dece67f758da0301a3ea1c5c34e8e7f33a17f41ea518fa016d6d2d386ce52 conformance/invalid/009_yaml_alias/meta.yaml 8ac2a6cb77025dfe7eaf732e15887a28e0563a81a444d465a535372ca115cd6e conformance/invalid/007_sealed_missing_path/input.yaml 8ac2a6cb77025dfe7eaf732e15887a28e0563a81a444d465a535372ca115cd6e conformance/invalid/010_required_value_missing/input.yaml @@ -226,10 +224,10 @@ a155c1bda117102e5a48969f23835620d528818b057cd98792345a04c5ceb359 conformance/RE a22ba9af1d65c75475635df964c7c0568417e1d0cd218c46321de4c4dc375041 tools/finalize_release.yaml a3f497092a2fe0fc41467edefff241687affff6f01adbbbf0dadbdefd791bd21 tools/compiler.py a41c7c65df2a47cffb061e4290f333ef26847c8d9d698ac568f8dd67cd3d59da conformance/materialization/013_access_inherit_injection/schema.yaml +a47aeee3986491faadcfd152d2d0f549d681d071c5bf7e38a7a9f1f3a7dfa5b1 go/objectmodel/branches_test.go a49ab35d001c64ab06a9368860224e2c15108c96840f632ff23b6e8b5e262cd8 rust/tests/foundation.rs a6c2baaaf307c0718ecaa43f654742269c3830fd9ff899a000af5e56713e5082 tools/__init__.py a77d0ec51190a8c5d1b9bcb57482773a68148313de6e4456288d3ac2cdf041d3 conformance/materialization/011_discriminator_envelope/expected.yaml -a8ecf9c2551505fd5a4c477c735c88fff9c4627136dade955cdcbfc16d8ca131 docs/en/makefile-cheatsheet.md a9f3d2ed8ade23e7a13e22961e535d83b104b0a91c7ae0b5867d0472ecb8860a rust/src/node.rs aa34e410797cbb287c42042a1724b377896616f7c2d4c3e023fe6d4ae2b762e4 go/objectmodel/defaults.yaml aa44b2b55d2ced16b5fc9072a97f2a0c0c30a34afd4055b8ef95591e77e02bfd reviews/82c05a168c5a20666d1dd5c898e2200ce072f9c47adead5207ebf5cb984d0870.md @@ -244,6 +242,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 +b6bda230e34be59f295ef57a8ba98469fcff5fd0a717f111ec4fc317f9de4a9f go/objectmodel/document.go b6c301c0695c34e7304717dcf3aeca7f022a7dec68b5e42303fad3e0d0aa57e1 conformance/invalid/006_unknown_primitive/expected-error.yaml b75db4a2182e6ebd8b246deedb6af40900dbc24520eaaec02c4738e9dc98d47a reviews/cbaf928.claim.md b78df50dc6d7807172273a4ded207fc85511901446f95b472e4ab26c389b4be7 conformance/validation/002_origin_empty/meta.yaml @@ -301,7 +300,6 @@ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 tests/infra/__ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 tests/test_compiler.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 tests/test_tools/__init__.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 tests/test_tools/test_releaselib/__init__.py -e3b48773d065af97cbdd3a1b086e7c9f9d89428a0a6e63a3c312ea334c59707a rust/src/validate.rs e3df83fc196b956b68cb7c09a7285c15b72e8b831cd5c74e33d0be13da7580ac conformance/validation/008_origin_terms_out_of_order/expected-error.yaml e42794fa9b56301ad073de55cac1dea683b320ed951454d8709ffc7156088dd2 go/go.sum e4e5841405e0f31bb3efb7f23df8b1e7a950175f0fd22c2fceadbb79cc3a12f3 conformance/materialization/012_discriminator_payload_keywords/meta.yaml @@ -310,6 +308,7 @@ e63367c6f011417eadf3f26e51dd648f6d85a7086d9db161d616b294e14b6995 tests/test_too e6c3bf74dfd983f1e70a662799cc9e6b99f28b18c7bc330c794f14919589f753 go/cmd/cic-materialize/main.yaml e6e946e141cd9d873d5f1012c9526956e7e22e55345e237aa52863dedcf7a2cd conformance/materialization/005_closure_structured/meta.yaml e7380ef0b0be1345db4c2857c6ba4b1aa8899b8eac78dec32d6ab7e51bdb3bee tests/test_tools/test_releaselib/test_exceptions.py +ea0573e86a27312b9ec88484a49facb7677f990d5cb52e62db4fd7f68e137bb1 rust/src/validate.rs ea3d9182bb0cd8f7601d2e80952adfdc91a0dc91c62ef4c8c53929f2d10641fd tools/releaselib/exceptions.py eb29d58fce2f9a987024ff05ae9dd6d3a8a421c3a0595c05eff3c9997fccfdf6 tools/schemalib/loader.yaml eb8d2d51fed8fb9fa1ad4081c02515942ffb42d830c643e8c8dffae765bee510 tools/schemalib/artifact.py @@ -329,6 +328,7 @@ f8a48b6ca4c4744cffd03edb72748831df9b83822989bba2bf72c5665466ea1b conformance/in f8d5eb9a78aad0da576ad083f7345da57620f3ca0c08d317727e353b44e4da49 go/objectmodel/doc.yaml fa3d02b5fc0677eb715d676e88b98b94016a76eb13e47abfa1040d0e6f12719f docs/rust-gate-extraction.md fa8afcf490e0b9b1e3ce94789f3233ed73289553ec94f16d2c347afd5e08b367 conformance/invalid/002_sealed_yaml_schema_conflict/input.yaml +fab14c32130c2345945fa55f41e6614bc443815733249fce81ed7d97e1d1f4cf rust/src/value.rs fd4ba258680da20f4084db2696fa942c09e47de8ec4650104f2b2e18c53980bc go/module/adversarial_test.go fdd8b8d94d94d9c08aafdc2b0e6f85db5deba627541786a8d72ef99c3eb37049 SPEC.md ff005c27b6c185b065c2121b01fa6f71c9668af920483714c9b137e411cec32c go/objectmodel/emit_test.go diff --git a/docs/en/makefile-cheatsheet.md b/docs/en/makefile-cheatsheet.md index 4e13a1b..6155544 100644 --- a/docs/en/makefile-cheatsheet.md +++ b/docs/en/makefile-cheatsheet.md @@ -18,10 +18,26 @@ This file provides a comprehensive list of all available `make` commands and the - `make typecheck`: Runs static type analysis on the Python codebase using `mypy`. - `make check`: A convenience target that runs `fmt`, `lint`, and `typecheck` in sequence. -## Release Management - -- `make release-dependency VERSION=`: This is the primary command for creating a signed, versioned artifact. It takes a `VERSION` argument (e.g., `v1.2.3`) and generates a signed schema file in the `/dependencies` directory. The process includes validation, checksumming, signing via Vault, and creating a new Git branch and tag for the release. -- `make release-schema VERSION=`: Similar to `release-dependency`, but intended for creating final, application-specific schemas. It places the signed artifact in the `/release` directory. +## Release and provenance + +- `make release.subject`: Prints the **release subject** — a digest over every + tracked file except `MANIFEST.sha256` and `project.yaml`, neither of which a + digest they carry can cover. It therefore binds `SPEC.md`, the schemas, every + conformance vector and both implementations. +- `make release.verify`: Checks that `project.yaml`'s `buildHash` is the subject + of the tree in front of it. Runs outside the container, stdlib only, so a + third party can verify a release with a clone and a Python. +- `make review.check`: Checks that an external review record exists for this + tree (INV-046). Not part of `make ci`, because it gates a release rather than + a commit; CI runs it on pull requests into `main`. +- `make release VERSION=`: The inherited Vault signing path. **Its + descriptor handling does not yet implement INV-045** — see + `docs/spec-defects.md` and the audit record in `reviews/`. + +The previous version of this section advertised `make release-dependency` and +`make release-schema`. Neither target has ever existed in this repository: both +were inherited from the base template, and `make -n` on either returns "No rule +to make target". Anyone following this page could not begin. ## Repository Setup diff --git a/docs/en/workflow.md b/docs/en/workflow.md index e066759..e62d252 100644 --- a/docs/en/workflow.md +++ b/docs/en/workflow.md @@ -82,30 +82,39 @@ This is the typical cycle you will follow when modifying or creating schemas. ## Creating a Release -When a schema is ready to be versioned and distributed, you will create a "release artifact". This is a signed, immutable version of the schema. +A release here is not a compiled artifact. The subject is the normative product: +the specification, the machine-readable schemas, every conformance vector and +every implementation shipped with it (SPEC INV-045). -1. **Ensure Your Working Directory is Clean:** - The release script will abort if you have uncommitted changes. - -2. **Run the Release Command:** - Use the `make release-dependency` command to generate a signed schema and place it in the `/dependencies` directory. The `VERSION` variable must be a valid semantic version (e.g., `v1.2.3`). +1. **Ensure the working tree is clean and the gates pass.** ```sh - make release-dependency VERSION=v1.0.0 + make ci ``` -3. **Review the Process:** - The script will perform the following actions automatically: - - Create a new release branch (e.g., `template-schema/releases/v1.0.0`). - - Invoke the `compiler.py` script to generate the signed artifact. - - Commit the new artifact to the release branch. - - Create a GPG-signed Git tag for the release version. - - Switch back to your original branch. +2. **Compute the subject and record it.** + + ```sh + make release.subject # prints the digest + # write it into project.yaml's metadata.buildHash + make manifest-update + make release.verify # confirms the descriptor describes this tree + ``` -4. **Push the Tag:** - The release process concludes by creating a local Git tag. To share the release with others, you must push this tag to the remote repository. +3. **Commission an external review of that subject** and file the record as + `reviews/.md`. `devel` reaches `main` only afterwards + (INV-046); the procedure and the three commissioning prompts are in + [`external-review.md`](../external-review.md). ```sh - # Example tag name: template-schema@v1.0.0 - git push origin + make review.check ``` + +4. **Open the pull request into `main`.** CI runs `review.check` for that + target branch, so a tree with no review for it cannot merge. + +This page previously instructed `make release-dependency VERSION=v1.0.0`. That +target has never existed in this repository — it was inherited from the base +template — so the documented procedure could not be started, let alone +completed. + diff --git a/docs/hu/makefile-cheatsheet.md b/docs/hu/makefile-cheatsheet.md index 25ec0e5..3b6b27c 100644 --- a/docs/hu/makefile-cheatsheet.md +++ b/docs/hu/makefile-cheatsheet.md @@ -18,10 +18,26 @@ Ez a fájl tartalmazza az összes elérhető `make` parancs és azok funkcióina - `make typecheck`: Statikus típusanalízist futtat a Python kódbázison a `mypy` segítségével. - `make check`: Egy kényelmi parancs, amely sorban futtatja a `fmt`, `lint` és `typecheck` parancsokat. -## Kiadáskezelés - -- `make release-dependency VERSION=`: Ez az elsődleges parancs egy aláírt, verziózott artefaktum létrehozásához. Egy `VERSION` argumentumot vár (pl. `v1.2.3`), és egy aláírt sémafájlt generál a `/dependencies` könyvtárba. A folyamat magában foglalja a validálást, ellenőrzőösszeg-számítást, Vaulton keresztüli aláírást, valamint egy új Git ág és tag létrehozását a kiadáshoz. -- `make release-schema VERSION=`: Hasonló a `release-dependency`-hez, de végleges, alkalmazás-specifikus sémák létrehozására szolgál. Az aláírt artefaktumot a `/release` könyvtárba helyezi. +## Kiadás és provenance + +- `make release.subject`: Kiírja a **kiadás alanyát** — egy digestet minden + követett fájl fölött, kivéve a `MANIFEST.sha256`-ot és a `project.yaml`-t, + mert egyik sem fedhető le olyan digesttel, amit ő maga hordoz. Így köti a + `SPEC.md`-t, a sémákat, minden konformancia-vektort és mindkét implementációt. +- `make release.verify`: Ellenőrzi, hogy a `project.yaml` `buildHash`-e az előtte + lévő fa alanya. Konténeren kívül fut, csak stdlib-bel, hogy egy harmadik fél + klónnal és egy Pythonnal ellenőrizhessen egy kiadást. +- `make review.check`: Ellenőrzi, hogy létezik-e külső review-rekord erre a fára + (INV-046). Nem része a `make ci`-nek, mert kiadást kapuz, nem commitot; a CI a + `main`-be menő pull requesteken futtatja. +- `make release VERSION=`: Az örökölt Vault-aláírási út. **A leírókezelése + még nem valósítja meg az INV-045-öt** — lásd `docs/spec-defects.md` és a + `reviews/` alatti audit-rekordot. + +E szakasz korábbi változata a `make release-dependency` és a `make release-schema` +parancsokat hirdette. Egyik target sem létezett soha ebben a repositoryban: mindkettő +a bázissablonból örökölt szöveg, és a `make -n` mindkettőre azt adja, hogy „No rule +to make target". Aki ezt az oldalt követte, el sem tudott indulni. ## Repository Beállítása diff --git a/docs/hu/workflow.md b/docs/hu/workflow.md index b030b8f..74fe040 100644 --- a/docs/hu/workflow.md +++ b/docs/hu/workflow.md @@ -82,30 +82,38 @@ Ez a tipikus ciklus, amelyet a sémák módosításakor vagy létrehozásakor k ## Kiadás Létrehozása -Amikor egy séma készen áll a verziózásra és terjesztésre, létrehozol egy "kiadási artefaktumot". Ez a séma egy aláírt, megváltoztathatatlan verziója. +A kiadás itt nem lefordított artefaktum. Az alany a normatív termék: a +specifikáció, a gépi olvasható sémák, minden konformancia-vektor és minden vele +szállított implementáció (SPEC INV-045). -1. **Győződj meg róla, hogy a munkakönyvtárad tiszta:** - A kiadási szkript leáll, ha vannak nem commit-olt módosításaid. - -2. **Futtasd a Kiadási Parancsot:** - Használd a `make release-dependency` parancsot egy aláírt séma generálásához, amely a `/dependencies` könyvtárba kerül. A `VERSION` változónak érvényes szemantikus verziónak kell lennie (pl. `v1.2.3`). +1. **A munkafa legyen tiszta, és a kapuk menjenek át.** ```sh - make release-dependency VERSION=v1.0.0 + make ci ``` -3. **Tekintsd át a Folyamatot:** - A szkript automatikusan a következő műveleteket hajtja végre: - - Létrehoz egy új kiadási ágat (pl. `template-schema/releases/v1.0.0`). - - Meghívja a `compiler.py` szkriptet az aláírt artefaktum generálásához. - - Commit-olja az új artefaktumot a kiadási ágra. - - Létrehoz egy GPG-aláírt Git taget a kiadási verzióhoz. - - Visszavált az eredeti ágadra. +2. **Számold ki az alanyt, és rögzítsd.** + + ```sh + make release.subject # kiírja a digestet + # írd be a project.yaml metadata.buildHash mezőjébe + make manifest-update + make release.verify # megerősíti, hogy a leíró ezt a fát írja le + ``` -4. **A Tag Feltöltése:** - A kiadási folyamat egy helyi Git tag létrehozásával zárul. Ahhoz, hogy a kiadást megoszd másokkal, fel kell töltened ezt a taget a távoli repository-ba. +3. **Rendelj külső vizsgálatot erre az alanyra**, és tedd le a rekordot + `reviews/.md` néven. A `devel` csak ezután érheti el a + `main`-t (INV-046); az eljárás és a három megrendelő prompt az + [`external-review.md`](../external-review.md) fájlban van. ```sh - # Példa tag névre: template-schema@v1.0.0 - git push origin + make review.check ``` + +4. **Nyisd meg a pull requestet a `main`-be.** A CI ehhez a célághoz futtatja a + `review.check`-et, tehát olyan fa, amihez nincs review, nem mergelhető. + +Ez az oldal korábban a `make release-dependency VERSION=v1.0.0` parancsot írta elő. +Az a target soha nem létezett ebben a repositoryban — a bázissablonból örökölt +szöveg volt —, tehát a dokumentált eljárást el sem lehetett kezdeni. + diff --git a/go/objectmodel/branches_test.go b/go/objectmodel/branches_test.go index df0bed0..d0a8c31 100644 --- a/go/objectmodel/branches_test.go +++ b/go/objectmodel/branches_test.go @@ -304,36 +304,46 @@ func TestSealedTemplateContent(t *testing.T) { }) } -// TestNonStringMappingKeys — YAML allows keys that are not strings, and -// gopkg.in/yaml.v3 hands those back as map[any]any rather than map[string]any. -// The library normalises them; without that, an input using a numeric key would -// take a different path through every mapping check in the pipeline. +// TestNonStringMappingKeys — a mapping key is a node name, and node names are +// strings. // -// The schema language has no way to declare such a key, so the correct outcome -// is a rejection that names it — not a crash, and not a silent skip. +// This used to assert INV-029: the key was read through the YAML node's textual +// value, so `1:` arrived as the name "1" and was then rejected for not being +// declared. A rejection for the wrong reason, and it hid two things. +// +// It made `1: x` and `'1': x` — different keys in YAML — indistinguishable, in +// a model whose purpose is unique addressing. And it disagreed with the Rust +// implementation, which refused non-string keys outright, so the two did not +// agree on which names a document contains. Neither was visible from any +// vector. +// +// The last case here was previously asserted as "does not crash and does not +// pass", with a comment saying pinning the invariant would pin behaviour nobody +// designed. It is designed now. func TestNonStringMappingKeys(t *testing.T) { schema := "model: \"0.2\"\nroot:\n shape: object\n children:\n a:\n shape: scalar\n scalar_type: string\n" for name, input := range map[string]string{ "an integer key": "1: one\n", "a boolean key": "true: yes\n", + "a null key": "~: x\n", + "a sequence key": "? [a, b]\n: value\n", } { t.Run(name, func(t *testing.T) { e := mustReject(t, schema, input, "entry-validation") - if e.Invariant != "INV-029" { - t.Errorf("invariant = %s, want INV-029 — the key is not declared", e.Invariant) + if e.Code != om.CodeMalformedDocument { + t.Errorf("code = %s, want %s", e.Code, om.CodeMalformedDocument) } }) } - // A complex key (a sequence used as a mapping key) also rejects, but under - // INV-007 rather than INV-029 — the stringified key apparently reaches the - // `origin` branch of the envelope walk. It is asserted here only as "does - // not crash and does not pass", because pinning the invariant would pin - // behaviour nobody designed. Worth a look; not worth guessing at. - t.Run("a key that is a list rejects, invariant unpinned", func(t *testing.T) { - if _, err := materialize(t, schema, "? [a, b]\n: value\n"); err == nil { - t.Error("a sequence used as a mapping key was accepted") + // The quoted form of the same text IS a string key, and is refused for the + // ordinary reason: nothing declares it. The check must reject the KEY FORM, + // not every key that happens to look numeric. + t.Run("a quoted numeric key is a string, merely undeclared", func(t *testing.T) { + e := mustReject(t, schema, "'1': one\n", "entry-validation") + if e.Invariant != "INV-029" { + t.Errorf("invariant = %s, want INV-029", e.Invariant) } }) } diff --git a/go/objectmodel/document.go b/go/objectmodel/document.go index 2fe6792..2d4028c 100644 --- a/go/objectmodel/document.go +++ b/go/objectmodel/document.go @@ -125,6 +125,24 @@ func checkNode(n *yaml.Node, what string, stage Stage) error { what)) } + // A mapping key is a node name, and node names are strings. + // + // This read the key node's textual .Value without looking at its tag, so + // `1: x` arrived as the name "1" — indistinguishable from `'1': x`, which + // is a different key in YAML. The Rust implementation rejected the same + // document, so the two disagreed on which names exist. Coercion is also + // how two distinct keys collapse into one in a model whose purpose is + // unique addressing. + if n.Kind == yaml.MappingNode { + for i := 0; i < len(n.Content); i += 2 { + if k := n.Content[i]; k.Tag != "" && k.Tag != "!!str" { + return newError(CodeMalformedDocument, "INV-013", stage, "$", + fmt.Sprintf("%s has a non-string mapping key (%s); node names are strings", + what, k.Value)) + } + } + } + // INV-041 — one address written twice has no single answer. The same name // at different addresses is not a duplicate, which is why this looks only // at the keys of ONE mapping. diff --git a/go/objectmodel/validate.go b/go/objectmodel/validate.go index 58e3a60..52e00b6 100644 --- a/go/objectmodel/validate.go +++ b/go/objectmodel/validate.go @@ -182,6 +182,28 @@ func validateOrigin(v any, path string) error { return newError(CodeSealedMissingTemplateOrPath, "INV-015", StageFinalValidation, path, "a sealed term must carry both template and path") } + // And nothing else, and both scalars. + // + // INV-013 says the grammar has exactly four productions. Checking + // that the two members are PRESENT leaves the term open: a + // constructor carrying a third member, or a `template` that is a + // sequence, passed both implementations while being no production + // the grammar contains. That is the same presence-versus-shape + // mistake the sequence check already fixed one level up. + if len(sm.keys) != 2 { + return newError(CodeOriginGrammar, "INV-013", StageFinalValidation, path, + fmt.Sprintf("a sealed term carries template and path and nothing else; found %v", sm.keys)) + } + for _, member := range []struct { + name string + value any + }{{"template", tpl}, {"path", pth}} { + switch member.value.(type) { + case *orderedMap, []any: + return newError(CodeOriginGrammar, "INV-013", StageFinalValidation, path, + fmt.Sprintf("a sealed term's %s must be a scalar", member.name)) + } + } hasSealed = true shape = append(shape, "sealed") default: diff --git a/project.schema.yaml b/project.schema.yaml index 7e6cf16..048baf4 100644 --- a/project.schema.yaml +++ b/project.schema.yaml @@ -187,7 +187,11 @@ properties: repo_type: type: string description: "The type of repository. Controls which compiler commands are available." - enum: ["schema", "workflow", "module"] + # `spec` was missing, so project.yaml — which declares repo_type: spec — + # could not validate against the schema it names as its own. Audit claim + # F-02: the descriptor and its schema disagreed, and nothing ran the + # validation that would have said so. + enum: ["schema", "workflow", "module", "spec"] component_name: type: string description: "Release component name (tools/infra.py: _check_base_branch_and_version)." diff --git a/project.yaml b/project.yaml index e568d84..3472eb7 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: '388ad2dc8f87f2d6951d2e8fffa0f2ccb843a46c2b07f6dafd11d0c98db124fb' + buildHash: 'b419bbddff3053f3899c418015276de11f1f6874bb78fbe987426817bc235312' cicSign: 'TBD' cicSignedCA: certificate: "TBD — filled by the release process with the CIC Root CA certificate" diff --git a/rust/src/validate.rs b/rust/src/validate.rs index 40008b0..cab7eb7 100644 --- a/rust/src/validate.rs +++ b/rust/src/validate.rs @@ -292,6 +292,37 @@ fn term(t: &Value, path: &str) -> Result { "a sealed term must carry both template and path", )); }; + // And nothing else, and both scalars. + // + // INV-013 says exactly four productions. Checking that the two + // members are PRESENT leaves the term open: a constructor with a + // third member, or a `template` that is a sequence, passed both + // implementations while being no production the grammar contains. + // The same presence-versus-shape mistake the sequence match fixed + // one level up, one level down. + if sm.len() != 2 { + return Err(Error::new( + code::ORIGIN_GRAMMAR, + "INV-013", + Stage::FinalValidation, + path, + format!( + "a sealed term carries template and path and nothing else; found {:?}", + sm.keys() + ), + )); + } + for name in ["template", "path"] { + if matches!(sm.get(name), Some(Value::Map(_) | Value::Seq(_))) { + return Err(Error::new( + code::ORIGIN_GRAMMAR, + "INV-013", + Stage::FinalValidation, + path, + format!("a sealed term's {name} must be a scalar"), + )); + } + } Ok(Term::Sealed(Sealed { template, path: p })) } _ => Err(Error::new( diff --git a/rust/src/value.rs b/rust/src/value.rs index 0e14904..2969d8e 100644 --- a/rust/src/value.rs +++ b/rust/src/value.rs @@ -48,6 +48,7 @@ use crate::error::{code, Error, Result, Stage}; use saphyr::{LoadableYamlNode, Yaml}; use saphyr_parser::{Event, Parser}; +use std::collections::HashSet; #[derive(Debug, Clone, PartialEq)] pub enum Value { @@ -72,6 +73,18 @@ impl Map { pub fn contains(&self, k: &str) -> bool { self.get(k).is_some() } + /// Append without looking for an existing key. + /// + /// `insert` searches the whole vector on every call, so building an n-key + /// mapping with it costs `n(n-1)/2` comparisons — the second quadratic in + /// this file, and the one that remained after the duplicate-key scan + /// stopped being the first. The parser can append safely because + /// `scan_input` has already refused duplicates, so there is nothing to + /// overwrite. + pub fn push(&mut self, k: impl Into, v: Value) { + self.0.push((k.into(), v)); + } + pub fn insert(&mut self, k: impl Into, v: Value) { let k = k.into(); if let Some(slot) = self.0.iter_mut().find(|(key, _)| *key == k) { @@ -232,7 +245,7 @@ fn scan_input(text: &str, stage: Stage, path: &str, what: &str) -> Result<()> { Event::MappingStart(..) => { consume_value(&mut stack); stack.push(Frame { - keys: Vec::new(), + keys: HashSet::new(), expecting_key: true, is_mapping: true, }); @@ -240,7 +253,7 @@ fn scan_input(text: &str, stage: Stage, path: &str, what: &str) -> Result<()> { Event::SequenceStart(..) => { consume_value(&mut stack); stack.push(Frame { - keys: Vec::new(), + keys: HashSet::new(), expecting_key: false, is_mapping: false, }); @@ -272,7 +285,7 @@ fn scan_input(text: &str, stage: Stage, path: &str, what: &str) -> Result<()> { ), )); } - frame.keys.push(k); + frame.keys.insert(k); } } _ => {} @@ -285,7 +298,15 @@ fn scan_input(text: &str, stage: Stage, path: &str, what: &str) -> Result<()> { /// scalar in it is a key or a value. Sequences push a frame too, so a mapping /// nested inside one does not inherit its parent's key set. struct Frame { - keys: Vec, + /// A set, not a `Vec`. + /// + /// This was a `Vec` with a `contains` before every push, which is + /// `n(n-1)/2` string comparisons for a flat mapping of n keys — quadratic, + /// under a comment claiming the scan is linear in the input's own size. + /// Measured on the `Vec`: 10k keys 1.0s, 20k 2.9s, 40k 11.6s for 389 KB. + /// Cheap to write, expensive to read, which is the shape of every + /// amplification finding in this file. + keys: HashSet, expecting_key: bool, is_mapping: bool, } @@ -325,7 +346,7 @@ fn convert(y: &Yaml, stage: Stage, path: &str, what: &str) -> Result { format!("{what} has a non-string mapping key; node names are strings"), )); }; - out.insert(key.to_string(), convert(v, stage, path, what)?); + out.push(key.to_string(), convert(v, stage, path, what)?); } Value::Map(out) }