diff --git a/GENERATED_CODE.md b/GENERATED_CODE.md
index 9b5405b..e863853 100644
--- a/GENERATED_CODE.md
+++ b/GENERATED_CODE.md
@@ -11,4 +11,8 @@ Output must be deterministic for identical source, compiler version, runtime ABI
The template/application author chooses the generated file's license to the extent they hold the necessary rights. A project-wide license may cover generated files because inline headers would be overwritten. Sandwich Hime adds provenance metadata, not an AGPL license identifier or a compiler copyright claim.
+The ordered v1 marker fields, compile-time ABI assertion, and source-map form
+are machine-checked against
+[`contracts/generated-provenance-v1.txt`](contracts/generated-provenance-v1.txt).
+
[OUTPUT_EXCEPTION.md](OUTPUT_EXCEPTION.md) is an additional permission for Cole Speelman-owned generator scaffolding copied into output. It is intended to remove licensing ambiguity without claiming that every generated file is or is not a derivative work. It does not cover third-party inputs, code copied manually from the compiler, other contributors' additions unless they grant the same permission, or the Apache-licensed runtime.
diff --git a/LICENSES.md b/LICENSES.md
index 23ff2bf..960484c 100644
--- a/LICENSES.md
+++ b/LICENSES.md
@@ -7,9 +7,12 @@ Sandwich Hime deliberately separates the development tool from application runti
| Path or material | License |
| --- | --- |
-| Project-authored files in the repository root, `cmd/**`, `internal/**`, `docs/**`, and `scripts/**`, except the legal texts listed below | AGPL-3.0-only |
+| Project-authored files in the repository root, `cmd/**`, `contracts/**`, `internal/**`, `docs/**`, and `scripts/**`, except the legal texts listed below | AGPL-3.0-only |
| Nested `sando/**` runtime module, except its verbatim license text | Apache-2.0 |
| `LICENSE`, `sando/LICENSE`, and `DCO.txt` | Their own stated copying terms and notices |
+| `contracts/himesan-config-v1.schema.json` | AGPL-3.0-only |
+| `contracts/himesan-operation-output-v1.schema.json` | AGPL-3.0-only |
+| `contracts/himesan-version-output-v1.schema.json` | AGPL-3.0-only |
| User-authored `.sando` templates | Chosen by their author, subject to rights in their inputs |
| Generated application `.sando.go` files | Chosen by the template/application author, subject to rights in their inputs and dependencies |
diff --git a/PUBLIC-SNAPSHOT.json b/PUBLIC-SNAPSHOT.json
index f5fc685..be7d310 100644
--- a/PUBLIC-SNAPSHOT.json
+++ b/PUBLIC-SNAPSHOT.json
@@ -1 +1 @@
-{"schema_version":2,"project":"sandwich-hime","export_policy":"exact-allowlist-v1","export_mode":"release","file_count":89,"allowlist_sha256":"2947ef034f9bfe9bd20c00e67d0033f0ff5e62e55fc2db0952696485d9eeec40","manifest_sha256":"5005a799e84f5cbb66cc9d0ced03c5897924de1b9c28adf8aaf06b2ddb4affff"}
+{"schema_version":2,"project":"sandwich-hime","export_policy":"exact-allowlist-v1","export_mode":"release","file_count":125,"allowlist_sha256":"b320a95ca85f48941ee956fde1eda46e0d9bc664da21d13c5f65a8f623063ed5","manifest_sha256":"882a7147949ab827f11102095dbe6cbd75811336e677192969c3abb25ffb0001"}
diff --git a/PUBLIC-SNAPSHOT.sha256 b/PUBLIC-SNAPSHOT.sha256
index 182d42a..60033d0 100644
--- a/PUBLIC-SNAPSHOT.sha256
+++ b/PUBLIC-SNAPSHOT.sha256
@@ -7,30 +7,38 @@ b696cab3cf482ff5737501371cca749369b119351383e698ced42bcdbcbfc8ae ./CLA.md
797e884105738fc931b585b695424f43ec5f296d8ab9bba5191b096e87a9e2c2 ./CONTRIBUTING.md
86d7e49d5d90e0f98a4ad0f14b5d8b9f11ed09a1e29ecdf27388316b28e195e8 ./COPYRIGHT
f7ac75b443f4ca16b503241344b41aeff9503b0c30bedc2b119551d83cb0fa90 ./DCO.txt
-73cb1b092a40c56e522c5a0ebddf2b44f347cdb57bf6994cdb305d0e5697b55e ./GENERATED_CODE.md
+8ac46ef4e559f0bb43a8acb8cf6013052e09d41aa591a25c5e6fd462179eb067 ./GENERATED_CODE.md
a4570d054f072d33b8f17b0c8b162a6ee0ca37d7df2b1aee7e4b728ab350a892 ./GOVERNANCE.md
0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 ./LICENSE
-47d857e49f89596bac9b09fc8ca57a668a33d01e2b51508acfc92ed321cdc27f ./LICENSES.md
+9957e4aea02effb4d4b8012a3cc0e2d6ee1ce8be3761555c2c3e8aee27a5ee08 ./LICENSES.md
b6aa08e5ccaec3c5dccdc19d7cd7f54a70adae4d57966263c7aa353c7ba70e08 ./MAINTAINERS.md
6638db2f1fba831c79de835ce95c847a5b36c5b5c693b99a28655b2d096cc440 ./OUTPUT_EXCEPTION.md
-fbc4a7c118ac983a1ea49dc3a9d714f5130ace21a8af59ab9fcabd6519cb6b97 ./README.md
-fafa1494fa1a5a5cf3b3d371155f0448d46f1f13cd394555e06396b336bc102a ./RELEASE.md
+de0d4945a8246edc8da46fbc54f18349c45b6b08069309d071cb33127aee101d ./README.md
+14fbed2a6de1496d7ad3783bac1b79f3f340de72545fe0dcf39de829622d7d32 ./RELEASE.md
c0e65a8bffcba71cd42d6122be25fd4993c04c8cba8c5e69108c9f5dbaca9243 ./ROADMAP.md
-17e10aaab589d40b479e374523982117d2c7ffac95306493cfa4e3171108a1a8 ./SECURITY.md
-53bd6eda804d6b782bdb07115ec197c890813cf2d5d0125dfe8f47f5f92f75b0 ./SPEC.md
+813e329cfc692c0a341c47a12e2950b0c2ee403b739ad763a204d678a27765b5 ./SECURITY.md
+d566b8b27777cb33209602df81d654a94072ad875440c089ac41764a349b6c17 ./SPEC.md
3d9e680cdfe147df7cc9ff29ecf1d3e566e9cd559ae84db4880e559b9c7c7205 ./TRADEMARKS.md
+e2ce8cbc43e4575984839b61332207f7c90e2848fa270d4c68dbc5fc394f28e7 ./cmd/himesan-release/main.go
+9dda7402f76b38d417981f9e7658880b592c282c00163a3fdbfb0ab55e1edcba ./cmd/himesan/contract_test.go
8cd8db68e1300f9b78cc7235855853cbc7aeb499a921419e23a22e4d22826fcb ./cmd/himesan/main.go
-0709990e28d58d50e49219690e435338b269284efb6c2d2e2c95ed6b242580df ./cmd/himesan/main_test.go
+8c53127437cd8a77676fdbcdf62f6dd9346e533665bf7d5ac32f103839a24974 ./cmd/himesan/main_test.go
+de004164324a640555256a91ca21a02be163bd3d231b38f08f8539c9a77ef665 ./contracts/diagnostic-codes-v1.txt
+60de7d2cb9e8626136a0d197cbf55d2eed545ebab184cca66d83d5593a45b8d7 ./contracts/generated-provenance-v1.txt
+e6fb36b274bf44143497b2af454e9ee0fabe6122f935cbf97e785a003277e211 ./contracts/himesan-cli-help-v1.txt
+6cad9bb55238c268b25937d02ac1708bb2302c8614556648fb6c63b4479d053b ./contracts/himesan-config-v1.schema.json
+09e574ed55e3a48fa117936465d3de887686cf3f2a5c4231cf598ff30e4b1a75 ./contracts/himesan-operation-output-v1.schema.json
+7679c9908266811a4336be9698982e58ffedecedf2c340b082bf5dcdef1554dd ./contracts/himesan-version-output-v1.schema.json
1ecbba46f8b1b2d548a01d7e98afae17b2dd17a814338ff1f88db885655d1c07 ./docs/ARCHITECTURE.md
-9c598559a89fa4a9bdd2311bd1ed8330992d0a0f74ec8b29ac151fc0ff8fef16 ./docs/BENCHMARKS.md
+9e4f5a80afbffe51dda3e7f9fa5221e3ef4ca516c4b7f7ca33b61ac97ff213bb ./docs/BENCHMARKS.md
5c3a62fed80ca28d56558b8c75e8b5be8ba7d2554127adf4609d96da314e85b0 ./docs/BRAND.md
-a88ae86f046779db2ee4e0e7458510d782c459ab898efb8b58decaec53f72743 ./docs/COMPATIBILITY.md
-5f4ac209a16ab110baeaa64a40c19d9239c903e17550c3f05e1e1473ddcc33a3 ./docs/DEVELOPMENT_SERVER.md
-51aa57a81131b64f76c45552122de842f22be92d81c8bba8f6fd38a18a7670d6 ./docs/DIAGNOSTICS.md
+8627f689faa123507102e3c2114c368f905580ea2b0151c717860de9cfa7a4b0 ./docs/COMPATIBILITY.md
+d1b4ef192e02b59a9d8d0eb3b1072392e633116433fa308f6ca9bbe17b75e213 ./docs/DEVELOPMENT_SERVER.md
+4d92dc88ef33d4f7d952de039f842e54a3f3bfb4dbe56debf7ddb6e6c4a428f6 ./docs/DIAGNOSTICS.md
a62cc7174f3c92d8ef77e4bd9607fbf5d4b80bc514ff05bd433c02a9b0578f18 ./docs/LANGUAGE_SERVER.md
-f2622e0eba601470624e862caf717060c7b73037f13f0b7bd6e9792a49463480 ./docs/SECURITY_EVIDENCE.md
-f2423c325ebe5371af43db6b09b11ea5a4ea3d3d3c14098f9d0ccd89110ea03d ./docs/THREAT_MODEL.md
-bb2fde5ffd9736ef2a46b2931484bcec7b872353c7c20821a8fa7a32946fa97d ./docs/V1_RELEASE_PLAN.md
+32253dbe59d9b0da40354b48c2e54feb8c38d441b41efba046125571d3dabb50 ./docs/SECURITY_EVIDENCE.md
+e600caf17a87997de330ca787bfc5f16c1b20e07f467f8d1ad450e96e2421910 ./docs/THREAT_MODEL.md
+280e5d78f5bead40f10308b750c725d2b2aca00aaf435aa07419bbc290b37fb0 ./docs/V1_RELEASE_PLAN.md
f27c46ca63707bb8cc570eab1ea521824e94bc59b1d153998a5e91c2c7340d16 ./go.mod
ca0bf5051d356d2602f46201fb1637ce48b629ad42161877eec13f743f215dc5 ./internal/compiler/abi_test.go
d891b9b075617050471b2ca34de73d926aaebde4ec638a5039b0d5001d3172f4 ./internal/compiler/analysis.go
@@ -38,10 +46,11 @@ c123bf8c8fd847962e147041172b71fef7e852819ccd7b27d4648748e02d522f ./internal/com
6ef6a0f15a5aca1c8708cbf24218372e1fca9c6fead1a5a75d261faa69651af7 ./internal/compiler/backend.go
4d309392a7739b26e41cd9d9db71b52077bb8198573634ddf793ad531acc81cf ./internal/compiler/compiler_test.go
dbba23e360bd6dd1e8f42953a49a7cfcc241aa3ac76f5ce505ec8f8558833c84 ./internal/compiler/context.go
+1e0f83c3e9eea34a23e8d62f8da36bb697268531e8edd9d76a1f3752abfeb563 ./internal/compiler/contract_test.go
b2a96ef1ad572ad9cd0e9247328ca261de6f9f3689da41e3f3e111d405a6dee6 ./internal/compiler/diagnostic.go
6a3dccd0c7caf92fe2690b350f1db6f34037609b1fff6d3e68e0e229241950e5 ./internal/compiler/discover.go
-50a8bad11deea99faf0c7c9de1266bc086b7a1247ff9dd275ffedfc69a3be81d ./internal/compiler/e2e_test.go
-eefb05a35bd07660a293c8af97949cd6f69a22709728f3fe2cc9132b863b7d5a ./internal/compiler/fuzz_test.go
+13fb6843b4e43fa766f8fbb7dc7f04fa7328edf159d40c5a51cd87b1a0b2dc6c ./internal/compiler/e2e_test.go
+3cf1b6c26b2551ad2e8f1e06e45adfdf8b72e6f9ce7f9baf6b73209b7d788022 ./internal/compiler/fuzz_test.go
440580fcd844b2c80b1f4dddc4d83db82974e203cca298387a190e53e6a524f3 ./internal/compiler/model.go
01516c0330b14a1970019690d8efcc9004d2dcfa59e82fef1b67f98005b67532 ./internal/compiler/operation.go
1f633caa019d8de9ae5bbad27e29679fcc01b3cb9a33d7d616be07140e06f38b ./internal/compiler/parse.go
@@ -49,11 +58,13 @@ eefb05a35bd07660a293c8af97949cd6f69a22709728f3fe2cc9132b863b7d5a ./internal/com
0fff1c67447bf5353ed1df6e7dfc4b14581b67adc1bf02f7a4a7c1f2680c392f ./internal/compiler/replace_windows.go
f4ba01010ed5f5ba1e979702d82e95312bc0a4b13cc205c098926839be4ecb73 ./internal/compiler/testdata/golden/basic.sando
63fa75a3049a3a8a12d769d7f9b6b510dfe763baacf706775b75cef2c57a984f ./internal/compiler/testdata/golden/basic.sando.go
+3b9755803bb66a08ce74a973d275234a593b6a944e7fe325de35bdbe52e62c6b ./internal/devserver/browser_evidence_test.go
eafbe9f7d8abb8fa792ec9e01f56655f9ec9d67279ffaac66d6035f9b2bfc404 ./internal/devserver/config.go
99807040a870dd38ad1e04ae179243316778f94a41feb5d2c3076d463f52f9fe ./internal/devserver/config_test.go
+53cdeb4131976a4191b43ae3537b7883c18b69be9468aaae6b2cfed86c13bed2 ./internal/devserver/contract_test.go
eddac51aecaac99bd11cfcf98f8a47cec5d51672efedad75d6f2a862c5d57fc1 ./internal/devserver/events.go
-36c4a415ee8201064b2365647f24b3cc7497bf74fefbc30db8e3d4dadcc2997b ./internal/devserver/process.go
-72074ddb02d2eae3cabe5a79c648fe0debbae3ffaa1b0800b897259eca74f209 ./internal/devserver/process_test.go
+1449c00e9f25a0a2cd2ecda99ee5b11d6b30a48ff7ba0eb2f00572f917ac332a ./internal/devserver/process.go
+c1a487deaca6ab22b4d45b9fa7cd1918834443f258945a9472967852ee55daff ./internal/devserver/process_test.go
5286e7ba5f59114e37265ab614d9dfc14bfb3003eccc93278e819e52d12f5e7a ./internal/devserver/process_unix.go
c0f76ef5c14b0a28ed1e68d8d518102ffcbf067285b087eed4d13cd3c87b0e00 ./internal/devserver/process_windows.go
6fbbe08813385ed43a9377b3772260e577dbd9f742f7a9ed5140a02f4c7991a4 ./internal/devserver/process_windows_32.go
@@ -61,7 +72,7 @@ c0f76ef5c14b0a28ed1e68d8d518102ffcbf067285b087eed4d13cd3c87b0e00 ./internal/dev
7f1efbefea3a277f0f4d96a29219293efd78d9dc44823c09b9667b19d5042047 ./internal/devserver/proxy.go
aebf8388576d7bc9b047ceedf8a893acb3ace5fe16f44cb883efe63eef072ef9 ./internal/devserver/proxy_test.go
e6561e693138a3b77be06c1a98999e71494bbca0d0c72ccb9bff57b8e8575c0f ./internal/devserver/supervisor.go
-5c0ce1eedb08c0381dd048526e9f781a2ac8501cc1142648c43d4aa07ca62299 ./internal/devserver/supervisor_test.go
+40872123cec152daad73eaaabdba465033a4de57fade62790c29a8655444e67c ./internal/devserver/supervisor_test.go
e0a682c0153bf4f2a1f26cc6095d7893ad96e6199cbe76d0150785fc996f1141 ./internal/devserver/watch.go
b7a7fabf9a6c497f7ac2262628c5fb37a6bd00da676e1b7d5088d5f649c9f14c ./internal/devserver/watch_test.go
76609ee5c57b1c174076689d4953e6ec5ef914ca114c27f1ca1d6e7fcabbddcf ./internal/lsp/features.go
@@ -69,21 +80,46 @@ edd377bca36a04ae76620a146f986e25c954fd137bab0b0e49084dbcde216ce7 ./internal/lsp
73b2ff8d6fd8fe44abec287384f0051520fe9564a50b4764538474f336624eee ./internal/lsp/protocol_test.go
2a2151a0211a5048c3bc89f9a2901a3c208609f483e99144f3837f87941efff9 ./internal/lsp/security_test.go
3be838b4bb0b618f46c0eab40788a1e03121da25644dedfa7542bd0fa64571e1 ./internal/lsp/server.go
-c919b132373d9ab715cdaf057789a3d23f27b1c9f0eded14c06d04d2a4c86ee3 ./internal/lsp/server_test.go
+99f7ccc8768bd92efe6aec9039d4a1f8ba5a2d072da50c65ab3ed887e8ad9cf5 ./internal/lsp/server_test.go
4c5ade5f649dffdd0a1b0a350c15488fc601b7eba582386f39e64412387b5c91 ./internal/lsp/workspace.go
+21ec36dd353b237cd6e6705d09b2ad8a1b7dade8ad18024976e4c6fc140294d2 ./internal/releaseartifact/artifact.go
+0c482e0c0ff2659ee6c650c56a7a267239cf8149283dfe5b3e680a5e3ab47121 ./internal/releaseartifact/artifact_test.go
+83dd9a9d683fc443e69449bc0ac5cdc5c764f4404718c41b3a05676f7abef6d0 ./internal/releaseartifact/evidence.go
+40faefc3a8ab0a6e1e28972fc515e118989da068186307133ae83c676604d741 ./internal/releaseartifact/evidence_test.go
+77e7dc75b48765bb4ce926c634b0eaaded08b4d59d0951ee814001d23aebcf4c ./internal/releaseartifact/macos.go
+8e31a8c4654e8a1bc8a025b350494f509b5979b61e500cc937dcbc916ee87d39 ./internal/releaseartifact/macos_test.go
+f2e1873c4beb131266d6b4b8ee27acc12b7cb1baca734cc0d9586fb14484b7b0 ./internal/releaseartifact/native.go
+c07cd08a7875f851981a11a4d570a7784cf453faafe0c991aa43881cd4f2e39a ./internal/releaseartifact/native_test.go
+fd1babf093a791d7e50a1faa3d4b00e42a1e487ad64eece7841a3f4d81cb4072 ./internal/testpath/tempdir.go
+5a8ffb091328464b84403ea54a8911e41173881a4909a334ba2e4c44dd1c0de4 ./internal/testpath/tempdir_test.go
d8c6f37c94ef426fc2d95c82331265f7d700d2e2a23100ad78c92849280ff6d8 ./internal/version/version.go
a1ee0ec44dc45f01a3357cf93e13a81587af7f061497c25df374e2a88231f3b7 ./internal/version/version_test.go
e8a3026ec920d7312f843e2001e50ae4e34fd1ba5f9b2ae25a6113de1fa88385 ./sando/COPYRIGHT
c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4 ./sando/LICENSE
-b4a7bffe678a97209881e07989563a5085aa0ead9e1b67306087dac6b97bad70 ./sando/README.md
+9b9b09b595658cf65aef8f7ace9cc955f026cdd71ece0f79671bfada8bd9c260 ./sando/README.md
+acfbbc860417aeabba486ca4fea530e711c3e488bc3f5f6588db102b8a58bec2 ./sando/api_contract_test.go
+7e862661f19fc9fcd694ef64e65fda898186c51836335c27c21397e8dcf09c05 ./sando/benchmark_test.go
7ec3fe73755a385e0950b9fbf833dd4b6a753a769ab743e97b9d94e77370a32c ./sando/component.go
a242fd3bebb9cb8786c92651950999c6a2575d0be9602bac562e0b63c9ded015 ./sando/component_test.go
+e12e2b27396ba4447d27cb1f80ef44100f7295135c26a1bdad5e13e1312a3a5f ./sando/differential_test.go
ff76daee5b642ad84af31701833246d68b54d09580192312d750a7a2e893a692 ./sando/go.mod
+ed2b8d8040689c608623d30085a55678ae3c9d70a8a40a42435e04bff47a31ab ./sando/testdata/public-api-v1.txt
80ff53787919e809b8085d6ad9c3e183c9c7c1d74cfeda73369ac5c4607c236f ./sando/trust.go
85621a44c730582f4410ac2c70418b739fb55e916f7e6b73a1a619982c459572 ./sando/write.go
b188917e258890e6b6e4840a6fd946fc9a77cabc2068da3764f221e4a6a5df97 ./sando/write_test.go
-36265470310f8463895761bb53a2137583d395d4b19b0190d038eecce1f4ce25 ./scripts/README.md
-0bc796f71c863aa898674a26c56f055e3d81cf20629ca7b32fbae87d8841e0a8 ./scripts/check-licenses.sh
-03d58bea32691d6d503983ddcf979fb88091eed4cb322e74a9eeb4ee434bacec ./scripts/release-check.sh
-78a64c7fb3a039b15a1d08b4c0b873952852287a07f670247b081e59dbb09a30 ./scripts/verify-public-install.sh
-f0cbd86759fa729064cb1c69991db2ac291792dadb6b1e1ba83794f2e390404d ./scripts/verify.sh
+6ea995e9d7d22e09a13065a6e340df4efa0f1860b0b42409f84835c93f29962c ./scripts/PUBLIC_SNAPSHOT.md
+2d152851bc77f6eb5ca842a8021cde386a3cae91db819cd99cedc31b3b62518f ./scripts/README.md
+81ab4c10a436bc9b345828a55c2fe446f297fa6cfd1debfe95e1082b6e28c969 ./scripts/check-licenses.sh
+3adba1428df83d6180d4fa3c683f0604dc7cdc0c4358f3dae7595189cc40f949 ./scripts/export-public-snapshot.sh
+47dceef111d1da4345b473ff9092df7fcdab655fa1ec660e58012f75c5135d83 ./scripts/native-gate.sh
+a038a002f0c895fef38a7265bcb7df01efdda57de5c4d36d0dc7c8e377798a95 ./scripts/package-macos.sh
+7d1e3606f3159e0d5d222bb5b02171ad90bb2c42b0e041042dcf8e6b7692c40a ./scripts/package-native.sh
+1fddbdf81e86f4310a603cedd0f24a39d6c62d810c7e1e2e6c5f7bff1d1d4198 ./scripts/public-snapshot-lib.sh
+b320a95ca85f48941ee956fde1eda46e0d9bc664da21d13c5f65a8f623063ed5 ./scripts/public-snapshot.allow
+183b76febe506a8e0dfaa70ddf6269749ab2c475e6c345c8adfb2ad6e7e1019b ./scripts/release-check.sh
+494cf5573cc6a891d4243ec01a98c43263de75090cb3b55c9c398e545b1ec1a8 ./scripts/sign-notarize-macos.sh
+7c65325664e95df1a63262bc440a599fb799b36123a68c5cc26cfb81ddd08efa ./scripts/test-public-snapshot.sh
+39a80708b1c1a64c837032c6570e77dea28b2c6e6058eea40efac63ad5827431 ./scripts/verify-consumer.sh
+ddcb5602a51a515ee6649e26f27d919addb0590abdcc095c374c90f5b0c2f8a3 ./scripts/verify-public-install.sh
+1673257efa50b906363ccab45f4ebcf7cb377185861a1c37221c98292c2c7cf6 ./scripts/verify-real-browser.sh
+d67b73b716fa795bf166df12ad91eea74225b6c76e659d0e8b088dad221865aa ./scripts/verify.sh
diff --git a/README.md b/README.md
index ba37022..89bc029 100644
--- a/README.md
+++ b/README.md
@@ -48,17 +48,17 @@ semantic-version prerelease: source syntax, generated output, the runtime API,
and CLI behavior may change before final v1, and this beta is not recommended
for production deployment.
-Linux/amd64 is the maintained execution and release target. Required release
-evidence runs on Linux with the supported Go lines. WSL is a useful Linux
-development environment, but it does not turn native Windows into a supported
-target. Native Windows, macOS, and other operating systems may happen to build
-or work and portability reports are welcome; they are not release gates or a
-maintained compatibility promise.
+Linux/amd64 and Apple Silicon macOS/arm64 are the maintained v1 execution and
+release targets. Native release evidence runs with pinned Go 1.26.7 and Go
+1.27.0 toolchains on both platforms; the module language directive remains Go
+1.25 for consumer compatibility. WSL, native Windows, Intel macOS, and other
+targets may be useful development or portability environments but are not v1
+compatibility promises.
The evidence ledger retains the exact Beta 1 Windows and Linux observations as
historical facts. Those past results do not expand the current support policy.
Maintainers remain responsible for security review, triage, fixes, and release
-decisions on the supported Linux target.
+decisions on both supported native targets.
Inside an application module, add the small runtime first:
diff --git a/RELEASE.md b/RELEASE.md
index 28e2bba..00b62f0 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -21,11 +21,10 @@ while it is the current prerelease, but it is not recommended or supported as a
production-stable dependency. Syntax, generated output, runtime APIs, CLI
behavior, and diagnostics may change in a later prerelease.
-Current and future beta release gates run on Linux/amd64. WSL may be used as a
-Linux development environment, but native Windows, macOS, and other targets
-are not release blockers or maintained compatibility promises. Portability
-reports remain useful input; they do not transfer security review, triage,
-remediation, or release responsibility away from the maintainers.
+Beta 1 and Beta 2 retain their historical platform evidence. The v1 RC line
+raises the maintained release gate to native Linux/amd64 and Apple Silicon
+macOS/arm64. WSL, native Windows, Intel macOS, and other targets remain useful
+portability input but are not v1 release blockers or maintained promises.
Beta tags are signed, annotated, and immutable. Beta 1 is a source/module
release installed through the Go toolchain; it does not promise downloadable
@@ -37,9 +36,9 @@ final v1.
An RC means the intended v1 source, runtime, CLI, diagnostics, schemas, and
generated contract are frozen except for release-blocking fixes. An RC requires
-maintainer-run Linux/amd64 evidence, complete release artifacts and provenance,
-signed tags, clean direct/proxy installs, and every RC gate in this repository.
-Findings produce a new RC rather than a moved tag.
+maintainer-run Linux/amd64 and native macOS/arm64 evidence, complete release
+artifacts and provenance, signed tags, clean direct/proxy installs, and every
+RC gate in this repository. Findings produce a new RC rather than a moved tag.
### Final v1
@@ -121,9 +120,48 @@ development-supervisor failure tests, and reproducible repository-owned
benchmark and security results.
Release candidates require a clean canonical checkout, reviewed changelog,
-compatible vanity-import metadata, reproducible Linux/amd64 binaries, signed
-annotated tags, checksums, SBOMs, vulnerability results, and verification on
-Linux/amd64.
+compatible vanity-import metadata, reproducible Linux/amd64 and Darwin/arm64
+binaries, signed annotated tags, checksums, SBOMs, vulnerability results, and
+verification on both maintained native targets. Darwin artifacts additionally
+require manual Developer ID signing, notarization, stapling, and Gatekeeper
+validation outside runner authority.
+
+Human-reviewed RC evidence stays outside the source tree. After completing the
+fixed document set reported by `himesan-release verify-evidence`, the release
+operator seals its exact bytes and source identity once:
+
+```sh
+go run ./cmd/himesan-release evidence-manifest \
+ --directory "$HIMESAN_RELEASE_EVIDENCE_DIR" \
+ --repository gamertan/sandwich-hime \
+ --version v1.0.0-rc.1 \
+ --commit "$(git rev-parse HEAD)" \
+ --tree "$(git rev-parse 'HEAD^{tree}')" \
+ --reviewed-by "REVIEWER" \
+ --reviewed-at "YYYY-MM-DDTHH:MM:SSZ"
+```
+
+The manifest is created without overwrite. Changing any document or candidate
+identity requires a fresh review directory and manifest; deleting a manifest
+is not an approval shortcut. `release-check.sh --public` verifies the sealed
+digests and identities but never substitutes for the human review itself.
+
+Native receipts use an equally strict, extraction-scratch-free layout. Download
+runner ZIPs outside this directory, then copy only each checksummed receipt pair
+into the exact four lanes:
+
+```text
+$HIMESAN_NATIVE_EVIDENCE_DIR/
+├── darwin-arm64-go1.26.7/TEND-CI-VERIFICATION.json{,.sha256}
+├── darwin-arm64-go1.27.0/TEND-CI-VERIFICATION.json{,.sha256}
+├── linux-amd64-go1.26.7/TEND-CI-VERIFICATION.json{,.sha256}
+└── linux-amd64-go1.27.0/TEND-CI-VERIFICATION.json{,.sha256}
+```
+
+ZIPs, additional files, renamed lanes, development-repository identities, and
+receipts for a public commit other than current canonical `main` are rejected.
+The strict layout prevents extraction debris or a nearby historical run from
+being mistaken for the reviewed native receipt set.
## Public source and artifacts
diff --git a/SECURITY.md b/SECURITY.md
index 1a12db4..2e68bbc 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -10,7 +10,8 @@ fixes while this pair is current. This is not production support,
an SLA, a fitness guarantee, or a promise that a fix will preserve beta APIs.
The community is invited to help find portability gaps outside the maintained
-Linux target, but those reports do not create a support or release commitment.
+Linux/amd64 and Darwin/arm64 targets, but those reports do not create a support
+or release commitment.
That invitation does not outsource security assurance. Maintainers retain
responsibility for vulnerability review, triage, remediation decisions,
advisories, and release decisions.
@@ -76,6 +77,28 @@ may be delivered through a new immutable version, a retraction, an advisory, or
documentation that narrows an incorrect guarantee. Published tags will not be
moved or silently replaced.
+## Severity, advisories, retractions, and CVEs
+
+Triage considers attacker prerequisites, affected trust boundary, data or code
+impact, availability impact, default reachability, and whether exploitation
+crosses the documented application-owned capability boundary. The project uses
+plain-language critical, high, moderate, and low labels; it does not publish a
+CVSS score unless the vector and calculation have been reviewed.
+
+An accepted vulnerability affecting a published version receives a canonical
+advisory or release security note identifying affected and fixed versions,
+mitigations, credit, and disclosure chronology as safely available. The
+maintainer requests a CVE when a disclosed vulnerability materially affects a
+published release and a stable public advisory exists. A CVE is an identifier,
+not evidence of severity or independent validation.
+
+Published source and tags are never deleted or moved to hide a faulty release.
+When Go tooling supports the boundary, a later immutable module version may
+use a `retract` directive with a concise rationale. Otherwise the project
+publishes a superseding version and marks the affected release in canonical
+release notes. Retraction does not erase source, evidence, or the disclosure
+record.
+
## Scope and trust boundary
The most useful reports concern:
diff --git a/SPEC.md b/SPEC.md
index e807ff3..f3cc481 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -21,7 +21,23 @@ func Card(card model.Card)
The v1 target is `go`. Other target names are rejected. The explicit target is an architectural seam for a possible future San backend; it is not a promise that such a backend exists.
-The header permits one package clause, ordinary Go imports, and one bodyless, receiverless function declaration. The component name is the function name and its parameters form the generated typed API. Multiple components, methods, global declarations, and executable initialization in the header are errors.
+The header permits one package clause, ordinary Go imports, and one bodyless,
+receiverless function declaration. The component name is the function name and
+its parameters form the generated typed API. A Go type-parameter list is part
+of the v1 grammar and is preserved after `go/format` normalization:
+
+```sando
+
+
for _, value := range values { ?>- = value ?>
} ?>
+```
+
+Constraints, inference, and instantiation use ordinary Go rules; Sandwich Hime
+does not add a second generic type system. Multiple components, methods, global
+declarations, and executable initialization in the header are errors.
## Template tags
@@ -42,6 +58,12 @@ For `func Card(card model.Card)`, generation emits:
func Card(card model.Card) sando.Component
```
+For the generic example above, generation emits:
+
+```go
+func List[T ~string](values []T) sando.Component
+```
+
The component captures its typed parameters and renders later with a context and writer. All static writes, escaping operations, nested component renders, and application-provided writers propagate errors.
Generated files are adjacent to their source (`card.sando.go`), formatted with `go/format`, and contain the compiler version, runtime ABI, source digest, and source mappings. Hime-san does not inject the compiler's AGPL license identifier or copyright claim. An application rightsholder remains free to select AGPL intentionally through the application's own license policy.
@@ -62,6 +84,35 @@ V1 recognizes:
Ordinary URL values are attribute-escaped and rejected at render time when their normalized scheme is dangerous. Only `sando.TrustedURL`, made by an explicit `sando.TrustURL` call in trusted Go code, may bypass that scheme policy. The analogous trusted HTML, JavaScript, and CSS types are opaque and have conspicuous constructors.
+### V1 output matrix
+
+| Template position | Ordinary value | Explicit trusted value | Unsupported or rejected |
+| --- | --- | --- | --- |
+| HTML text | HTML-escaped by `WriteText` | `TrustedHTML` is written verbatim | Dynamic markup structure remains the caller's capability boundary |
+| `title`/`textarea` RCDATA | HTML-escaped by `WriteRCDATA` | Trusted wrappers are still escaped | Closing the element through a value |
+| Quoted ordinary attribute | HTML-escaped by `WriteAttr` | Trusted wrappers stringify, then escape | Unquoted values, dynamic names, and event-handler attributes |
+| Quoted URL attribute | Scheme-checked, normalized, then attribute-escaped by `WriteURL` | `TrustedURL` bypasses only the scheme check | Ambiguous schemes, controls, and non-allowlisted schemes |
+| `script` data | Not accepted | `TrustedJS` is written verbatim | Plain strings and ambiguous escaped-script parser states |
+| `style` data | Not accepted | `TrustedCSS` is written verbatim | Plain strings and dynamic style attributes |
+| Ordinary HTML content | `~` renders a `Component` | Handwritten components are explicit trusted-output capabilities | Component rendering in attributes, tags, comments, RCDATA, script, or style |
+
+Ordinary values use `fmt.Sprint` semantics before contextual normalization.
+Invalid UTF-8 and NUL bytes become U+FFFD in text, RCDATA, attribute, and URL
+helpers. A nil render context, writer, component, typed-nil component, or
+typed-nil writer produces the corresponding stable sentinel error rather than
+a panic. All writer errors and short writes propagate.
+
+Relative URLs and the `http`, `https`, `mailto`, and `tel` schemes are accepted.
+Leading and trailing Unicode whitespace is removed before classification;
+ASCII controls, ambiguous scheme syntax, and every other ordinary scheme are
+rejected before bytes are written. `TrustedURL` does not bypass quoted-attribute
+escaping.
+
+Handwritten Go statements, handwritten components, and every `Trust*` call are
+application-owned capabilities. Sandwich Hime does not sanitize or sandbox
+trusted source, prevent panics or blocking inside application code, provide
+HTTP routing, or infer that a string became safe elsewhere in the program.
+
## Compatibility
V1 is a clean break from the 2025 prototype. `.go.hime`, injected `himesan` helper directories, `SandoName(io.Writer)` functions, Go plugins, and nested demonstration modules are not accepted or generated. `.san` is not and will never be a Sandwich Hime extension.
diff --git a/cmd/himesan-release/main.go b/cmd/himesan-release/main.go
new file mode 100644
index 0000000..8937ece
--- /dev/null
+++ b/cmd/himesan-release/main.go
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Command himesan-release creates deterministic unsigned artifacts and native
+// verification receipts. Signing and notarization intentionally remain outside
+// this command and outside unattended runner authority.
+package main
+
+import (
+ "encoding/json"
+ "errors"
+ "flag"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "gamertan.com/sandwich-hime/internal/releaseartifact"
+)
+
+func main() {
+ if err := run(os.Args[1:]); err != nil {
+ fmt.Fprintf(os.Stderr, "himesan-release: %v\n", err)
+ os.Exit(1)
+ }
+}
+
+func run(arguments []string) error {
+ if len(arguments) == 0 {
+ return errors.New("usage: himesan-release [options]")
+ }
+ switch arguments[0] {
+ case "package":
+ return runPackage(arguments[1:])
+ case "receipt":
+ return runReceipt(arguments[1:])
+ case "evidence-manifest":
+ return runEvidenceManifest(arguments[1:])
+ case "verify-evidence":
+ return runVerifyEvidence(arguments[1:])
+ case "verify-native":
+ return runVerifyNative(arguments[1:])
+ case "extract-macos":
+ return runExtractMacOS(arguments[1:])
+ case "finalize-macos":
+ return runFinalizeMacOS(arguments[1:])
+ default:
+ return fmt.Errorf("unknown command %q", arguments[0])
+ }
+}
+
+func runVerifyNative(arguments []string) error {
+ flags := flag.NewFlagSet("verify-native", flag.ContinueOnError)
+ var directory string
+ var expected releaseartifact.NativeReceiptExpectation
+ flags.StringVar(&directory, "directory", "", "four-lane native receipt directory")
+ flags.StringVar(&expected.Repository, "repository", "", "repository identity")
+ flags.StringVar(&expected.Commit, "commit", "", "source commit")
+ flags.StringVar(&expected.Tree, "tree", "", "source tree")
+ if err := flags.Parse(arguments); err != nil {
+ return err
+ }
+ summary, err := releaseartifact.VerifyNativeReceiptSet(directory, expected)
+ if err != nil {
+ return err
+ }
+ return json.NewEncoder(os.Stdout).Encode(summary)
+}
+
+func runExtractMacOS(arguments []string) error {
+ flags := flag.NewFlagSet("extract-macos", flag.ContinueOnError)
+ var archive, checksum, output string
+ flags.StringVar(&archive, "archive", "", "unsigned Darwin/arm64 archive")
+ flags.StringVar(&checksum, "sha256", "", "approved archive SHA-256")
+ flags.StringVar(&output, "output", "", "empty extraction parent directory")
+ if err := flags.Parse(arguments); err != nil {
+ return err
+ }
+ root, err := releaseartifact.ExtractVerifiedMacOSPackage(archive, checksum, output)
+ if err != nil {
+ return err
+ }
+ return json.NewEncoder(os.Stdout).Encode(map[string]string{"root": root, "unsigned_archive_sha256": checksum})
+}
+
+func runFinalizeMacOS(arguments []string) error {
+ flags := flag.NewFlagSet("finalize-macos", flag.ContinueOnError)
+ var options releaseartifact.MacOSSigningOptions
+ flags.StringVar(&options.Directory, "directory", "", "extracted signed distribution directory")
+ flags.StringVar(&options.UnsignedArchiveSHA256, "unsigned-archive-sha256", "", "approved unsigned archive SHA-256")
+ flags.StringVar(&options.Identity, "identity", "", "Developer ID identity")
+ flags.StringVar(&options.Identifier, "identifier", "", "signed binary identifier")
+ flags.StringVar(&options.FinalizedAt, "finalized-at", "", "RFC3339 finalization time")
+ if err := flags.Parse(arguments); err != nil {
+ return err
+ }
+ if err := releaseartifact.FinalizeSignedMacOSDistribution(options); err != nil {
+ return err
+ }
+ return json.NewEncoder(os.Stdout).Encode(map[string]any{"valid": true, "directory": options.Directory})
+}
+
+func runEvidenceManifest(arguments []string) error {
+ flags := flag.NewFlagSet("evidence-manifest", flag.ContinueOnError)
+ var directory string
+ var identity releaseartifact.EvidenceIdentity
+ flags.StringVar(&directory, "directory", "", "reviewed evidence directory")
+ bindEvidenceIdentityFlags(flags, &identity, true)
+ if err := flags.Parse(arguments); err != nil {
+ return err
+ }
+ checksum, err := releaseartifact.WriteEvidenceManifest(directory, identity)
+ if err != nil {
+ return err
+ }
+ return json.NewEncoder(os.Stdout).Encode(map[string]string{"manifest": filepath.Join(directory, "RELEASE-EVIDENCE.json"), "sha256": checksum})
+}
+
+func runVerifyEvidence(arguments []string) error {
+ flags := flag.NewFlagSet("verify-evidence", flag.ContinueOnError)
+ var directory string
+ var identity releaseartifact.EvidenceIdentity
+ flags.StringVar(&directory, "directory", "", "sealed evidence directory")
+ bindEvidenceIdentityFlags(flags, &identity, false)
+ if err := flags.Parse(arguments); err != nil {
+ return err
+ }
+ if err := releaseartifact.VerifyEvidenceManifest(directory, identity); err != nil {
+ return err
+ }
+ return json.NewEncoder(os.Stdout).Encode(map[string]any{"valid": true, "files": releaseartifact.RequiredEvidenceFiles()})
+}
+
+func bindEvidenceIdentityFlags(flags *flag.FlagSet, identity *releaseartifact.EvidenceIdentity, review bool) {
+ flags.StringVar(&identity.Repository, "repository", "", "canonical repository identity")
+ flags.StringVar(&identity.Version, "version", "", "candidate semantic version")
+ flags.StringVar(&identity.Commit, "commit", "", "source commit")
+ flags.StringVar(&identity.Tree, "tree", "", "source tree")
+ if review {
+ flags.StringVar(&identity.ReviewedBy, "reviewed-by", "", "human reviewer identity")
+ flags.StringVar(&identity.ReviewedAt, "reviewed-at", "", "RFC3339 review time")
+ }
+}
+
+func runPackage(arguments []string) error {
+ flags := flag.NewFlagSet("package", flag.ContinueOnError)
+ var options releaseartifact.PackageOptions
+ flags.StringVar(&options.Version, "version", "", "candidate semantic version")
+ flags.StringVar(&options.Commit, "commit", "", "source commit")
+ flags.StringVar(&options.Tree, "tree", "", "source tree")
+ flags.StringVar(&options.GoVersion, "go-version", "", "Go toolchain identity")
+ flags.StringVar(&options.GOOS, "goos", "", "target operating system")
+ flags.StringVar(&options.GOARCH, "goarch", "", "target architecture")
+ flags.StringVar(&options.BinaryPath, "binary", "", "unsigned native binary")
+ flags.StringVar(&options.LicensePath, "license", "LICENSE", "license text")
+ flags.StringVar(&options.ReleaseNotes, "release-notes", "RELEASE.md", "release notes")
+ flags.StringVar(&options.OutputDirectory, "output", "", "output directory")
+ flags.Int64Var(&options.SourceDateEpoch, "source-date-epoch", 0, "fixed Unix timestamp")
+ if err := flags.Parse(arguments); err != nil {
+ return err
+ }
+ result, err := releaseartifact.Package(options)
+ if err != nil {
+ return err
+ }
+ return json.NewEncoder(os.Stdout).Encode(result)
+}
+
+func runReceipt(arguments []string) error {
+ flags := flag.NewFlagSet("receipt", flag.ContinueOnError)
+ var receipt releaseartifact.Receipt
+ var output, gates, generatedFiles string
+ flags.StringVar(&output, "output", "", "receipt output path")
+ flags.StringVar(&receipt.Repository, "repository", "", "repository identity")
+ flags.StringVar(&receipt.Commit, "commit", "", "source commit")
+ flags.StringVar(&receipt.Tree, "tree", "", "source tree")
+ flags.StringVar(&receipt.GOOS, "goos", "", "native operating system")
+ flags.StringVar(&receipt.GOARCH, "goarch", "", "native architecture")
+ flags.StringVar(&receipt.GoVersion, "go-version", "", "Go toolchain identity")
+ flags.StringVar(&receipt.RunnerVersion, "runner-version", "", "Gitea Runner version")
+ flags.StringVar(&receipt.RunnerName, "runner-name", "", "runner identity")
+ flags.StringVar(&receipt.UnsignedArtifactSHA, "artifact-sha256", "", "optional unsigned artifact digest")
+ flags.StringVar(&receipt.CompletedAt, "completed-at", "", "RFC3339 completion time")
+ flags.StringVar(&gates, "gates", "", "comma-separated successful gates")
+ flags.StringVar(&generatedFiles, "generated-files", "", "comma-separated generated output paths")
+ if err := flags.Parse(arguments); err != nil {
+ return err
+ }
+ if output == "" {
+ return errors.New("output is required")
+ }
+ receipt.SuccessfulGates = splitList(gates)
+ digest, err := releaseartifact.DigestFiles(splitList(generatedFiles))
+ if err != nil {
+ return err
+ }
+ receipt.GeneratedDigest = digest
+ checksum, err := releaseartifact.WriteReceipt(output, receipt)
+ if err != nil {
+ return err
+ }
+ return json.NewEncoder(os.Stdout).Encode(map[string]string{"receipt": output, "sha256": checksum})
+}
+
+func splitList(value string) []string {
+ var values []string
+ for _, item := range strings.Split(value, ",") {
+ item = strings.TrimSpace(item)
+ if item != "" {
+ values = append(values, item)
+ }
+ }
+ return values
+}
diff --git a/cmd/himesan/contract_test.go b/cmd/himesan/contract_test.go
new file mode 100644
index 0000000..18059ce
--- /dev/null
+++ b/cmd/himesan/contract_test.go
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+package main
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "reflect"
+ "sort"
+ "testing"
+
+ "gamertan.com/sandwich-hime/internal/testpath"
+)
+
+type contractSchema struct {
+ AdditionalProperties bool `json:"additionalProperties"`
+ Required []string `json:"required"`
+ Properties map[string]json.RawMessage `json:"properties"`
+}
+
+func TestV1CLIHelpContract(t *testing.T) {
+ t.Parallel()
+ want, err := os.ReadFile(filepath.Join("..", "..", "contracts", "himesan-cli-help-v1.txt"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ want = bytes.TrimPrefix(want, []byte("# SPDX-License-Identifier: AGPL-3.0-only\n\n"))
+ var output bytes.Buffer
+ printHelp(&output)
+ if !bytes.Equal(output.Bytes(), want) {
+ t.Fatalf("CLI help contract drifted\n--- want ---\n%s--- got ---\n%s", want, output.Bytes())
+ }
+}
+
+func TestV1VersionJSONSchemaMatchesOutput(t *testing.T) {
+ t.Parallel()
+ schema := readContractSchema(t, "himesan-version-output-v1.schema.json")
+ var stdout, stderr bytes.Buffer
+ if code := run(context.Background(), []string{"version", "--json"}, &stdout, &stderr); code != 0 {
+ t.Fatalf("version exit code = %d: %s", code, stderr.String())
+ }
+ var output map[string]any
+ if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
+ t.Fatal(err)
+ }
+ assertObjectShape(t, output, schema, "version output")
+}
+
+func TestV1OperationJSONSchemaMatchesSuccessAndDiagnosticOutput(t *testing.T) {
+ t.Parallel()
+ schema := readContractSchema(t, "himesan-operation-output-v1.schema.json")
+ directory := testpath.TempDir(t)
+ source := filepath.Join(directory, "page.sando")
+ if err := os.WriteFile(source, []byte("\npage
\n"), 0o600); err != nil {
+ t.Fatal(err)
+ }
+ var stdout, stderr bytes.Buffer
+ if code := run(context.Background(), []string{"check", "--json", source}, &stdout, &stderr); code != 1 {
+ t.Fatalf("missing-output check exit code = %d, want 1: %s", code, stderr.String())
+ }
+ var output map[string]any
+ if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
+ t.Fatal(err)
+ }
+ assertObjectShape(t, output, schema, "operation output")
+
+ resultSchema := nestedSchema(t, schema.Properties["result"])
+ result, ok := output["result"].(map[string]any)
+ if !ok {
+ t.Fatalf("result = %T, want object", output["result"])
+ }
+ assertObjectShape(t, result, resultSchema, "operation result")
+ files, ok := result["files"].([]any)
+ if !ok || len(files) != 1 {
+ t.Fatalf("files = %#v, want one item", result["files"])
+ }
+ filesProperty := rawObject(t, resultSchema.Properties["files"])
+ fileSchema := nestedSchema(t, filesProperty["items"])
+ assertObjectShape(t, files[0].(map[string]any), fileSchema, "file result")
+
+ diagnostics, ok := result["diagnostics"].([]any)
+ if !ok || len(diagnostics) == 0 {
+ t.Fatalf("diagnostics = %#v, want at least one item", result["diagnostics"])
+ }
+ diagnosticsProperty := rawObject(t, resultSchema.Properties["diagnostics"])
+ diagnosticSchema := nestedSchema(t, diagnosticsProperty["items"])
+ assertObjectShape(t, diagnostics[0].(map[string]any), diagnosticSchema, "diagnostic")
+}
+
+func readContractSchema(t *testing.T, name string) contractSchema {
+ t.Helper()
+ contents, err := os.ReadFile(filepath.Join("..", "..", "contracts", name))
+ if err != nil {
+ t.Fatal(err)
+ }
+ var schema contractSchema
+ if err := json.Unmarshal(contents, &schema); err != nil {
+ t.Fatalf("decode %s: %v", name, err)
+ }
+ if schema.AdditionalProperties || len(schema.Properties) == 0 {
+ t.Fatalf("%s is not a closed object schema", name)
+ }
+ return schema
+}
+
+func nestedSchema(t *testing.T, raw json.RawMessage) contractSchema {
+ t.Helper()
+ var schema contractSchema
+ if err := json.Unmarshal(raw, &schema); err != nil {
+ t.Fatal(err)
+ }
+ return schema
+}
+
+func rawObject(t *testing.T, raw json.RawMessage) map[string]json.RawMessage {
+ t.Helper()
+ var object map[string]json.RawMessage
+ if err := json.Unmarshal(raw, &object); err != nil {
+ t.Fatal(err)
+ }
+ return object
+}
+
+func assertObjectShape(t *testing.T, actual map[string]any, schema contractSchema, label string) {
+ t.Helper()
+ actualKeys := make([]string, 0, len(actual))
+ for key := range actual {
+ actualKeys = append(actualKeys, key)
+ if _, declared := schema.Properties[key]; !declared {
+ t.Fatalf("%s emitted undeclared property %q", label, key)
+ }
+ }
+ sort.Strings(actualKeys)
+ for _, required := range schema.Required {
+ if _, present := actual[required]; !present {
+ t.Fatalf("%s omitted required property %q (got %v)", label, required, actualKeys)
+ }
+ }
+ if len(actual) == 0 || reflect.ValueOf(actual).IsNil() {
+ t.Fatalf("%s is empty", label)
+ }
+}
diff --git a/cmd/himesan/main_test.go b/cmd/himesan/main_test.go
index a288707..48394ef 100644
--- a/cmd/himesan/main_test.go
+++ b/cmd/himesan/main_test.go
@@ -12,6 +12,7 @@ import (
"testing"
"gamertan.com/sandwich-hime/internal/compiler"
+ "gamertan.com/sandwich-hime/internal/testpath"
)
func TestRunHelpVersionAndUnknownCommand(t *testing.T) {
@@ -67,7 +68,7 @@ func TestRunHelpVersionAndUnknownCommand(t *testing.T) {
func TestGenerateCheckBlessAndJSONDiagnostics(t *testing.T) {
t.Parallel()
- directory := t.TempDir()
+ directory := testpath.TempDir(t)
sourcePath := filepath.Join(directory, "hello.sando")
source := "\n= name ?>
\n"
if err := os.WriteFile(sourcePath, []byte(source), 0o600); err != nil {
diff --git a/contracts/diagnostic-codes-v1.txt b/contracts/diagnostic-codes-v1.txt
new file mode 100644
index 0000000..5f346a5
--- /dev/null
+++ b/contracts/diagnostic-codes-v1.txt
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: AGPL-3.0-only
+
+HIM1001
+HIM1002
+HIM1101
+HIM1102
+HIM1103
+HIM1104
+HIM1105
+HIM1110
+HIM1111
+HIM1112
+HIM1113
+HIM1114
+HIM1115
+HIM1116
+HIM1117
+HIM1118
+HIM1119
+HIM1120
+HIM1121
+HIM1122
+HIM1123
+HIM1201
+HIM1202
+HIM1203
+HIM1210
+HIM1301
+HIM1302
+HIM1303
+HIM1310
+HIM1311
+HIM1320
+HIM1321
+HIM1322
+HIM1323
+HIM1324
+HIM1325
+HIM1326
+HIM1327
+HIM1328
+HIM1329
+HIM1330
+HIM1331
+HIM1332
+HIM1333
+HIM1340
+HIM1341
+HIM1342
+HIM1343
+HIM1344
+HIM1345
+HIM1346
+HIM1347
+HIM1350
+HIM1351
+HIM1352
+HIM1353
+HIM1354
+HIM1355
+HIM1356
+HIM1357
+HIM1401
+HIM1410
+HIM1500
+HIM1501
+HIM1901
+HIM1902
+HIM1903
+HIM2001
+HIM2002
+HIM2003
+HIM2004
+HIM2005
+HIM2006
+HIM2007
+HIM2008
+HIM2009
+HIM2010
+HIM2011
+HIM2012
+HIM2013
+HIM2014
+HIM2101
+HIM2102
+HIM2103
+HIM2104
+HIM2110
+HIM2201
+HIM2202
+HIM2203
+HIM2204
+HIM2205
+HIM2901
diff --git a/contracts/generated-provenance-v1.txt b/contracts/generated-provenance-v1.txt
new file mode 100644
index 0000000..e8f6a90
--- /dev/null
+++ b/contracts/generated-provenance-v1.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by himesan; DO NOT EDIT.
+// himesan:compiler
+// himesan:runtime-abi
+// himesan:source-sha256
+var _ = .ABISandoV1
+//line ::
diff --git a/contracts/himesan-cli-help-v1.txt b/contracts/himesan-cli-help-v1.txt
new file mode 100644
index 0000000..29830f4
--- /dev/null
+++ b/contracts/himesan-cli-help-v1.txt
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: AGPL-3.0-only
+
+Sandwich Hime / Hime-san — HTML-first typed components for Go
+
+Usage:
+ himesan generate [--json] [paths...] generate adjacent .sando.go files
+ himesan gen [--json] [paths...] alias for generate
+ himesan check [--json] [paths...] validate sources and committed output without writes
+ himesan bless [--json] [paths...] friendly read-only alias for check
+ himesan dev [flags] [package] [-- app-args...] run the loopback last-good supervisor
+ himesan lsp --stdio run the read-only language server
+ himesan version [--json] print compiler and runtime ABI versions
+
+Templates use .sando; .san remains exclusively San language source.
diff --git a/contracts/himesan-config-v1.schema.json b/contracts/himesan-config-v1.schema.json
new file mode 100644
index 0000000..13a4450
--- /dev/null
+++ b/contracts/himesan-config-v1.schema.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://sandwichhime.com/schema/himesan-config-v1.schema.json",
+ "title": "Hime-san local development configuration v1",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["version"],
+ "properties": {
+ "version": {"const": 1},
+ "sourceRoots": {
+ "type": "array",
+ "items": {"type": "string", "minLength": 1, "pattern": "^[^\\u0000]+$"},
+ "minItems": 1,
+ "default": ["."]
+ },
+ "goPackage": {"type": "string", "minLength": 1, "pattern": "^[^\\u0000\\r\\n]+$", "default": "."},
+ "appArgs": {"type": "array", "items": {"type": "string", "pattern": "^[^\\u0000]*$"}, "default": []},
+ "listenAddressEnv": {
+ "type": "string",
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
+ "default": "HIMESAN_LISTEN_ADDR"
+ },
+ "healthPath": {"type": "string", "pattern": "^/(?!/)[^?#\\u0000\\r\\n]*$", "default": "/"},
+ "proxyAddress": {
+ "type": "string",
+ "anyOf": [
+ {"pattern": "^127(?:\\.[0-9]{1,3}){3}:[0-9]{1,5}$"},
+ {"pattern": "^\\[::1\\]:[0-9]{1,5}$"}
+ ],
+ "default": "127.0.0.1:7331"
+ },
+ "additionalWatchRoots": {
+ "type": "array",
+ "items": {"type": "string", "minLength": 1, "pattern": "^[^\\u0000]+$"},
+ "default": []
+ }
+ }
+}
diff --git a/contracts/himesan-operation-output-v1.schema.json b/contracts/himesan-operation-output-v1.schema.json
new file mode 100644
index 0000000..6d262fd
--- /dev/null
+++ b/contracts/himesan-operation-output-v1.schema.json
@@ -0,0 +1,55 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://sandwichhime.com/schema/himesan-operation-output-v1.schema.json",
+ "title": "Hime-san generate/check JSON result v1",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["command", "ok", "result"],
+ "properties": {
+ "command": {"enum": ["generate", "check", "bless"]},
+ "ok": {"type": "boolean"},
+ "result": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["files", "discovered", "changed", "unchanged", "stale", "missing"],
+ "properties": {
+ "files": {
+ "type": ["array", "null"],
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["source_path", "output_path", "changed", "stale", "missing"],
+ "properties": {
+ "source_path": {"type": "string"},
+ "output_path": {"type": "string"},
+ "changed": {"type": "boolean"},
+ "stale": {"type": "boolean"},
+ "missing": {"type": "boolean"}
+ }
+ }
+ },
+ "diagnostics": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["path", "line", "column", "code", "severity", "message"],
+ "properties": {
+ "path": {"type": "string"},
+ "line": {"type": "integer", "minimum": 1},
+ "column": {"type": "integer", "minimum": 1},
+ "code": {"type": "string", "pattern": "^HIM[0-9]{4}$"},
+ "severity": {"enum": ["error", "warning"]},
+ "message": {"type": "string"}
+ }
+ }
+ },
+ "discovered": {"type": "integer", "minimum": 0},
+ "changed": {"type": "integer", "minimum": 0},
+ "unchanged": {"type": "integer", "minimum": 0},
+ "stale": {"type": "integer", "minimum": 0},
+ "missing": {"type": "integer", "minimum": 0}
+ }
+ }
+ }
+}
diff --git a/contracts/himesan-version-output-v1.schema.json b/contracts/himesan-version-output-v1.schema.json
new file mode 100644
index 0000000..66f4ef8
--- /dev/null
+++ b/contracts/himesan-version-output-v1.schema.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://sandwichhime.com/schema/himesan-version-output-v1.schema.json",
+ "title": "Hime-san version JSON result v1",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["compiler", "runtime_abi", "go", "features"],
+ "properties": {
+ "compiler": {"type": "string", "minLength": 1},
+ "runtime_abi": {"const": "sando.v1"},
+ "go": {"type": "string", "minLength": 1},
+ "features": {
+ "type": "array",
+ "prefixItems": [{"const": "lsp-stdio"}],
+ "minItems": 1,
+ "maxItems": 1
+ }
+ }
+}
diff --git a/docs/BENCHMARKS.md b/docs/BENCHMARKS.md
index f8db2b8..1a05bc5 100644
--- a/docs/BENCHMARKS.md
+++ b/docs/BENCHMARKS.md
@@ -9,6 +9,28 @@ synthetic cases under the published method. Only reproduced improvements become
marketing claims. Microbenchmarks do not justify claims about request
throughput, database-heavy pages, or whole-application latency.
+The threshold was fixed before measuring the RC. On each maintained native
+platform and toolchain, ten benchmark samples use the exact output-equivalent
+`BenchmarkV1Corpus*` pair. Sandwich Hime passes when its median `ns/op` and
+`B/op` are each no more than 125% of `html/template`, and its median
+allocations/op are no more than two allocations above `html/template`.
+Any failed platform/toolchain pair is a material regression. Timing is reviewed
+from raw samples rather than enforced in ordinary CI, where host contention
+would turn a performance policy into a flaky correctness gate.
+
+Run:
+
+```sh
+cd sando
+go test -run '^TestBenchmarkCorpusEquivalent$' \
+ -bench '^BenchmarkV1Corpus' -benchmem -benchtime=2s -count=10
+```
+
+`benchmarkSandoComponent` intentionally mirrors generated writer calls and
+captures the same typed view used by the parsed standard template. This is a
+runtime renderer microbenchmark; it excludes parsing, compiler execution,
+HTTP, routing, logging, databases, and deployment.
+
Benchmark fixtures must be self-contained, synthetic, reviewable, and committed
to this repository. Application-specific datasets and deployment measurements
belong with their applications and are neither copied here nor treated as core
diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md
index fe69e5b..562aab2 100644
--- a/docs/COMPATIBILITY.md
+++ b/docs/COMPATIBILITY.md
@@ -30,13 +30,28 @@ compiler versions; `himesan check` defines whether they are current. The
project makes no compatibility promise for internal packages, development SSE
payloads before final v1, or hand-edited generated files.
+The v1 compatibility snapshots cover the exported `sando` API and values, CLI
+help and exit-code classes, structured operation/version output, diagnostic
+codes, `himesan.json`, and generated provenance. English diagnostic wording,
+internal packages, temporary paths, and compiler implementation details are not
+stable API.
+
+An API deprecated after final v1 remains available for the rest of the v1
+major line and may be removed in v2. A security correction may fail closed in
+a patch release when retaining old behavior would contradict a published safety
+guarantee; that exception receives an advisory and migration note rather than a
+silent compatibility claim. Until a broader maintenance policy is announced,
+only the latest stable v1 patch and the current prerelease receive fixes.
+
## Go and platform support
-The current beta targets Go 1.25 and Go 1.26 on Linux/amd64. Required release
-evidence runs in Linux CI and on Linux deployment hosts. WSL is treated as a
-Linux development environment. Native Windows, macOS, and other targets are
-not maintained release targets or release blockers; a successful build there
-is useful portability evidence, not a compatibility promise. A Go or platform
+The modules retain a `go 1.25` language directive for consumer compatibility.
+The maintained v1 build and verification targets are Linux/amd64 and Apple
+Silicon macOS/arm64 using the pinned patched Go 1.26.7 and Go 1.27.0 toolchains.
+Both native targets are release blockers. A sleeping or unavailable Mac delays
+the release gate rather than silently converting it into Linux or
+cross-compilation evidence. Native Windows, Intel macOS, Linux/arm64, and other
+targets may work but are not v1 compatibility promises. A Go or platform
support change is announced in release notes before it takes effect.
### Historical Beta 1 observations
diff --git a/docs/DEVELOPMENT_SERVER.md b/docs/DEVELOPMENT_SERVER.md
index 3e6e99b..53a35bb 100644
--- a/docs/DEVELOPMENT_SERVER.md
+++ b/docs/DEVELOPMENT_SERVER.md
@@ -8,6 +8,9 @@ The application must read its listen address from the configured environment var
## `himesan.json` schema version 1
+The machine-readable contract is
+[`contracts/himesan-config-v1.schema.json`](../contracts/himesan-config-v1.schema.json).
+
```json
{
"version": 1,
diff --git a/docs/DIAGNOSTICS.md b/docs/DIAGNOSTICS.md
index de2b8bd..46f19a2 100644
--- a/docs/DIAGNOSTICS.md
+++ b/docs/DIAGNOSTICS.md
@@ -21,3 +21,27 @@ Code families are intentionally coarse compatibility surfaces:
| `HIM29xx` | Boundary warnings |
Scripts should consume the JSON `code`, `severity`, and location fields, not parse English messages. Message wording may improve within a compatible release.
+
+The exact v1 code inventory is machine-checked against
+[`contracts/diagnostic-codes-v1.txt`](../contracts/diagnostic-codes-v1.txt).
+Adding, removing, or renumbering a code requires an explicit compatibility
+review and snapshot update.
+
+## CLI and structured-output contract
+
+Command exit codes use three classes: `0` for success (including help and
+warning-only results), `1` for a completed operation that failed validation or
+runtime service, and `2` for invalid command usage or failure to encode the
+requested CLI result. `gen` normalizes to `generate`; `bless` remains a named
+read-only alias of `check` in structured output.
+
+The v1 JSON shapes are published as closed schemas:
+
+- [`himesan-operation-output-v1.schema.json`](../contracts/himesan-operation-output-v1.schema.json)
+ for `generate`, `check`, and `bless`;
+- [`himesan-version-output-v1.schema.json`](../contracts/himesan-version-output-v1.schema.json)
+ for `version --json`.
+
+Unknown output fields are not introduced in a compatible v1 patch without an
+explicit schema/version decision. Consumers should still ignore English
+message wording.
diff --git a/docs/SECURITY_EVIDENCE.md b/docs/SECURITY_EVIDENCE.md
index ef2af33..9a561b7 100644
--- a/docs/SECURITY_EVIDENCE.md
+++ b/docs/SECURITY_EVIDENCE.md
@@ -25,8 +25,9 @@ is retained only as supplementary Linux evidence. Hostnames, network addresses,
account names, private paths, private repository identities, and private commit
mappings are intentionally absent from this public ledger. These platform
observations are historical evidence, not the current support matrix.
-Linux/amd64 is now the maintained release target; WSL is a Linux development
-environment, while native Windows and macOS are not release blockers.
+Linux/amd64 and Darwin/arm64 are now the maintained v1 release targets. This
+section retains historical Beta 1 evidence; the exact RC must supply new native
+evidence on both targets. WSL and native Windows are not v1 release blockers.
## Beta 2 compiler publication addendum
@@ -82,11 +83,11 @@ baseline commit.
| Parser robustness smoke | Two bounded Go fuzz targets | Pass; no panic found |
| Deterministic generation | repeated generate/check/hash/mtime gates | Pass |
| Writer failures | runtime error/short-write/nil-writer tests | Pass |
-| HTML text/attribute/RCDATA escaping | compiler and runtime adversarial cases | Pass for enumerated cases |
-| URL scheme handling | ordinary/trusted URL test matrix | Pass for enumerated cases |
+| HTML text/attribute/RCDATA escaping | compiler/runtime adversarial cases plus the committed `html/template` overlap corpus | Pass for the committed corpus; documented stricter invalid-UTF-8 handling remains intentional |
+| URL scheme handling | ordinary/trusted URL matrices plus safe, unsafe, and intentionally divergent `html/template` cases | Pass for the committed corpus; control rejection and the explicit `tel` allowlist are documented policy differences |
| Filesystem boundaries | symlink, nested-module, VCS, ownership, stale-output tests | Pass for tested cases; see open findings |
| Development proxy browser boundary | Host, Origin, Fetch Metadata, CSP, fragment and response tests | Pass for tested cases |
-| Platform behavior | Historical exact-candidate native Windows and executed Linux matrices | Windows/Linux passed for the tested lanes; current releases require Linux/amd64 evidence |
+| Platform behavior | Historical exact-candidate native Windows and executed Linux matrices | Windows/Linux passed for the tested lanes; the v1 RC requires fresh Linux/amd64 and Darwin/arm64 evidence |
Coverage measures statements executed by tests. It is not branch completeness
and is not evidence that the executed behavior is secure.
@@ -105,7 +106,7 @@ independent audit.
| Windows 11/amd64, NTFS | 1.25.12, 1.26.5 | Native PowerShell verifier with race; root/runtime tests, vet, trimpath build, freshness, two generation passes, process-tree cleanup, watcher boundaries, and temporary consumer compilation | Pass. Symlink-output rejection skipped because the test account lacked symlink privilege; the read-only-directory case is POSIX-only |
| Ubuntu 20.04/amd64 under WSL2, native ext4 checkout | 1.25.12, 1.26.5 | Race-enabled verifier; root/runtime tests, vet, build, two generation passes, ten focused filesystem cases, five focused development-process/watcher cases, and license check | Pass. This is Linux execution under WSL2, not bare-metal or Linux/arm64 evidence |
| Linux/amd64 server containers | 1.25.12, 1.26.5 | Earlier pre-beta root/runtime tests, vet, builds, race, licensing, and deterministic generation in sequential isolated official Go containers | Pass on the earlier baseline only. Container resources were capped at 1 CPU and 2 GiB; this is supplementary evidence, not an exact Beta 1 lane or Linux/arm64 evidence |
-| macOS | — | Cross-compilation only | No native Beta 1 evidence; not a maintained release target |
+| macOS | — | Cross-compilation only | No native Beta 1 evidence; Darwin/arm64 becomes a maintained target at the v1 RC and requires fresh evidence |
The generated golden `basic.sando.go` was 1,399 bytes and had SHA-256
`63fa75a3049a3a8a12d769d7f9b6b510dfe763baacf706775b75cef2c57a984f`
@@ -118,7 +119,7 @@ architecture, `go version`, exact command, and a minimal reproduction.
Suspected vulnerabilities use the private route in
[SECURITY.md](../SECURITY.md). Such reports help find gaps but do not create a
support promise; maintainers remain responsible for security triage and fixes
-on the supported Linux target.
+on both supported native targets.
## Security-relevant design evidence
@@ -183,8 +184,13 @@ go test ./internal/compiler -run '^$' \
./scripts/verify-public-install.sh --version v1.0.0-beta.1
```
-The fuzz targets currently assert process robustness and result bounds. They do
-not yet prove semantic HTML safety.
+Those historical Beta 1 fuzz targets asserted process robustness and result
+bounds; they did not prove semantic HTML safety. The v1 compiler target now
+also asserts deterministic diagnostics and generated Go, valid formatted Go,
+source-digest binding, bounded public diagnostic shape, and sanitized source
+map directives. A separate runtime target asserts deterministic, fail-closed
+URL handling with no partial output. These properties still do not replace the
+committed differential corpus or real-browser testing.
## Assessment findings and remediation status
@@ -207,8 +213,8 @@ and executed Linux matrices, and Windows/macOS cross-compilation on 2026-08-12.
Signed annotated runtime and compiler tags were then published from that commit
in that order. Fresh runtime-first installation passed through both direct Git
resolution and the public Go proxy after normal proxy propagation. Future
-release decisions use the current Linux-only support policy rather than
-requiring this historical multi-platform campaign.
+release decisions require fresh evidence for the maintained Linux/amd64 and
+Darwin/arm64 targets rather than reusing this historical campaign.
## Open assurance gaps
@@ -218,9 +224,10 @@ requiring this historical multi-platform campaign.
- the signed annotated Beta tags and their common peeled commit were verified;
prebuilt-artifact signing, checksums, SBOM, reproducible provenance, and key
recovery remain incomplete;
-- Linux/arm64 and non-Linux portability are outside the current maintained
- release target;
-- browser-parser differential and semantic property testing need expansion;
+- Linux/arm64, Darwin/amd64, Windows, and other targets are outside the current
+ maintained release set;
+- the exact public candidate still needs the committed real-browser generated
+ document and development-supervisor campaign on both maintained hosts;
- compiler input size, CPU, and memory have no built-in hard budget;
- filesystem checks do not defend against a hostile local actor racing path
components between inspection and use;
@@ -234,6 +241,33 @@ requiring this historical multi-platform campaign.
- static cycle detection and trust-use warnings are best-effort analyses; and
- the project has no independent security audit or bug-bounty program.
+## v1 disposition of open gaps
+
+The list above intentionally mixes incomplete release evidence with boundaries
+that are not promised by this product. The RC may not convert either category
+into vague assurance. The following disposition is explicit and remains
+subject to exact-public-candidate review:
+
+| Gap | v1 disposition |
+| --- | --- |
+| Security mailbox delivery, backup, and recovery | Release blocker. Complete the delivery/reply and recovery drill before RC publication. Encrypted reporting may remain optional if the supported confidential channel and its limit are stated accurately. |
+| Artifact signing, provenance, and key recovery | Release blocker. Complete deterministic native artifacts, Developer ID notarization, signed-tag rehearsal, and recovery evidence. |
+| Maintained native matrix | Release blocker for Linux/amd64 and Darwin/arm64 only. Other architectures and operating systems are explicitly unsupported, not silently untested promises. |
+| Real-browser parser and supervisor evidence | Release blocker. The repository-owned gate covers a generated typed document, parsed structure, hostile-value inertness, and supervisor behavior. Execute it against the exact public candidate on both maintained hosts before publication. |
+| Compiler resource budgets | Accepted v1 boundary. The compiler is a trusted local build tool; operating-system and runner limits own CPU, memory, and input quotas. No hostile-input resource guarantee is made. |
+| Hostile local filesystem races | Accepted v1 boundary. Symlinks and ownership are checked, but an actor able to mutate the workspace concurrently is outside the trust model. |
+| Watcher integrity | Accepted v1 boundary. Watching is development convenience; explicit `check`, generation, Go tests, and builds remain release/deployment authority. |
+| Human-readable child diagnostics | Accepted v1 boundary. They are bounded for resources but remain trusted local terminal output, not a sanitized telemetry format. |
+| Development CSP rewriting | Accepted v1 boundary. It enables reload on trusted loopback pages and is not production CSP validation. |
+| Deliberately detached descendants | Accepted v1 boundary. Ordinary process groups are terminated and waited for; adversarial detachment is outside the trusted-project development model. |
+| Render recursion, output, panic, allocation, CPU, and deadlines | Accepted v1 boundary. Components are ordinary trusted Go; applications own recovery, deadlines, and resource policy. |
+| Static cycles and trust warnings | Accepted v1 boundary. They are documented best-effort audit hints and never replace Go review/tests or explicit trust decisions. |
+| Independent audit and bug bounty | Accepted disclosure, not a security claim. Neither exists for RC. Public tests, threat model, reporting, and correction policy must not be described as an independent audit. |
+
+An accepted boundary is permitted only because matching compatibility, threat
+model, and release copy already avoid the stronger promise. Any conflicting
+marketing or documentation reopens the item as a release blocker.
+
## Interpreting this ledger
“Pass” means the named command or case produced its expected result in the named
diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md
index 042fd27..6b21beb 100644
--- a/docs/THREAT_MODEL.md
+++ b/docs/THREAT_MODEL.md
@@ -177,7 +177,8 @@ Sandwich Hime does not:
## Open release work
- broaden semantic and browser-parser differential testing;
-- execute the Linux/amd64 security and process-lifecycle release matrix;
+- execute the Linux/amd64 and Darwin/arm64 security and process-lifecycle
+ release matrices;
- complete signed release provenance, checksums, and SBOM evidence;
- test the confidential reporting and signing-key recovery procedures; and
- close or explicitly accept every finding listed in the evidence ledger before
diff --git a/docs/V1_RELEASE_PLAN.md b/docs/V1_RELEASE_PLAN.md
index c1af097..71aad32 100644
--- a/docs/V1_RELEASE_PLAN.md
+++ b/docs/V1_RELEASE_PLAN.md
@@ -28,7 +28,7 @@ private history or an indiscriminate Git mirror.
Beta 1 is deliberately earlier than a release candidate. It creates a real,
repeatable install for learners and evaluators without claiming that the final
-v1 compatibility, Linux release, artifact, signing, or soak gates are complete.
+v1 compatibility, native release, artifact, signing, or soak gates are complete.
### Demonstrated for Beta 1
@@ -38,8 +38,8 @@ Public commit `b7a84054d755e42285e50298e41e47f06a8325a5` (tree
WSL2, with the earlier pre-beta server-container run retained only as
supplementary Linux evidence. The same generated golden SHA-256 was observed
across the exact Beta Windows and Linux lanes. This is historical evidence,
-not the current support definition; Linux/amd64 is now the maintained release
-target.
+not the current support definition; Linux/amd64 and Darwin/arm64 are the
+maintained v1 release targets.
Other demonstrated controls include:
@@ -53,15 +53,16 @@ Other demonstrated controls include:
### Not demonstrated yet
-- final Linux/amd64 release-candidate evidence on the exact candidate;
-- stable final-v1 API, CLI, schema, diagnostic, and generated snapshots;
-- systematic browser-parser and `html/template` differential testing;
+- final Linux/amd64 and Darwin/arm64 release-candidate evidence on the exact
+ canonical candidate;
+- exact-public-candidate execution of the committed real-browser generated
+ document and development-supervisor gate on both maintained hosts;
- a long semantic fuzz campaign beyond bounded no-panic smoke;
-- committed comparative benchmarks and predefined regression thresholds;
-- complete real-browser development-supervisor evidence;
+- exact-candidate comparative benchmark results on both maintained targets;
- deterministic prebuilt archives, checksums, SBOMs, signed binaries, and
tested signing/recovery procedures; or
-- independently reproduced Linux release artifacts, checksums, and SBOMs.
+- independently reproduced Linux and macOS release artifacts, checksums, and
+ SBOMs.
## Beta 1 publication lane
@@ -82,45 +83,50 @@ dependency, and its interfaces may change.
## Milestone 1: contract freeze
-Required before security/Linux release-candidate work is declared complete:
+Required before security/native release-candidate work is declared complete:
-- [ ] Decide and specify whether generic component function signatures are v1.
-- [ ] Inventory and freeze every exported `sando` symbol, trusted type,
+- [x] Specify generic component function signatures as ordinary Go-backed v1
+ APIs and compile them in a temporary consumer module.
+- [x] Inventory and freeze every exported `sando` symbol, trusted type,
sentinel error, concrete error field, helper, and ABI marker.
-- [ ] Freeze CLI commands, exit-code meanings, diagnostic codes, JSON schemas,
+- [x] Freeze CLI commands, exit-code meanings, diagnostic codes, JSON schemas,
`himesan.json` schema, and generated provenance fields.
-- [ ] Specify nil/stringification behavior, supported HTML-context matrix,
+- [x] Specify nil/stringification behavior, supported HTML-context matrix,
component trust boundary, URL semantics, and explicit unsupported cases.
-- [ ] Add machine-checked public API, CLI, diagnostic, schema, and generated
+- [x] Add machine-checked public API, CLI, diagnostic, schema, and generated
output compatibility snapshots.
-- [ ] Define the v1 deprecation and security-support policy.
+- [x] Define the v1 deprecation and security-support policy.
-## Milestone 2: security and Linux release evidence
+## Milestone 2: security and native release evidence
-- [ ] Run the minimum supported Go line and the latest two stable Go lines on
- Linux/amd64 runners and a Linux deployment-class host.
-- [ ] Prove identical generated bytes across those Linux lanes and exercise
+- [ ] Run the pinned patched Go 1.26 and Go 1.27 lines on Linux/amd64 and
+ native Darwin/arm64 runners, plus a Linux deployment-class host.
+- [ ] Prove identical generated bytes across both native platforms and exercise
path, replacement, permission, race, process-tree, and watcher behavior.
-- [ ] Build a systematic differential corpus against Go's documented
+- [x] Build a systematic differential corpus against Go's documented
`html/template` safety baseline for overlapping supported contexts.
-- [ ] Parse representative outputs in real browsers and test structure/code
- invariants rather than only byte equality.
+- [x] Commit a real-browser gate that generates and builds a representative
+ typed document, then tests parsed structure and hostile-value inertness in
+ Chrome rather than relying only on byte equality.
- [ ] Extend semantic fuzzing across delimiters, HTML transitions, imports,
paths, source maps, URL normalization, and filesystem operations.
- [ ] Resolve or explicitly accept every open item in
`SECURITY_EVIDENCE.md`; no accepted item may contradict a public guarantee.
- [ ] Test delivery and reply through `security@sandwichhime.com`.
-- [ ] Define severity, advisory, retraction, and CVE-request handling.
+- [x] Define severity, advisory, retraction, and CVE-request handling.
## Milestone 3: measured performance and development UX
-- [ ] Commit a synthetic, repository-owned benchmark corpus comparing
+- [x] Commit a synthetic, repository-owned benchmark corpus comparing
equivalent typed views and output with `html/template`.
-- [ ] Define “no material regression” before measuring the release candidate;
- publish hardware, OS, Go version, commands, samples, allocations, and output
- equivalence with every result.
-- [ ] Test SSE reconnect, reload, diagnostic overlays, CSP changes, fragment/API
- exclusions, caching, and child cleanup in a real browser on supported hosts.
+- [x] Define “no material regression” before measuring the release candidate.
+- [ ] Measure the exact release candidate and publish hardware, OS, Go version,
+ commands, samples, allocations, and output equivalence with every result.
+- [x] Commit a real-browser development-supervisor gate covering SSE
+ diagnostics, reload, CSP behavior, fragment/API exclusions, caching, and
+ child cleanup.
+- [ ] Execute the committed browser gate on the exact public candidate on both
+ maintained hosts and preserve the browser identities and results.
- [ ] Remove any v1 development-supervisor guarantee that cannot be evidenced
reliably instead of substituting prose for a test.
@@ -134,7 +140,7 @@ Required before security/Linux release-candidate work is declared complete:
and source/build provenance from a clean sanitized canonical checkout.
- [ ] Test release-key backup and two-person recovery for Gitea, domains,
signing material, and publication instructions.
-- [ ] Make evidence gates validate content and commit identity rather than only
+- [x] Make evidence gates validate content and commit identity rather than only
the presence of non-empty files.
- [ ] Rehearse runtime-first publication and rollback without creating public
semver tags.
@@ -145,7 +151,8 @@ Required before security/Linux release-candidate work is declared complete:
2. Publish signed `sando/v1.0.0-rc.1`, then signed `v1.0.0-rc.1` from the same
reviewed public Gitea commit.
3. Verify documented installs through fresh `GOPROXY=direct` and
- `proxy.golang.org` caches on supported Go versions under Linux/amd64.
+ `proxy.golang.org` caches on supported Go versions under Linux/amd64 and
+ Darwin/arm64.
4. Run the complete evidence suite again from the exact public commit.
5. Operate the official Sandwich Hime website on the RC runtime for a 14-day
observation period with no unresolved Hime render, security, accessibility,
diff --git a/internal/compiler/contract_test.go b/internal/compiler/contract_test.go
new file mode 100644
index 0000000..e08b9bb
--- /dev/null
+++ b/internal/compiler/contract_test.go
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+package compiler
+
+import (
+ "bytes"
+ "context"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+var diagnosticCodePattern = regexp.MustCompile(`^HIM[0-9]{4}$`)
+
+func TestV1DiagnosticCodeContract(t *testing.T) {
+ t.Parallel()
+ directory := packageDirectory(t)
+ entries, err := os.ReadDir(directory)
+ if err != nil {
+ t.Fatal(err)
+ }
+ codes := make(map[string]struct{})
+ fileSet := token.NewFileSet()
+ for _, entry := range entries {
+ if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
+ continue
+ }
+ parsed, err := parser.ParseFile(fileSet, filepath.Join(directory, entry.Name()), nil, 0)
+ if err != nil {
+ t.Fatalf("parse %s: %v", entry.Name(), err)
+ }
+ ast.Inspect(parsed, func(node ast.Node) bool {
+ literal, ok := node.(*ast.BasicLit)
+ if !ok || literal.Kind != token.STRING {
+ return true
+ }
+ value, err := strconv.Unquote(literal.Value)
+ if err == nil && diagnosticCodePattern.MatchString(value) {
+ codes[value] = struct{}{}
+ }
+ return true
+ })
+ }
+ actual := make([]string, 0, len(codes))
+ for code := range codes {
+ actual = append(actual, code)
+ }
+ sort.Strings(actual)
+ assertContractFile(t, filepath.Join(directory, "..", "..", "contracts", "diagnostic-codes-v1.txt"), strings.Join(actual, "\n")+"\n")
+}
+
+func TestV1GeneratedProvenanceContract(t *testing.T) {
+ t.Parallel()
+ compiled, diagnostics := Compile("views/generic.sando", []byte(`
+ for _, value := range values { ?>- = value ?>
} ?>
`))
+ assertNoErrorDiagnostics(t, diagnostics)
+ lines := strings.Split(string(compiled.Code), "\n")
+ if len(lines) < 4 {
+ t.Fatalf("generated header has %d lines", len(lines))
+ }
+ actual := []string{lines[0]}
+ if !strings.HasPrefix(lines[1], "// himesan:compiler ") {
+ t.Fatalf("compiler provenance line = %q", lines[1])
+ }
+ actual = append(actual, "// himesan:compiler ")
+ if !strings.HasPrefix(lines[2], "// himesan:runtime-abi ") {
+ t.Fatalf("runtime provenance line = %q", lines[2])
+ }
+ actual = append(actual, "// himesan:runtime-abi ")
+ if !regexp.MustCompile(`^// himesan:source-sha256 [0-9a-f]{64}$`).MatchString(lines[3]) {
+ t.Fatalf("source provenance line = %q", lines[3])
+ }
+ actual = append(actual, "// himesan:source-sha256 ")
+ generated := string(compiled.Code)
+ if !regexp.MustCompile(`(?m)^var _ = [A-Za-z_][A-Za-z0-9_]*\.ABISandoV1$`).MatchString(generated) {
+ t.Fatal("generated output is missing the compile-time ABI marker")
+ }
+ actual = append(actual, "var _ = .ABISandoV1")
+ if !regexp.MustCompile(`(?m)^//line [^\r\n]+:[1-9][0-9]*:[1-9][0-9]*$`).MatchString(generated) {
+ t.Fatal("generated output is missing source mappings")
+ }
+ actual = append(actual, "//line ::")
+ assertContractFile(t, filepath.Join(packageDirectory(t), "..", "..", "contracts", "generated-provenance-v1.txt"), strings.Join(actual, "\n")+"\n")
+}
+
+func TestV1GenericComponentSignature(t *testing.T) {
+ t.Parallel()
+ source := []byte(`
+ for _, value := range values { ?>- = value ?>
} ?>
`)
+ compiled, diagnostics := Compile("views/list.sando", source)
+ assertNoErrorDiagnostics(t, diagnostics)
+ if !bytes.Contains(compiled.Code, []byte("func List[T ~string](values []T)")) {
+ t.Fatalf("generic signature was not preserved:\n%s", compiled.Code)
+ }
+ analyses := AnalyzeSources(context.Background(), []SourceInput{{Path: "views/list.sando", Source: source}})
+ if len(analyses) != 1 {
+ t.Fatalf("analysis count = %d, want 1", len(analyses))
+ }
+ analysis := analyses[0]
+ if analysis.TypeParams != "[T ~string]" || analysis.Params != "(values []T)" || analysis.Signature != "func List[T ~string](values []T)" {
+ t.Fatalf("generic analysis contract = %#v", analysis)
+ }
+}
+
+func packageDirectory(t *testing.T) string {
+ t.Helper()
+ _, file, _, ok := runtime.Caller(0)
+ if !ok {
+ t.Fatal("runtime.Caller failed")
+ }
+ return filepath.Dir(file)
+}
+
+func assertContractFile(t *testing.T, path, actual string) {
+ t.Helper()
+ expected, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected = bytes.TrimPrefix(expected, []byte("# SPDX-License-Identifier: AGPL-3.0-only\n\n"))
+ if string(expected) != actual {
+ t.Fatalf("contract drift in %s\n--- expected ---\n%s--- actual ---\n%s", path, expected, actual)
+ }
+}
diff --git a/internal/compiler/e2e_test.go b/internal/compiler/e2e_test.go
index 193fab0..85f2708 100644
--- a/internal/compiler/e2e_test.go
+++ b/internal/compiler/e2e_test.go
@@ -128,6 +128,9 @@ func TestRCDATACannotBeBypassedByTrustedHTML(t *testing.T) {
view := View{Name: "title", URL: "/", JS: sando.TrustJS(""), HTML: sando.TrustHTML("")}
if err := sando.Render(context.Background(), &output, Page(view)); err != nil { t.Fatal(err) }
if strings.Contains(output.String(), "