Skip to content

feat(schema-identity): fix the D1.3 schema_digest and schema_name contracts (#63) - #64

Merged
sinkog merged 1 commit into
mainfrom
feat/schema-identity-contract
Oct 31, 2026
Merged

sinkog merged 1 commit into
mainfrom
feat/schema-identity-contract

Conversation

@sinkog

@sinkog sinkog commented Oct 31, 2026

Copy link
Copy Markdown
Member

Refs #63 (does not close it).

What

Fixes the two D1.3 contracts decided in the owner comment on #63, and proves them with golden and negative vectors read by both languages.

  • schema_digest = "sha256:" + lowercase hex of the SHA-256 of the raw bytes of the pinned schema file. No parsing, normalization or re-serialization; line endings and the final newline are part of the identity.
  • schema_name = identity.namespace + ":" + identity.kind, as authored. Both non-empty; the namespace is colon-separated non-empty components; the kind has no colon. No trimming or case folding.
  • A digest claim that is absent or not exactly the digest of the source bytes is refused. Check order: digest binding, namespace, kind.

Changes

  • engine/src/schema_identity.rs and go/schemaidentity: schema_digest, schema_name, bind_schema_identity (+ BindSchemaIdentity). The digest reuses the existing A7 hash function over the raw bytes; no new algorithm.
  • New codes E_SCHEMA_SOURCE_BINDING and E_INVALID_SCHEMA_IDENTITY (rules in RECEIPT-SCHEMA.md).
  • conformance/differential/schema_identity/: 23 vectors (5 accepted, 18 rejected). Each holds the real source bytes; .gitattributes marks them -text, and git ls-files --eol shows the index bytes unchanged (LF, no-final-LF, CRLF).
  • docs/MATERIALIZATION-SPEC.md D1.3 and a new RECEIPT-SCHEMA.md section, "Schema identity contract".
  • Not changed: the receipt verifier (receipt.rs, go/receipt). It still does not authenticate the source.

Evidence

  • The golden digests were recomputed from the files on disk with sha256sum and openssl dgst (314 bytes a36e841d…d1a338; 313 bytes 4fff76af…07f7), not copied from the issue comment. The Rust test also checks the spec.identity the vectors state against the file with the engine's reader.
  • Gate, locally: cargo fmt --check, clippy -D warnings, cargo test --workspace (debug and release), gofmt, go vet, go test -race ./...: all clean.
  • Rust and Go give the same code, rule and path on all 23 vectors. Each also flips every one of the 314 golden bytes and requires the mismatch rule.
  • The contract tests can fail: 8 planted defects, each turned both the Rust and the Go suite red (build errors excluded): CRLF-normalized hashing, final LF dropped, mismatch accepted, kind colon allowed, empty namespace component allowed, namespace trimmed, kind case-folded, missing claim treated as a match.

Not covered here

  • Moving the pin in cic-primitive-forms and adopting the contract there: a separate, separately checked step after merge.
  • Release-origin authenticity. Production receipts stay on hold until verified release metadata exists.
  • The caller supplies the identity components; this crate still has no schema reader.

…tracts, with golden vectors and a Rust/Go differential

schema_digest is the SHA-256 of the raw bytes of the pinned schema source
(no parsing, no normalization; a final newline and the line endings are part
of the identity). schema_name is identity.namespace + ':' + identity.kind,
used as authored, with non-empty colon-separated namespace components and a
colon-free kind. Absent or mismatching digest claims and invalid identity
components are refused, in a fixed check order.

- engine/src/schema_identity.rs and go/schemaidentity: the contract, in both
  languages; the receipt verifier is not changed.
- conformance/differential/schema_identity: 23 vectors (5 accepted, 18
  rejected) holding the real source bytes (-text), including the 314-byte
  golden file and its 313-byte variant.
- docs: MATERIALIZATION-SPEC D1.3 and RECEIPT-SCHEMA 'Schema identity
  contract'.

Refs #63. The pin update in cic-primitive-forms and production receipts
(release-origin authenticity) are separate steps.
@sinkog
sinkog merged commit a6a0680 into main Oct 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant