30-second answer

JSON permits the same string to be written more than one way. Python's default writer escapes non-ASCII (ensure_ascii=True); .NET's default encoder escapes differently. Both outputs parse back to the same document, and their SHA-256 digests differ. The engine hit this over ten apostrophes: two correct implementations, one document, two digests. The fix is to make the byte form part of the contract: sorted keys, no whitespace, minimal escaping, written into the schema description itself, so every implementation serializes the same bytes before hashing.

What this proves

  • That "valid JSON" is not an identity. Validity is about the parsed value; a digest is about bytes. Any pipeline that hashes JSON without fixing the byte form has an identity that depends on which runtime wrote the file.
  • That the contract can carry the fix. The canonical form lives in the schema description, so a C# consumer and a Python producer read the same sentence, and the engine's contract bundle ships with generated models whose headers list the constraints no C# type can express.

What it does not prove

  • That two digests matching means the content is right. A digest binds bytes, not truth. Matching digests prove nobody touched the document, not that the document was worth writing.
  • That validation alone is enough. The engine's own ordering is explicit: validate, then deserialize; neither alone is the contract.

PowerShell

PowerShell
# The same value, two byte forms, two digests
$s = '{"name": "O''Neill''s list"}'
$py = python3 -c "import json,sys;print(json.dumps(json.loads(sys.argv[1])))" $s
$net = ($s | ConvertFrom-Json | ConvertTo-Json -Compress)
($py, $net | ForEach-Object {
  (Get-FileHash -InputStream ([IO.MemoryStream]::new(
    [Text.Encoding]::UTF8.GetBytes($_))) -Algorithm SHA256).Hash.Substring(0,12)
})

Example output

Text
7B0E9A6C21D4
3F41C08872AA

Explanation

The failure is silent by design: nothing errors, nothing warns, both sides keep validating, and only the digest comparison at the end says the two halves of your system disagree. That is why the engine treats escaping policy as contract, not style. Once the canonical form is fixed, a digest becomes portable: the assessment_id computed by the Python engine is the assessment_id any other correct implementation computes, and the shipped contract bundle can be verified byte-identical against the repository copy.

Production considerations

  • If you sign or hash JSON anywhere, write the canonical form down where every implementation must read it. "We use the platform default" is two defaults the day you add a second platform.
  • Sort keys at the writer, not the comparer. A comparer that normalizes is a comparer that hides what the writer did.
  • None directly. The canonical form underwrites the identity of every Assessment that carries any rule's results.

References

Found something wrong? Suggest a correction. The article source is not public; the engine it cites is.