mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-27 16:02:05 +00:00
The Tests workflow was red on every OS and Python version for one reason:
`black --check` found 28 unformatted files. Part predates this branch (the
tui_v3 screens, core/api.py, core/index0.py); part is mine from this session.
Formatting only — verified rather than assumed, because a formatter touching
363 files is exactly where an unnoticed behaviour change would hide:
- 1223 Python tests pass, unchanged
- regenerating the fixtures after reformatting the generator produces
byte-identical output, so no hashed value moved
- differential fuzz: no divergences
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
103 lines
3.0 KiB
Python
103 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
||
"""Emit canonical-JSON differential cases from the Python reference.
|
||
|
||
Entry hashes decide merge winners, so if the two implementations serialize
|
||
the same value differently they can pick different winners and never
|
||
converge. A security review found several such disagreements (number
|
||
formatting, key ordering) that the existing fixtures missed because they
|
||
only ever used ASCII keys and integer values.
|
||
|
||
Writes js/packages/test-vectors/fixtures/canonical_json.json.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from pathlib import Path
|
||
|
||
REPO = Path(__file__).resolve().parent.parent
|
||
OUT = REPO / "js" / "packages" / "test-vectors" / "fixtures" / "canonical_json.json"
|
||
|
||
# Values a vault can realistically contain, plus the edges that broke.
|
||
CASES: list = [
|
||
{},
|
||
{"a": 1},
|
||
{"b": 2, "a": 1},
|
||
{"modified_ts": 1700000000, "label": "site"},
|
||
{"tags": ["b", "a"], "notes": ""},
|
||
# Key ordering: supplementary plane vs BMP above U+E000
|
||
{"\U0001f600": 1, "!": 2},
|
||
{"z": 1, "é": 2, "中": 3, "\U0001f600": 4},
|
||
# Escaping
|
||
{'quote"': "back\\slash"},
|
||
{"ctrl": "\x00\x01\x1f"},
|
||
{"nl": "line\nbreak\ttab"},
|
||
{"unicode": "café 中文 \U0001f512"},
|
||
# Numbers
|
||
{"n": 0},
|
||
{"n": -0},
|
||
{"n": 1},
|
||
{"n": -17},
|
||
{"n": 9007199254740991},
|
||
{"n": 0.5},
|
||
{"n": -0.25},
|
||
{"n": 1e-7},
|
||
{"n": 1.5e-9},
|
||
# Values above 2**53 are deliberately absent: JSON gives no way to tell a
|
||
# Python int from a float there, so the two implementations cannot agree
|
||
# on a canonical form. canonicalJson refuses them rather than guessing;
|
||
# see the "unsupported" list below.
|
||
# Nesting and arrays
|
||
{"links": [{"target_id": 1, "relation": "totp", "note": ""}]},
|
||
[1, "two", None, True, False],
|
||
{"deep": {"a": [{"b": {"c": [1, 2, 3]}}]}},
|
||
# Entry-shaped, the actual hashed object
|
||
{
|
||
"type": "password",
|
||
"kind": "password",
|
||
"label": "example.com",
|
||
"length": 16,
|
||
"archived": False,
|
||
"notes": "",
|
||
"tags": ["web"],
|
||
"modified_ts": 1700000000,
|
||
"links": [],
|
||
"custom_fields": [],
|
||
},
|
||
]
|
||
|
||
|
||
# Values TypeScript must refuse rather than serialize ambiguously.
|
||
UNSUPPORTED = [
|
||
{"n": 10**20},
|
||
{"n": 12345678901234567890},
|
||
]
|
||
|
||
|
||
def main() -> None:
|
||
cases = []
|
||
for value in CASES:
|
||
canonical = json.dumps(value, sort_keys=True, separators=(",", ":"))
|
||
cases.append({"value": value, "canonical": canonical})
|
||
OUT.write_text(
|
||
json.dumps(
|
||
{
|
||
"description": (
|
||
"canonicalJson must match json.dumps(sort_keys=True, "
|
||
"separators=(',',':')) exactly: entry hashes derived from "
|
||
"it decide merge winners across implementations."
|
||
),
|
||
"cases": cases,
|
||
"unsupported": UNSUPPORTED,
|
||
},
|
||
indent=2,
|
||
sort_keys=True,
|
||
)
|
||
+ "\n"
|
||
)
|
||
print(f"wrote {OUT.relative_to(REPO)} ({len(cases)} cases)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|