Repository navigation
feat(receipt): Rust/Go receipt verifier with a shared differential corpus - #61
Merged
Merged
Conversation
added 2 commits
October 19, 2026 07:11
…rpus Adds verify_receipt (Rust) and receipt.Verify (Go), which check a materialization receipt (docs/RECEIPT-SCHEMA.md) for structural and content consistency. There is no receipt producer here: materialization stays with the environment. Checks: the 15 required fields and their types in a closed layout, the supported receipt_schema_version (1, an integer), sha256:+64 lowercase hex digests, strictly ascending evidence lists and inputs sets, output_digest against materialized_object_digest, input_digest against the A7 digest of the processed authored input, and evidence membership and value digests against the object. Evidence is verified only through an explicit PathBinding; an entry without a binding is refused (E_RECEIPT_UNBOUND), never partially accepted. Flat object keys are not turned into $.* paths by guessing. derived_values[].inputs is checked for form and order only. A success does not show that a default or a derivation ran, and no signature or release origin is authenticated. conformance/differential/receipt/ holds 35 vectors (3 accepted, 32 rejected) whose digests were computed by an independent script; both languages run them plus a test that alters each accepted receipt and requires a rejection. --- [signing-metadata] key = cic-my-sign-key signature = vault:v1:MEUCIF72wcNI7aVFKfxqzYSnN5i5hM1dbbLX+LIHiPRvj3hQAiEAk6ybyw0GpmwPxh7sgpsVl4nCOGrgAOMy+WLL3HozXd4= hash-algorithm = sha256 digest = dL0QUwxD6EtQ3gpaxmPmfEVeEEfICm+RmBySfUqOnX8= [certificate] -----BEGIN CERTIFICATE----- MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0 lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW -----END CERTIFICATE-----
…ds non-nil empty slices RECEIPT-SCHEMA.md makes applied_defaults, derived_values and derived_values[].inputs required arrays: [] when empty, never null. The Go typed API let a nil slice through CheckStructure, and Parse produced nil for a JSON [] because it only appended. CheckStructure now refuses nil lists (R-RECEIPT-SHAPE) and Parse starts from non-nil empty slices. The test helper no longer builds its accepted receipt from nil lists. Rust needs no change: Vec cannot be null. Regression tests: nil lists and nil inputs refused on a directly built receipt, and JSON [] -> Parse -> non-nil slices -> Verify succeeds. The 35 differential vectors are unchanged. --- [signing-metadata] key = cic-my-sign-key signature = vault:v1:MEUCIQCL7FRTyZlTy1BDq7QLrhaY0yyoYxUnW1t2TKsm1AsTjgIgFpKyRt+L3Z9hy7pUaaUiw9LMKfseOd4LswIXSQNRlxI= hash-algorithm = sha256 digest = SWULenrxpF7VhVMEv619vTul30puklFy/bJe3p7NVc4= [certificate] -----BEGIN CERTIFICATE----- MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0 lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW -----END CERTIFICATE-----
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A Rust and Go verifier for the materialization receipt (
docs/RECEIPT-SCHEMA.md), cross-checked through a shared differential corpus. It verifies a receipt; it does not produce one. No new repository, no producer, no change to the receipt format, D-018, the materialization scope boundary or Relay.verify_receipt/receipt.Verifyestablish structural and content consistency only (details and non-claims indocs/RECEIPT-VERIFIER.md).Checked
materialization. TypedReceipt,DefaultEvidence,DerivedEvidencein both languages.receipt_schema_versionis the integer1(1.0is rejected).sha256:+ 64 lowercase hex. Base64 / bare hex are refused; only an equal-bytes re-spelling by the producer is a valid conversion.applied_defaults/derived_valuesstrictly ascending by.path;inputsa strictly ascending, deduplicated set; rule, path and value-digest forms.output_digest==materialized_object_digest;input_digest== A7 digest of the canonical form of the processed authored input (a value, not raw source bytes; no YAML reader assumed).value_digestchecked.Evidence boundary
MaterializedObjectkeys are not turned into$.config.*paths by guessing. Evidence is verified only through an explicitPathBindingsupplied by the caller. A missing binding isE_RECEIPT_UNBOUND(fail closed), never partial success.derived_values[].inputsis checked for form and order only. No rule requires an input path to exist in the object (a vector pins that).rule/inputsdescribe what happened, or who produced the receipt. Metadata identifiers are claims; no signature or release-origin check.engine_identitynames the implementation that materialized the data, not this verifier.Tests and evidence
conformance/differential/receipt/: 35 vectors (3 accepted, 32 rejected), including changed input digest, changed output digest, missing field, unsupported version, version not an integer, unsorted / duplicated evidence and inputs, missing path binding, partial binding, wrong provenance, changed value digests. Rejections pin code, rule and location.materialized_object_digestvector first. Neither engine generated them.-count=1for that: its test cache does not track files outside the module).Local:
cargo fmt --check,clippy -D warnings,cargo test --workspace(debug and release),gofmt,go vet,go test -race ./...all green.Not merging
Per instruction, this is not to be merged automatically.