Skip to content

Commit 630124b

Browse files
test: hermetic cross-language scope-hash parity via the pinned golden fixture
The python-sdk has its own compute_scope_hash and after_payload_canonical — a fourth transcription that had not been measured against the others. A pinned scope-hash.golden.json (18 vectors, byte-identical to the TS copy, GOLDEN.sha256) lets it match the golden set with no sibling checkout and no $HOME dependency (reads only os.path.dirname(__file__)). Same vectors and a does-the-oracle-bite case as the TS side. Runs on a clean clone with an empty $HOME (explicit PYTHONPATH): 21 pass. 158 pass total.
1 parent 1c67217 commit 630124b

3 files changed

Lines changed: 277 additions & 0 deletions

File tree

‎tests/fixtures/xlang/GOLDEN.sha256‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1ba73e01c9d508d7e027369b9ebdf32e8ae8108fabba0fe4006abd4b9f561526 scope-hash.golden.json
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
{
2+
"schema": "cr.xlang.scope-hash-golden.v1",
3+
"note": "Repo-internal golden oracle for the cross-language invariants. Each side asserts its OWN computation against these values; two sides that both match the file match each other, with neither needing the other checked out.",
4+
"scope_hash": [
5+
{
6+
"name": "all three present",
7+
"args": {
8+
"operation": "publish",
9+
"target_id": "sha256:abc",
10+
"after_payload": "{\"a\":1}"
11+
},
12+
"expected": "sha256:d22964152ce92e6bc49774e7516c99a571720e49dcaf9c78b1943344ac061c98"
13+
},
14+
{
15+
"name": "empty target_id",
16+
"args": {
17+
"operation": "publish",
18+
"target_id": "",
19+
"after_payload": "body"
20+
},
21+
"expected": "sha256:10209a523e88e0667598c65fe4babf8a7df65450acb3452d4e55b5d5481ca16b"
22+
},
23+
{
24+
"name": "absent target_id hashes like empty",
25+
"args": {
26+
"operation": "publish",
27+
"after_payload": "body"
28+
},
29+
"expected": "sha256:10209a523e88e0667598c65fe4babf8a7df65450acb3452d4e55b5d5481ca16b"
30+
},
31+
{
32+
"name": "absent after_payload",
33+
"args": {
34+
"operation": "deploy",
35+
"target_id": "42"
36+
},
37+
"expected": "sha256:08954b9ef10c11166c7293ab51819b3a385d389ffb5a6788310fddb78c03b43d"
38+
},
39+
{
40+
"name": "all absent",
41+
"args": {},
42+
"expected": "sha256:b8c9e440ead3ddaccf7cc7e879d512a263272270df2d5504c0c3d1f85d16f9d9"
43+
},
44+
{
45+
"name": "separator byte inside the payload",
46+
"args": {
47+
"operation": "publish",
48+
"target_id": "",
49+
"after_payload": "a\u001fb"
50+
},
51+
"expected": "sha256:2ae12e363f0512b2768bc0f12fb8ca24e485e74cc4ff1179d5a978fcb4293667"
52+
},
53+
{
54+
"name": "newline and pipe inside the payload",
55+
"args": {
56+
"operation": "publish",
57+
"target_id": "",
58+
"after_payload": "a\n|b"
59+
},
60+
"expected": "sha256:78df185047ed9cc6cc43ef92ccc41186d49b3269456ca2aed7db0dcd64a9e6f8"
61+
},
62+
{
63+
"name": "multi-byte UTF-8",
64+
"args": {
65+
"operation": "publish",
66+
"target_id": "ő",
67+
"after_payload": "日本語 — em dash"
68+
},
69+
"expected": "sha256:059223d5ad18eff481a63da9d28c2b9214a8579d52a184311a8641311de02652"
70+
},
71+
{
72+
"name": "empty string payload",
73+
"args": {
74+
"operation": "publish",
75+
"target_id": "x",
76+
"after_payload": ""
77+
},
78+
"expected": "sha256:5f3a4ac4811b1ffac3f013b4547ba18c5ca0d8512a9ac76ddcd396d4eea9dd90"
79+
},
80+
{
81+
"name": "CRLF and trailing whitespace survive verbatim",
82+
"args": {
83+
"operation": "publish",
84+
"target_id": "",
85+
"after_payload": "a\r\n \nb"
86+
},
87+
"expected": "sha256:77ab17231a993da3595a61a4b49d90761a411176e373d4bedabc8910c548b131"
88+
},
89+
{
90+
"name": "NUL byte inside the payload",
91+
"args": {
92+
"operation": "publish",
93+
"target_id": "",
94+
"after_payload": "a\u0000b"
95+
},
96+
"expected": "sha256:b93f1acee919c0463b89979bfa5a8b5ac2f972f4dfe0f9db2ce11a949be17907"
97+
},
98+
{
99+
"name": "astral plane",
100+
"args": {
101+
"operation": "publish",
102+
"target_id": "🔐",
103+
"after_payload": "𝔘𝔫𝔦"
104+
},
105+
"expected": "sha256:6264d36fafdb8abc19314ebd52dc6a041d36c1a227bdadd8742735dc6bb8092b"
106+
}
107+
],
108+
"after_payload_canonical": [
109+
{
110+
"name": "empty artifact list",
111+
"artifacts": [],
112+
"expected": ""
113+
},
114+
{
115+
"name": "one artifact",
116+
"artifacts": [
117+
{
118+
"id": "a.yaml",
119+
"type": "openapi",
120+
"after": "X"
121+
}
122+
],
123+
"expected": "X"
124+
},
125+
{
126+
"name": "sorted by (type,id), not input order",
127+
"artifacts": [
128+
{
129+
"id": "b",
130+
"type": "openapi",
131+
"after": "B"
132+
},
133+
{
134+
"id": "a",
135+
"type": "openapi",
136+
"after": "A"
137+
}
138+
],
139+
"expected": "A\u001fB"
140+
},
141+
{
142+
"name": "type sorts before id",
143+
"artifacts": [
144+
{
145+
"id": "a",
146+
"type": "z",
147+
"after": "Z"
148+
},
149+
{
150+
"id": "b",
151+
"type": "a",
152+
"after": "A"
153+
}
154+
],
155+
"expected": "A\u001fZ"
156+
},
157+
{
158+
"name": "absent after is the empty string",
159+
"artifacts": [
160+
{
161+
"id": "a",
162+
"type": "t"
163+
}
164+
],
165+
"expected": ""
166+
},
167+
{
168+
"name": "object after is JSON.stringify, not RFC 8785",
169+
"artifacts": [
170+
{
171+
"id": "a",
172+
"type": "t",
173+
"after": {
174+
"b": 1,
175+
"a": 2
176+
}
177+
}
178+
],
179+
"expected": "{\"b\":1,\"a\":2}"
180+
}
181+
]
182+
}

‎tests/test_xlang_golden.py‎

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""HERMETIC cross-language parity (1456) — this side against a repo-internal golden oracle.
2+
3+
WHAT WAS MEASURED, AND WHY A SKIP IS NOT A PROOF
4+
------------------------------------------------
5+
The TypeScript SDK compared itself against three sibling checkouts under ``$HOME`` — including
6+
``receipt-verifier/verify_grant.py`` for the Python comparison. On a clean clone none of them
7+
exists, so the comparison skipped: honest, and then the release was gated on a check that did not
8+
run. This package was not part of that comparison at all, and it carries its OWN
9+
``compute_scope_hash`` — a fourth transcription of the same rule.
10+
11+
HOW A GOLDEN SET REMOVES THE SIBLING
12+
------------------------------------
13+
The two languages do not need to meet; they need to agree, and agreement is transitive. If this
14+
side matches the golden file and the TypeScript side matches the SAME file, the two match each
15+
other. The oracle is committed in BOTH packages, byte-identical, pinned by sha256, and each side
16+
asserts only its own computation.
17+
18+
``GOLDEN.sha256`` holds the same digest in both repos. A copy that drifts fails its own test before
19+
it can quietly diverge from the other — which is what makes two files one oracle.
20+
21+
RELEASE-BLOCKING. There is no skip path: the fixture lives in this repository.
22+
"""
23+
24+
import hashlib
25+
import json
26+
import os
27+
28+
import pytest
29+
30+
from coderifts.execution_grant import after_payload_canonical, compute_scope_hash
31+
32+
DIR = os.path.join(os.path.dirname(__file__), "fixtures", "xlang")
33+
FILE = os.path.join(DIR, "scope-hash.golden.json")
34+
PIN = os.path.join(DIR, "GOLDEN.sha256")
35+
36+
with open(FILE, "rb") as fh:
37+
RAW = fh.read()
38+
GOLDEN = json.loads(RAW.decode("utf-8"))
39+
40+
41+
def test_golden_matches_its_pin():
42+
"""The same digest the TypeScript package records — that is what makes it one oracle."""
43+
with open(PIN, "r", encoding="utf-8") as fh:
44+
want = fh.read().strip().split()[0]
45+
assert len(want) == 64
46+
assert hashlib.sha256(RAW).hexdigest() == want
47+
48+
49+
def test_the_set_is_not_thin():
50+
assert GOLDEN["schema"] == "cr.xlang.scope-hash-golden.v1"
51+
assert len(GOLDEN["scope_hash"]) >= 10
52+
assert len(GOLDEN["after_payload_canonical"]) >= 5
53+
names = " | ".join(v["name"] for v in GOLDEN["scope_hash"])
54+
for need in ("absent", "separator", "UTF-8", "NUL"):
55+
assert need in names, 'no vector covers "%s"' % need
56+
57+
58+
@pytest.mark.parametrize(
59+
"vector", GOLDEN["scope_hash"], ids=[v["name"] for v in GOLDEN["scope_hash"]]
60+
)
61+
def test_scope_hash_matches_golden(vector):
62+
"""An ABSENT key is None here and `undefined` there — both must hash like the empty string."""
63+
args = vector["args"]
64+
got = compute_scope_hash(
65+
args.get("operation"), args.get("target_id"), args.get("after_payload")
66+
)
67+
assert got == vector["expected"]
68+
69+
70+
@pytest.mark.parametrize(
71+
"vector",
72+
GOLDEN["after_payload_canonical"],
73+
ids=[v["name"] for v in GOLDEN["after_payload_canonical"]],
74+
)
75+
def test_canonical_input_matches_golden(vector):
76+
assert after_payload_canonical(vector["artifacts"]) == vector["expected"]
77+
78+
79+
def test_the_oracle_bites():
80+
"""All sides agreeing could mean all sides are wrong in the same way.
81+
82+
A golden file generated from one implementation would enshrine that implementation's mistake,
83+
so the separator is shown to be load-bearing rather than assumed.
84+
"""
85+
v = GOLDEN["scope_hash"][0]
86+
a = v["args"]
87+
wrong = "|".join(
88+
[
89+
a.get("operation") or "",
90+
a.get("target_id") or "",
91+
a.get("after_payload") or "",
92+
]
93+
)
94+
assert "sha256:" + hashlib.sha256(wrong.encode("utf-8")).hexdigest() != v["expected"]

0 commit comments

Comments
 (0)