diff --git a/crates/polymodel-ldraw-testkit/corpus/artifact-provenance.json b/crates/polymodel-ldraw-testkit/corpus/artifact-provenance.json index 245657f..791770e 100644 --- a/crates/polymodel-ldraw-testkit/corpus/artifact-provenance.json +++ b/crates/polymodel-ldraw-testkit/corpus/artifact-provenance.json @@ -1,9 +1,265 @@ { "schema_version": "ldraw-canonical-v1", "artifacts": [ - {"id":"external-thomas","path":"corpus/external-thomas.ldr","source_url":"https://github.com/ThomasThelen/ldraw-fixtures","source_revision":"PM-77-selection-2026-03-09","sha256":"authored-at-build-time","license":"CC BY 4.0; attribution retained in ledger","original_test":"thomas-line-and-triangle","modifications":"minimized to one triangle; expected values independently derived","authority":"observation"}, - {"id":"external-weldr","path":"corpus/external-weldr.ldr","source_url":"https://github.com/weldr/ldraw","source_revision":"PM-77-selection-2026-03-09","sha256":"authored-at-build-time","license":"MIT; attribution retained in ledger","original_test":"weldr-quad","modifications":"minimized to one quad; expected values independently derived","authority":"observation"}, - {"id":"external-ldr-tools","path":"corpus/external-ldr-tools.ldr","source_url":"https://github.com/ldraw-tools/ldr-tools","source_revision":"PM-77-selection-2026-03-09","sha256":"authored-at-build-time","license":"GPL-2.0-or-later; attribution retained in ledger","original_test":"ldr-tools-line","modifications":"minimized to one line; expected values independently derived","authority":"observation"} + { + "id": "syntax-types", + "path": "corpus/syntax-types.ldr", + "source_url": "https://www.ldraw.org/article/218.html", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "e9aeff06a6eecd2301f4dc32fd755862ed9bc47bc9c6046ffc52f6257a278a27", + "license": "LDraw.org specification; minimized authored vector", + "original_test": "LDraw.org file format specification \u00a72 line types 0\u20135", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "ratified_spec" + }, + { + "id": "profile-matrix", + "path": "corpus/profile-matrix.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/131433", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "9289d666fe7a68a8db8325f2049df6d10a98a983e77467944fb1e233d3750900", + "license": "Polymodel-owned safety vector", + "original_test": "PM-77 compatibility profile matrix", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "polymodel_safety" + }, + { + "id": "mpd-data", + "path": "corpus/mpd-data.ldr", + "source_url": "https://www.ldraw.org/article/218.html", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "fd2d037811f1df936934e04f29029e11fba5f6e78dfa6442702854d79facfe0f", + "license": "LDraw.org specification; minimized authored vector", + "original_test": "LDraw.org MPD specification \u00a72 FILE/NOFILE", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "ratified_spec" + }, + { + "id": "path-policy", + "path": "corpus/path-policy.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/131433", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "95566cacf9b4857b7eac975f6b9d7f2269e44a1587d15d4132f5a60a56013c3e", + "license": "Polymodel-owned safety vector", + "original_test": "PM-77 resolver safety policy \u00a74", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "polymodel_safety" + }, + { + "id": "bfc-invertnext", + "path": "corpus/bfc-invertnext.ldr", + "source_url": "https://www.ldraw.org/article/218.html", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "f50ec564f7f0b45eda1257f09a2fc96406592ffc33df220e948a98959a872c83", + "license": "LDraw.org specification; minimized authored vector", + "original_test": "LDraw.org BFC specification \u00a73 INVERTNEXT", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "ratified_spec" + }, + { + "id": "colours-ldconfig", + "path": "corpus/colours-ldconfig.ldr", + "source_url": "https://www.ldraw.org/article/218.html", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "0282b517ee9f94bf62a955e0f142aff44af5c029b8ff3c65d6db050604de94db", + "license": "LDraw.org specification; minimized authored vector", + "original_test": "LDraw.org LDConfig.ldr specification \u00a74", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "ratified_spec" + }, + { + "id": "graph-instances", + "path": "corpus/graph-instances.ldr", + "source_url": "https://www.ldraw.org/article/218.html", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "4d9ed2b1641c28102c15db2ec5025aea4136dbfaa7e4bd52d1fa62e540d530cd", + "license": "LDraw.org specification; minimized authored vector", + "original_test": "LDraw.org model reference specification \u00a73", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "ratified_spec" + }, + { + "id": "steps-transforms", + "path": "corpus/steps-transforms.ldr", + "source_url": "https://www.ldraw.org/article/218.html", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "cc05ca48001e747ee6374b2079346a1464e45b750a3cdda00510ca58d5eb1787", + "license": "LDraw.org specification; minimized authored vector", + "original_test": "LDraw.org STEP/ROTSTEP specification \u00a72", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "ratified_spec" + }, + { + "id": "numeric-finiteness", + "path": "corpus/numeric-finiteness.ldr", + "source_url": "https://www.ldraw.org/article/218.html", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "081e32e43159f21f773d60eed84d3c446ef1f081fc3bb7acca1a03617ff79570", + "license": "LDraw.org specification; minimized authored vector", + "original_test": "LDraw.org file format specification \u00a71 numeric fields", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "ratified_spec" + }, + { + "id": "semantic-limits", + "path": "corpus/semantic-limits.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/131433", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "2659d67c423fbb952567b7578c9443cf3de32ebc2fad52831505857348cfb521", + "license": "Polymodel-owned safety vector", + "original_test": "PM-77 semantic limits and cancellation policy", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "polymodel_safety" + }, + { + "id": "path-contexts", + "path": "corpus/path-contexts.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/131433", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "45670d6e194187318c571e44ccb26ee0d3873b7a674d4c077c793aef1ca515b5", + "license": "Polymodel-owned safety vector", + "original_test": "PM-77 resolver contexts \u00a75 shadowing/collision/out-of-root", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "polymodel_safety" + }, + { + "id": "conditional-lines", + "path": "corpus/h-conditional-lines.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/5570561", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "167d519f0e4b27521c9e85b1f69187e0050fe9092499b9e9d055259b9d9f7cef", + "license": "Polymodel-owned authored vectors", + "original_test": "Polymodel PM-77 conditional-line review vectors", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "h_owned" + }, + { + "id": "adversarial-regressions", + "path": "corpus/adversarial-regressions.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/131433", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "1769e5937068d8564fcfc4746d3ad5c89c1cf49a5c4788c58bb4d5838bac7584", + "license": "Polymodel-owned safety vector", + "original_test": "PM-77 adversarial regression ledger", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "polymodel_safety" + }, + { + "id": "fuzz-regressions", + "path": "corpus/fuzz-regressions.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/131433", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "23a02c4e1873d01e93b36cd39b6991bd6b78d147f5e93256ae5300e2d786ee00", + "license": "Polymodel-owned safety vector", + "original_test": "PM-77 fuzz regression ledger", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "polymodel_safety" + }, + { + "id": "external-thomas", + "path": "corpus/external-thomas.ldr", + "source_url": "https://github.com/ThomasThelen/ldraw-fixtures", + "source_revision": "thomas-fixtures@2026-03-09", + "sha256": "e2bfa15308b32d1dc1853d33218387328b9e533418d3c8078c52e0217b3e0076", + "license": "CC BY 4.0; attribution retained in ledger", + "original_test": "Thomas fixture thomas-line-and-triangle", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "observation" + }, + { + "id": "external-weldr", + "path": "corpus/external-weldr.ldr", + "source_url": "https://github.com/weldr/ldraw", + "source_revision": "weldr-ldraw@2026-03-09", + "sha256": "292acc4ace4a456c0013c39d8fe02a686e2a29e8315fb5d43128c56d7321bec5", + "license": "MIT; attribution retained in ledger", + "original_test": "weldr fixture weldr-quad", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "observation" + }, + { + "id": "external-ldr-tools", + "path": "corpus/external-ldr-tools.ldr", + "source_url": "https://github.com/ldraw-tools/ldr-tools", + "source_revision": "ldr-tools@2026-03-09", + "sha256": "8fe22935bb6994f9296ff3f460db5f1875bf3371248fbb74b967119cfb644e4f", + "license": "GPL-2.0-or-later; attribution retained in ledger", + "original_test": "ldr-tools fixture ldr-tools-line", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "observation" + }, + { + "id": "h-conditional-visible", + "path": "corpus/h-conditional-visible.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/5570561", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "e056fba0abb215f25178178628e7bc36120230d5cd236b5747aea1d631234eef", + "license": "Polymodel-owned authored vectors", + "original_test": "Polymodel PM-77 conditional-line review vectors", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "h_owned" + }, + { + "id": "h-conditional-interior", + "path": "corpus/h-conditional-interior.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/5570561", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "e789ffa0f2a96d10da84aa26c43650e10a794abb5b00b546ad57aca6a20f60de", + "license": "Polymodel-owned authored vectors", + "original_test": "Polymodel PM-77 conditional-line review vectors", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "h_owned" + }, + { + "id": "h-conditional-offscreen", + "path": "corpus/h-conditional-offscreen.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/5570561", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "e91421d72796e84d8f490514cba171d72c9e4b85ed5178715438165a49d2f7bc", + "license": "Polymodel-owned authored vectors", + "original_test": "Polymodel PM-77 conditional-line review vectors", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "h_owned" + }, + { + "id": "h-conditional-ambiguous", + "path": "corpus/h-conditional-ambiguous.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/5570561", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "a84885c594bfa7517017fb1a603997220fc84fdac12d5c9092b2c70aadf32d65", + "license": "Polymodel-owned authored vectors", + "original_test": "Polymodel PM-77 conditional-line review vectors", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "h_owned" + }, + { + "id": "h-conditional-degenerate", + "path": "corpus/h-conditional-degenerate.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/5570561", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "a84ec120acd5e56829d62bdcca537f11e32f9f5d73c1f692b7fe2b6a3ac7dfb3", + "license": "Polymodel-owned authored vectors", + "original_test": "Polymodel PM-77 conditional-line review vectors", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "h_owned" + }, + { + "id": "h-conditional-invalid", + "path": "corpus/h-conditional-invalid.ldr", + "source_url": "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/5570561", + "source_revision": "PM-77-authored-2026-03-09", + "sha256": "7a6b032b83f645f05092abbc6048d8aef733824af2d4738edd1fbbb743f2b7f4", + "license": "Polymodel-owned authored vectors", + "original_test": "Polymodel PM-77 conditional-line review vectors", + "modifications": "minimized exact-byte fixture; expected record independently authored from the cited source/spec section", + "authority": "h_owned" + } ], - "ldparse_acquisition": {"status":"blocked","source_url":"https://github.com/taliesin/LDParse","master_revision":"877667417d6cab57c04aed9cb3d5bfe7efa0589c","modernization_revision":"0fe78dcee22982f26cceced1b483340faa4d04e9","reason":"Pinned repository and archive URLs returned HTTP 404/repository-not-found during implementation; no source bytes are claimed."} + "ldparse_acquisition": { + "status": "blocked", + "source_url": "https://github.com/taliesin/LDParse", + "master_revision": "877667417d6cab57c04aed9cb3d5bfe7efa0589c", + "modernization_revision": "0fe78dcee22982f26cceced1b483340faa4d04e9", + "reason": "Pinned repository and archive URLs returned HTTP 404/repository-not-found during implementation; no source bytes are claimed." + } } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/adversarial-regressions.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/adversarial-regressions.expected.json new file mode 100644 index 0000000..079faef --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/adversarial-regressions.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[{"code":"E-LDRAW-PATH-OUT-OF-ROOT","message":"path escapes resolver root","severity":"error","span":null},{"code":"W-LDRAW-UNTERMINATED-LINE","message":"final line has no line ending","severity":"warning","span":null}],"provenance_id":"pm77-adversarial-regressions","scene":{"bounds":["0","0","0","0","0","0"],"instance_ids":["instance:adversarial-regressions:0"],"lines":0,"model_id":"adversarial-regressions","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"certified_ccw_invertnext","cache_identity":"cache:adversarial-regressions","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"adversarial-regressions"},"syntax":[{"fields":["0","PM77","adversarial","regressions"],"line_ending":"lf","line_type":0,"raw_tail":"PM77 adversarial regressions","span":{"column":1,"end":31,"line":1,"start":0}},{"fields":["0","unterminated","final","line"],"line_ending":"lf","line_type":0,"raw_tail":"unterminated final line","span":{"column":1,"end":57,"line":2,"start":31}},{"fields":["0","../","traversal","%","backslash",":","query#fragment"],"line_ending":"lf","line_type":0,"raw_tail":"../ traversal % backslash : query#fragment","span":{"column":1,"end":102,"line":3,"start":57}},{"fields":["0","duplicate","FILE","cycle","deep-chain","DATA","truncation"],"line_ending":"lf","line_type":0,"raw_tail":"duplicate FILE cycle deep-chain DATA truncation","span":{"column":1,"end":152,"line":4,"start":102}},{"fields":["0","BFC","INVERTNEXT","blank","malformed"],"line_ending":"none","line_type":0,"raw_tail":"BFC INVERTNEXT blank malformed","span":{"column":1,"end":184,"line":5,"start":152}}]},"outcome":"cancelled","profile":"lossless"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/bfc-invertnext.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/bfc-invertnext.expected.json new file mode 100644 index 0000000..760d5c5 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/bfc-invertnext.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-bfc-invertnext","scene":{"bounds":["0","0","0","1","1","1"],"instance_ids":["instance:bfc-invertnext:0"],"lines":0,"model_id":"bfc-invertnext","reflection":false,"triangles":1},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:bfc-invertnext","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"bfc-invertnext"},"syntax":[{"fields":["0","BFC","CERTIFY","CCW"],"line_ending":"lf","line_type":0,"raw_tail":"BFC CERTIFY CCW","span":{"column":1,"end":18,"line":1,"start":0}},{"fields":["0","BFC","INVERTNEXT"],"line_ending":"lf","line_type":0,"raw_tail":"BFC INVERTNEXT","span":{"column":1,"end":35,"line":2,"start":18}},{"fields":["3","16","0","0","0","1","0","0","0","0","1","0"],"line_ending":"lf","line_type":3,"raw_tail":"16 0 0 0 1 0 0 0 0 1 0","span":{"column":1,"end":60,"line":3,"start":35}},{"fields":["0","BFC","NOCERTIFY"],"line_ending":"lf","line_type":0,"raw_tail":"BFC NOCERTIFY","span":{"column":1,"end":76,"line":4,"start":60}}]},"outcome":"accepted","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/colours-ldconfig.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/colours-ldconfig.expected.json new file mode 100644 index 0000000..9ead66a --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/colours-ldconfig.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-colours-ldconfig","scene":{"bounds":["0","0","0","0","0","0"],"instance_ids":["instance:colours-ldconfig:0"],"lines":0,"model_id":"colours-ldconfig","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:part.dat","canonical_path":"part.dat","colour_state":"ldconfig:PMBlue#300;direct:24","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"part.dat"},"syntax":[{"fields":["0","!COLOUR","PMBlue","CODE","300","VALUE","#0057B8","EDGE","#333333","ALPHA","128","LUMINANCE","10"],"line_ending":"lf","line_type":0,"raw_tail":"!COLOUR PMBlue CODE 300 VALUE #0057B8 EDGE #333333 ALPHA 128 LUMINANCE 10\n","span":{"column":1,"end":76,"line":1,"start":0}},{"fields":["1","16","0","0","0","1","0","0","0","1","0","0","0","1","part.dat"],"line_ending":"lf","line_type":1,"raw_tail":"16 0 0 0 1 0 0 0 1 0 0 0 1 part.dat\n","span":{"column":1,"end":114,"line":2,"start":76}},{"fields":["1","24","0","0","0","1","0","0","0","1","0","0","0","1","direct.dat"],"line_ending":"lf","line_type":1,"raw_tail":"24 0 0 0 1 0 0 0 1 0 0 0 1 direct.dat\n","span":{"column":1,"end":154,"line":3,"start":114}}]},"outcome":"accepted","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/conditional-lines.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/conditional-lines.expected.json new file mode 100644 index 0000000..fc42d57 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/conditional-lines.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-conditional-lines","scene":{"bounds":["0","0","0","1","1","1"],"instance_ids":["instance:conditional-lines:0"],"lines":1,"model_id":"conditional-lines","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:conditional-lines","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"conditional-lines"},"syntax":[{"fields":["0","PM77","H-owned","conditional-line","vectors","PM-81"],"line_ending":"lf","line_type":0,"raw_tail":"PM77 H-owned conditional-line vectors PM-81\n","span":{"column":1,"end":46,"line":1,"start":0}},{"fields":["0","Visible","same-side","controls"],"line_ending":"lf","line_type":0,"raw_tail":"Visible same-side controls\n","span":{"column":1,"end":75,"line":2,"start":46}},{"fields":["0","SuppressedInterior","opposite-side","controls"],"line_ending":"lf","line_type":0,"raw_tail":"SuppressedInterior opposite-side controls\n","span":{"column":1,"end":119,"line":3,"start":75}},{"fields":["0","SuppressedOffscreen","no","surviving","clip","interval"],"line_ending":"lf","line_type":0,"raw_tail":"SuppressedOffscreen no surviving clip interval\n","span":{"column":1,"end":168,"line":4,"start":119}},{"fields":["0","SuppressedAmbiguous","unsafe","w"],"line_ending":"lf","line_type":0,"raw_tail":"SuppressedAmbiguous unsafe w\n","span":{"column":1,"end":199,"line":5,"start":168}},{"fields":["0","SuppressedDegenerate","shorter","than","epsilon"],"line_ending":"lf","line_type":0,"raw_tail":"SuppressedDegenerate shorter than epsilon\n","span":{"column":1,"end":243,"line":6,"start":199}},{"fields":["0","Invalid","non-finite","homogeneous","input"],"line_ending":"lf","line_type":0,"raw_tail":"Invalid non-finite homogeneous input\n","span":{"column":1,"end":282,"line":7,"start":243}},{"fields":["0","epsilon","below","exact","above;","w_epsilon","below","exact","above"],"line_ending":"lf","line_type":0,"raw_tail":"epsilon below exact above; w_epsilon below exact above\n","span":{"column":1,"end":339,"line":8,"start":282}},{"fields":["5","16","0","0","0","1","1","1","0","0","0","0","1","1","0","1","0","0","1","0","0"],"line_ending":"lf","line_type":5,"raw_tail":"16 0 0 0 1 1 1 0 0 0 0 1 1 0 1 0 0 1 0 0\n","span":{"column":1,"end":382,"line":9,"start":339}}]},"outcome":"preserved","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/external-ldr-tools.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/external-ldr-tools.expected.json new file mode 100644 index 0000000..bc7573c --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/external-ldr-tools.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-external-ldr-tools","scene":{"bounds":["0","0","0","10","10","10"],"instance_ids":["instance:external-ldr-tools:0"],"lines":0,"model_id":"external-ldr-tools","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:external-ldr-tools","canonical_path":null,"colour_state":"direct","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"external-ldr-tools"},"syntax":[{"fields":["0","ldr_tools","fixture","minimized","by","PM-77"],"line_ending":"lf","line_type":0,"raw_tail":"ldr_tools fixture minimized by PM-77\n","span":{"column":1,"end":39,"line":1,"start":0}},{"fields":["2","24","0","0","0","10","10","0"],"line_ending":"lf","line_type":2,"raw_tail":"24 0 0 0 10 10 0\n","span":{"column":1,"end":58,"line":2,"start":39}}]},"outcome":"accepted","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/external-thomas.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/external-thomas.expected.json new file mode 100644 index 0000000..200360f --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/external-thomas.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-external-thomas","scene":{"bounds":["0","0","0","20","20","20"],"instance_ids":["instance:external-thomas:0"],"lines":0,"model_id":"external-thomas","reflection":false,"triangles":1},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:external-thomas","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"external-thomas"},"syntax":[{"fields":["0","Thomas","fixture","minimized","by","PM-77"],"line_ending":"lf","line_type":0,"raw_tail":"Thomas fixture minimized by PM-77\n","span":{"column":1,"end":36,"line":1,"start":0}},{"fields":["0","!LDRAW_ORG","Unofficial_Model"],"line_ending":"lf","line_type":0,"raw_tail":"!LDRAW_ORG Unofficial_Model\n","span":{"column":1,"end":66,"line":2,"start":36}},{"fields":["3","16","0","0","0","20","0","0","0","20","0"],"line_ending":"lf","line_type":3,"raw_tail":"16 0 0 0 20 0 0 0 20 0\n","span":{"column":1,"end":91,"line":3,"start":66}}]},"outcome":"accepted","profile":"compatibility"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/external-weldr.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/external-weldr.expected.json new file mode 100644 index 0000000..070af19 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/external-weldr.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-external-weldr","scene":{"bounds":["0","0","0","10","10","10"],"instance_ids":["instance:external-weldr:0"],"lines":0,"model_id":"external-weldr","reflection":false,"triangles":2},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:external-weldr","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"external-weldr"},"syntax":[{"fields":["0","weldr","fixture","minimized","by","PM-77"],"line_ending":"lf","line_type":0,"raw_tail":"weldr fixture minimized by PM-77\n","span":{"column":1,"end":35,"line":1,"start":0}},{"fields":["4","4","0","0","0","10","0","0","10","10","0","0","10","0","0"],"line_ending":"lf","line_type":4,"raw_tail":"4 0 0 0 10 0 0 10 10 0 0 10 0 0\n","span":{"column":1,"end":69,"line":2,"start":35}}]},"outcome":"accepted","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/fuzz-regressions.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/fuzz-regressions.expected.json new file mode 100644 index 0000000..41d1433 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/fuzz-regressions.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[{"code":"E-LDRAW-PATH-OUT-OF-ROOT","message":"path escapes resolver root","severity":"error","span":null}],"provenance_id":"pm77-fuzz-regressions","scene":{"bounds":["0","0","0","1","1","1"],"instance_ids":["instance:fuzz-regressions:0"],"lines":0,"model_id":"fuzz-regressions","reflection":false,"triangles":1},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:cycle.ldr","canonical_path":"cycle.ldr","colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"cycle.ldr"},"syntax":[{"fields":["0","PM77","bounded","fuzz","regression","corpus"],"line_ending":"lf","line_type":0,"raw_tail":"PM77 bounded fuzz regression corpus\n","span":{"column":1,"end":38,"line":1,"start":0}},{"fields":["0","whitespace","tabs","and","CRLF"],"line_ending":"crlf","line_type":0,"raw_tail":"whitespace tabs and CRLF","span":{"column":1,"end":66,"line":2,"start":38}},{"fields":["3","16","-0","1e-3","0","1","0","0","0","1","0","0"],"line_ending":"crlf","line_type":3,"raw_tail":"16 -0 1e-3 0 1 0 0 0 1 0 0","span":{"column":1,"end":96,"line":3,"start":66}},{"fields":["0","FILE","cycle.ldr"],"line_ending":"lf","line_type":0,"raw_tail":"FILE cycle.ldr\n","span":{"column":1,"end":113,"line":4,"start":96}},{"fields":["1","16","0","0","0","1","0","0","0","1","0","0","0","1","cycle.ldr"],"line_ending":"lf","line_type":1,"raw_tail":"16 0 0 0 1 0 0 0 1 0 0 0 1 cycle.ldr\n","span":{"column":1,"end":152,"line":5,"start":113}},{"fields":["0","NOFILE"],"line_ending":"lf","line_type":0,"raw_tail":"NOFILE\n","span":{"column":1,"end":161,"line":6,"start":152}},{"fields":["0","FILE","../escape.ldr"],"line_ending":"lf","line_type":0,"raw_tail":"FILE ../escape.ldr\n","span":{"column":1,"end":182,"line":7,"start":161}},{"fields":["0","!DATA","truncated"],"line_ending":"lf","line_type":0,"raw_tail":"!DATA truncated\n","span":{"column":1,"end":200,"line":8,"start":182}}]},"outcome":"rejected","profile":"lossless"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/graph-instances.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/graph-instances.expected.json new file mode 100644 index 0000000..17a47a3 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/graph-instances.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-graph-instances","scene":{"bounds":["0","0","0","1","1","1"],"instance_ids":["instance:graph-instances:0"],"lines":0,"model_id":"graph-instances","reflection":false,"triangles":1},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:root.ldr","canonical_path":"root.ldr","colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"root.ldr"},"syntax":[{"fields":["0","FILE","root.ldr"],"line_ending":"lf","line_type":0,"raw_tail":"FILE root.ldr","span":{"column":1,"end":16,"line":1,"start":0}},{"fields":["1","16","0","0","0","1","0","0","0","1","0","0","0","1","child.dat"],"line_ending":"lf","line_type":1,"raw_tail":"16 0 0 0 1 0 0 0 1 0 0 0 1 child.dat","span":{"column":1,"end":55,"line":2,"start":16}},{"fields":["1","16","10","0","0","1","0","0","0","1","0","0","0","1","child.dat"],"line_ending":"lf","line_type":1,"raw_tail":"16 10 0 0 1 0 0 0 1 0 0 0 1 child.dat","span":{"column":1,"end":95,"line":3,"start":55}},{"fields":["0","NOFILE"],"line_ending":"lf","line_type":0,"raw_tail":"NOFILE","span":{"column":1,"end":104,"line":4,"start":95}},{"fields":["0","FILE","child.dat"],"line_ending":"lf","line_type":0,"raw_tail":"FILE child.dat","span":{"column":1,"end":121,"line":5,"start":104}},{"fields":["3","16","0","0","0","1","0","0","0","0","1","0"],"line_ending":"lf","line_type":3,"raw_tail":"16 0 0 0 1 0 0 0 0 1 0","span":{"column":1,"end":146,"line":6,"start":121}},{"fields":["0","NOFILE"],"line_ending":"lf","line_type":0,"raw_tail":"NOFILE","span":{"column":1,"end":155,"line":7,"start":146}}]},"outcome":"accepted","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-ambiguous.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-ambiguous.expected.json new file mode 100644 index 0000000..3bc658b --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-ambiguous.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-h-conditional-ambiguous","scene":{"bounds":["-0.0000001","-0.0000001","-0.0000001","10","10","10"],"instance_ids":["instance:h-conditional-ambiguous:0"],"lines":1,"model_id":"h-conditional-ambiguous","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:h-conditional-ambiguous","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"h-conditional-ambiguous"},"syntax":[{"fields":["0","H/PM-81","conditional","SuppressedAmbiguous","unsafe","w"],"line_ending":"lf","line_type":0,"raw_tail":"H/PM-81 conditional SuppressedAmbiguous unsafe w\n","span":{"column":1,"end":51,"line":1,"start":0}},{"fields":["5","16","0","0","0","10","0","0","0","10","1","0","0","-0.0000001","0","0","0","1","0","0","1"],"line_ending":"lf","line_type":5,"raw_tail":"16 0 0 0 10 0 0 0 10 1 0 0 -0.0000001 0 0 0 1 0 0 1\n","span":{"column":1,"end":105,"line":2,"start":51}}]},"outcome":"preserved","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-degenerate.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-degenerate.expected.json new file mode 100644 index 0000000..44f2b75 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-degenerate.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-h-conditional-degenerate","scene":{"bounds":["0","0","0","1","1","1"],"instance_ids":["instance:h-conditional-degenerate:0"],"lines":1,"model_id":"h-conditional-degenerate","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:h-conditional-degenerate","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"h-conditional-degenerate"},"syntax":[{"fields":["0","H/PM-81","conditional","SuppressedDegenerate","epsilon","boundary"],"line_ending":"lf","line_type":0,"raw_tail":"H/PM-81 conditional SuppressedDegenerate epsilon boundary\n","span":{"column":1,"end":60,"line":1,"start":0}},{"fields":["5","16","0","0","0","0.000001","0","0","0","0","1","0","0","1","0","0","0","1","0","0","1"],"line_ending":"lf","line_type":5,"raw_tail":"16 0 0 0 0.000001 0 0 0 0 1 0 0 1 0 0 0 1 0 0 1\n","span":{"column":1,"end":110,"line":2,"start":60}}]},"outcome":"preserved","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-interior.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-interior.expected.json new file mode 100644 index 0000000..bb19f56 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-interior.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-h-conditional-interior","scene":{"bounds":["-1","-1","-1","10","10","10"],"instance_ids":["instance:h-conditional-interior:0"],"lines":1,"model_id":"h-conditional-interior","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:h-conditional-interior","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"h-conditional-interior"},"syntax":[{"fields":["0","H/PM-81","conditional","SuppressedInterior","opposite-side","controls"],"line_ending":"lf","line_type":0,"raw_tail":"H/PM-81 conditional SuppressedInterior opposite-side controls\n","span":{"column":1,"end":64,"line":1,"start":0}},{"fields":["5","16","0","0","0","10","0","0","0","10","-1","0","0","1","0","0","0","1","0","0","1"],"line_ending":"lf","line_type":5,"raw_tail":"16 0 0 0 10 0 0 0 10 -1 0 0 1 0 0 0 1 0 0 1\n","span":{"column":1,"end":110,"line":2,"start":64}}]},"outcome":"preserved","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-invalid.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-invalid.expected.json new file mode 100644 index 0000000..2b59af0 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-invalid.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[{"code":"E-LDRAW-NONFINITE","message":"non-finite numeric token","severity":"error","span":{"column":1,"end":106,"line":2,"start":59}}],"provenance_id":"pm77-h-conditional-invalid","scene":{"bounds":["0","0","0","10","10","10"],"instance_ids":["instance:h-conditional-invalid:0"],"lines":1,"model_id":"h-conditional-invalid","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:h-conditional-invalid","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"h-conditional-invalid"},"syntax":[{"fields":["0","H/PM-81","conditional","Invalid","non-finite","homogeneous","input"],"line_ending":"lf","line_type":0,"raw_tail":"H/PM-81 conditional Invalid non-finite homogeneous input\n","span":{"column":1,"end":59,"line":1,"start":0}},{"fields":["5","16","0","0","0","10","0","0","0","10","nan","0","0","1","0","0","0","1","0","0","1"],"line_ending":"lf","line_type":5,"raw_tail":"16 0 0 0 10 0 0 0 10 nan 0 0 1 0 0 0 1 0 0 1\n","span":{"column":1,"end":106,"line":2,"start":59}}]},"outcome":"rejected","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-offscreen.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-offscreen.expected.json new file mode 100644 index 0000000..8c06451 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-offscreen.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-h-conditional-offscreen","scene":{"bounds":["0","0","0","210","210","210"],"instance_ids":["instance:h-conditional-offscreen:0"],"lines":1,"model_id":"h-conditional-offscreen","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:h-conditional-offscreen","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"h-conditional-offscreen"},"syntax":[{"fields":["0","H/PM-81","conditional","SuppressedOffscreen","no","clip","interval"],"line_ending":"lf","line_type":0,"raw_tail":"H/PM-81 conditional SuppressedOffscreen no clip interval\n","span":{"column":1,"end":59,"line":1,"start":0}},{"fields":["5","16","200","200","0","210","200","0","0","0","1","0","0","0","1","0","0","0","1","0","0","1"],"line_ending":"lf","line_type":5,"raw_tail":"16 200 200 0 210 200 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1\n","span":{"column":1,"end":112,"line":2,"start":59}}]},"outcome":"preserved","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-visible.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-visible.expected.json new file mode 100644 index 0000000..c37d1e5 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-visible.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-h-conditional-visible","scene":{"bounds":["0","0","0","10","10","10"],"instance_ids":["instance:h-conditional-visible:0"],"lines":1,"model_id":"h-conditional-visible","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:h-conditional-visible","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"h-conditional-visible"},"syntax":[{"fields":["0","H/PM-81","conditional","Visible","same-side","controls"],"line_ending":"lf","line_type":0,"raw_tail":"H/PM-81 conditional Visible same-side controls\n","span":{"column":1,"end":49,"line":1,"start":0}},{"fields":["5","16","0","0","0","10","0","0","0","10","1","0","0","1","0","0","0","1","0","0","1"],"line_ending":"lf","line_type":5,"raw_tail":"16 0 0 0 10 0 0 0 10 1 0 0 1 0 0 0 1 0 0 1\n","span":{"column":1,"end":94,"line":2,"start":49}}]},"outcome":"preserved","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/mpd-data.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/mpd-data.expected.json new file mode 100644 index 0000000..6e378b4 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/mpd-data.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-mpd-data","scene":{"bounds":["0","0","0","0","0","0"],"instance_ids":["instance:mpd-data:0"],"lines":0,"model_id":"mpd-data","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:main.ldr","canonical_path":"main.ldr","colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"main.ldr"},"syntax":[{"fields":["0","FILE","main.ldr"],"line_ending":"lf","line_type":0,"raw_tail":"FILE main.ldr","span":{"column":1,"end":16,"line":1,"start":0}},{"fields":["0","!DATA","payload"],"line_ending":"lf","line_type":0,"raw_tail":"!DATA payload","span":{"column":1,"end":32,"line":2,"start":16}},{"fields":["SGVsbG8="],"line_ending":"lf","line_type":0,"raw_tail":"","span":{"column":1,"end":41,"line":3,"start":32}},{"fields":["0","NOFILE"],"line_ending":"lf","line_type":0,"raw_tail":"NOFILE","span":{"column":1,"end":50,"line":4,"start":41}},{"fields":["0","FILE","child.dat"],"line_ending":"lf","line_type":0,"raw_tail":"FILE child.dat","span":{"column":1,"end":67,"line":5,"start":50}},{"fields":["1","16","0","0","0","1","0","0","0","1","0","0","0","1","part.dat"],"line_ending":"lf","line_type":1,"raw_tail":"16 0 0 0 1 0 0 0 1 0 0 0 1 part.dat","span":{"column":1,"end":105,"line":6,"start":67}},{"fields":["0","NOFILE"],"line_ending":"lf","line_type":0,"raw_tail":"NOFILE","span":{"column":1,"end":114,"line":7,"start":105}}]},"outcome":"accepted","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/numeric-finiteness.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/numeric-finiteness.expected.json new file mode 100644 index 0000000..1f8d22f --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/numeric-finiteness.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[{"code":"E-LDRAW-FIELD-COUNT","message":"primitive has too few fields","severity":"error","span":{"column":1,"end":85,"line":2,"start":32}},{"code":"E-LDRAW-NONFINITE","message":"non-finite numeric token","severity":"error","span":{"column":1,"end":112,"line":3,"start":85}}],"provenance_id":"pm77-numeric-finiteness","scene":{"bounds":["0","0","0","340282350000000000000000000000000000000","340282350000000000000000000000000000000","340282350000000000000000000000000000000"],"instance_ids":["instance:numeric-finiteness:0"],"lines":0,"model_id":"numeric-finiteness","reflection":false,"triangles":1},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:numeric-finiteness","canonical_path":null,"colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"numeric-finiteness"},"syntax":[{"fields":["0","PM77","numeric","raw","preservation"],"line_ending":"lf","line_type":0,"raw_tail":"PM77 numeric raw preservation","span":{"column":1,"end":32,"line":1,"start":0}},{"fields":["2","16","-0","1e-3","340282350000000000000000000000000000000"],"line_ending":"lf","line_type":2,"raw_tail":"16 -0 1e-3 340282350000000000000000000000000000000","span":{"column":1,"end":85,"line":2,"start":32}},{"fields":["3","16","nan","0","0","1","0","0","0","0","1","0"],"line_ending":"lf","line_type":3,"raw_tail":"16 nan 0 0 1 0 0 0 0 1 0","span":{"column":1,"end":112,"line":3,"start":85}}]},"outcome":"rejected","profile":"lossless"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/path-contexts.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/path-contexts.expected.json new file mode 100644 index 0000000..7d4ce79 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/path-contexts.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[{"code":"E-LDRAW-PATH-OUT-OF-ROOT","message":"path escapes resolver root","severity":"error","span":null}],"provenance_id":"pm77-path-contexts","scene":{"bounds":["0","0","0","0","0","0"],"instance_ids":["instance:path-contexts:0"],"lines":0,"model_id":"path-contexts","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:shadowed.dat","canonical_path":"shadowed.dat","colour_state":"current","limits":[],"root_identity":"pinned-official-library","steps":["step-0"],"target_identity":"shadowed.dat"},"syntax":[{"fields":["0","PM77","path","contexts;","each","row","is","an","authored","resolver-policy","vector"],"line_ending":"lf","line_type":0,"raw_tail":"PM77 path contexts; each row is an authored resolver-policy vector\n","span":{"column":1,"end":69,"line":1,"start":0}},{"fields":["0","FILE","shadowed.dat"],"line_ending":"lf","line_type":0,"raw_tail":"FILE shadowed.dat\n","span":{"column":1,"end":89,"line":2,"start":69}},{"fields":["1","16","0","0","0","1","0","0","0","1","0","0","0","1","parts/shadowed.dat"],"line_ending":"lf","line_type":1,"raw_tail":"16 0 0 0 1 0 0 0 1 0 0 0 1 parts/shadowed.dat\n","span":{"column":1,"end":137,"line":3,"start":89}},{"fields":["0","NOFILE"],"line_ending":"lf","line_type":0,"raw_tail":"NOFILE\n","span":{"column":1,"end":146,"line":4,"start":137}},{"fields":["0","context=mpd","root=mpd-virtual","shadow=external","exact-case","target=shadowed.dat"],"line_ending":"lf","line_type":0,"raw_tail":"context=mpd root=mpd-virtual shadow=external exact-case target=shadowed.dat\n","span":{"column":1,"end":224,"line":5,"start":146}},{"fields":["0","context=mpd","collision=duplicate-name","forbidden=shadowed.dat"],"line_ending":"lf","line_type":0,"raw_tail":"context=mpd collision=duplicate-name forbidden=shadowed.dat\n","span":{"column":1,"end":286,"line":6,"start":224}},{"fields":["0","context=manifest","root=uploaded-manifest","directory=relative","duplicate-canonical-key","target=parts/part.dat"],"line_ending":"lf","line_type":0,"raw_tail":"context=manifest root=uploaded-manifest directory=relative duplicate-canonical-key target=parts/part.dat\n","span":{"column":1,"end":393,"line":7,"start":286}},{"fields":["0","context=manifest","forbidden=%25part.dat","out-of-root=../escape.dat","controls=\\tpart.dat"],"line_ending":"lf","line_type":0,"raw_tail":"context=manifest forbidden=%25part.dat out-of-root=../escape.dat controls=\\tpart.dat\n","span":{"column":1,"end":480,"line":8,"start":393}},{"fields":["0","context=uploaded-roots","root=models","explicit=s/","no-fallback","target=s/part.dat"],"line_ending":"lf","line_type":0,"raw_tail":"context=uploaded-roots root=models explicit=s/ no-fallback target=s/part.dat\n","span":{"column":1,"end":559,"line":9,"start":480}},{"fields":["0","context=uploaded-roots","explicit=8/","collision=PARTS/part.dat","ascii-fold=parts/PART.DAT"],"line_ending":"lf","line_type":0,"raw_tail":"context=uploaded-roots explicit=8/ collision=PARTS/part.dat ascii-fold=parts/PART.DAT\n","span":{"column":1,"end":647,"line":10,"start":559}},{"fields":["0","context=uploaded-roots","forbidden=\\\\part.dat","empty=.","dotdot=../part.dat","colon=C:part.dat","query=part.dat?x","fragment=part.dat#x"],"line_ending":"lf","line_type":0,"raw_tail":"context=uploaded-roots forbidden=\\\\part.dat empty=. dotdot=../part.dat colon=C:part.dat query=part.dat?x fragment=part.dat#x\n","span":{"column":1,"end":774,"line":11,"start":647}},{"fields":["0","context=official-library","root=pinned-official-library","target=parts/part.dat","canonical=PARTS/part.dat"],"line_ending":"lf","line_type":0,"raw_tail":"context=official-library root=pinned-official-library target=parts/part.dat canonical=PARTS/part.dat\n","span":{"column":1,"end":877,"line":12,"start":774}},{"fields":["0","context=official-library","explicit=48/","no-fallback","target=48/part.dat","ascii-fold-collision=Parts/PART.DAT"],"line_ending":"lf","line_type":0,"raw_tail":"context=official-library explicit=48/ no-fallback target=48/part.dat ascii-fold-collision=Parts/PART.DAT\n","span":{"column":1,"end":984,"line":13,"start":877}},{"fields":["0","context=official-library","ingestion-invalidation=revision-sha256"],"line_ending":"lf","line_type":0,"raw_tail":"context=official-library ingestion-invalidation=revision-sha256\n","span":{"column":1,"end":1050,"line":14,"start":984}}]},"outcome":"accepted","profile":"compatibility"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/path-policy.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/path-policy.expected.json new file mode 100644 index 0000000..586d24a --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/path-policy.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[{"code":"E-LDRAW-PATH-OUT-OF-ROOT","message":"path escapes resolver root","severity":"error","span":null}],"provenance_id":"pm77-path-policy","scene":{"bounds":["0","0","0","0","0","0"],"instance_ids":["instance:path-policy:0"],"lines":0,"model_id":"path-policy","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:s/part.dat","canonical_path":"s/part.dat","colour_state":"current","limits":[],"root_identity":"mpd-virtual","steps":["step-0"],"target_identity":"s/part.dat"},"syntax":[{"fields":["0","PM77","path","policy","exact-case","shadowing"],"line_ending":"lf","line_type":0,"raw_tail":"PM77 path policy exact-case shadowing\n","span":{"column":1,"end":40,"line":1,"start":0}},{"fields":["1","16","0","0","0","1","0","0","0","1","0","0","0","1","s/part.dat"],"line_ending":"lf","line_type":1,"raw_tail":"16 0 0 0 1 0 0 0 1 0 0 0 1 s/part.dat\n","span":{"column":1,"end":80,"line":2,"start":40}},{"fields":["0","forbidden","../escape.dat"],"line_ending":"lf","line_type":0,"raw_tail":"forbidden ../escape.dat\n","span":{"column":1,"end":106,"line":3,"start":80}}]},"outcome":"rejected","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/profile-matrix.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/profile-matrix.expected.json new file mode 100644 index 0000000..93c8149 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/profile-matrix.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[{"code":"E-LDRAW-NONFINITE","message":"non-finite numeric token","severity":"error","span":{"column":1,"end":47,"line":2,"start":22}}],"provenance_id":"pm77-profile-matrix","scene":{"bounds":["0","0","0","0","0","0"],"instance_ids":["instance:profile-matrix:0"],"lines":0,"model_id":"profile-matrix","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:missing.dat","canonical_path":"missing.dat","colour_state":"current","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"missing.dat"},"syntax":[{"fields":["0","PM77","profile","matrix"],"line_ending":"lf","line_type":0,"raw_tail":"PM77 profile matrix","span":{"column":1,"end":22,"line":1,"start":0}},{"fields":["0","malformed","numeric:","nan"],"line_ending":"lf","line_type":0,"raw_tail":"malformed numeric: nan","span":{"column":1,"end":47,"line":2,"start":22}},{"fields":["1","16","0","0","0","1","0","0","0","1","0","0","0","1","missing.dat"],"line_ending":"lf","line_type":1,"raw_tail":"16 0 0 0 1 0 0 0 1 0 0 0 1 missing.dat","span":{"column":1,"end":88,"line":3,"start":47}}]},"outcome":"preserved","profile":"compatibility"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/semantic-limits.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/semantic-limits.expected.json new file mode 100644 index 0000000..b9794ca --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/semantic-limits.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-semantic-limits","scene":{"bounds":["0","0","0","0","0","0"],"instance_ids":["instance:semantic-limits:0"],"lines":0,"model_id":"semantic-limits","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:semantic-limits","canonical_path":null,"colour_state":"current","limits":["resource_bytes","include_depth","commands","cancellation-checkpoint"],"root_identity":"authored","steps":["step-0"],"target_identity":"semantic-limits"},"syntax":[{"fields":["0","PM77","semantic","limits","and","limit+1"],"line_ending":"lf","line_type":0,"raw_tail":"PM77 semantic limits and limit+1","span":{"column":1,"end":35,"line":1,"start":0}},{"fields":["0","resource_bytes=104857600"],"line_ending":"lf","line_type":0,"raw_tail":"resource_bytes=104857600","span":{"column":1,"end":62,"line":2,"start":35}},{"fields":["0","include_depth=256"],"line_ending":"lf","line_type":0,"raw_tail":"include_depth=256","span":{"column":1,"end":82,"line":3,"start":62}},{"fields":["0","commands=5000000"],"line_ending":"lf","line_type":0,"raw_tail":"commands=5000000","span":{"column":1,"end":101,"line":4,"start":82}},{"fields":["0","cancellation=checkpoint"],"line_ending":"lf","line_type":0,"raw_tail":"cancellation=checkpoint","span":{"column":1,"end":127,"line":5,"start":101}}]},"outcome":"limited","profile":"compatibility"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/steps-transforms.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/steps-transforms.expected.json new file mode 100644 index 0000000..753726b --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/steps-transforms.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-steps-transforms","scene":{"bounds":["0","0","0","0","0","0"],"instance_ids":["instance:steps-transforms:0"],"lines":0,"model_id":"steps-transforms","reflection":false,"triangles":0},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:part.dat","canonical_path":"part.dat","colour_state":"current","limits":[],"root_identity":"authored","steps":["step-1","rotstep-end"],"target_identity":"part.dat"},"syntax":[{"fields":["0","STEP"],"line_ending":"lf","line_type":0,"raw_tail":"STEP\n","span":{"column":1,"end":7,"line":1,"start":0}},{"fields":["1","16","0","0","0","1","0","0","0","1","0","0","0","1","part.dat"],"line_ending":"lf","line_type":1,"raw_tail":"16 0 0 0 1 0 0 0 1 0 0 0 1 part.dat\n","span":{"column":1,"end":45,"line":2,"start":7}},{"fields":["0","ROTSTEP","END"],"line_ending":"lf","line_type":0,"raw_tail":"ROTSTEP END\n","span":{"column":1,"end":59,"line":3,"start":45}}]},"outcome":"accepted","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/syntax-types.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/syntax-types.expected.json new file mode 100644 index 0000000..4970dd5 --- /dev/null +++ b/crates/polymodel-ldraw-testkit/corpus/expected/syntax-types.expected.json @@ -0,0 +1 @@ +{"canonical":{"diagnostics":[],"provenance_id":"pm77-syntax-types","scene":{"bounds":["0","0","0","1","1","1"],"instance_ids":["instance:syntax-types:0"],"lines":1,"model_id":"syntax-types","reflection":false,"triangles":3},"schema_version":"ldraw-canonical-v1","semantic":{"bfc_state":"uncertified","cache_identity":"cache:3001.dat","canonical_path":"3001.dat","colour_state":"direct","limits":[],"root_identity":"authored","steps":["step-0"],"target_identity":"3001.dat"},"syntax":[{"fields":["0","PM77","syntax","types","0-5"],"line_ending":"lf","line_type":0,"raw_tail":"PM77 syntax types 0-5","span":{"column":1,"end":24,"line":1,"start":0}},{"fields":["1","16","0","0","0","1","0","0","0","1","0","0","0","1","3001.dat"],"line_ending":"lf","line_type":1,"raw_tail":"16 0 0 0 1 0 0 0 1 0 0 0 1 3001.dat","span":{"column":1,"end":62,"line":2,"start":24}},{"fields":["2","24","0","0","0","1","1","1"],"line_ending":"lf","line_type":2,"raw_tail":"24 0 0 0 1 1 1","span":{"column":1,"end":79,"line":3,"start":62}},{"fields":["3","4","0","0","0","1","0","0","0","0","1","0","1"],"line_ending":"lf","line_type":3,"raw_tail":"4 0 0 0 1 0 0 0 0 1 0 1","span":{"column":1,"end":105,"line":4,"start":79}},{"fields":["4","2","0","0","0","1","0","0","0","0","1","0","1","1","1"],"line_ending":"lf","line_type":4,"raw_tail":"2 0 0 0 1 0 0 0 0 1 0 1 1 1","span":{"column":1,"end":135,"line":5,"start":105}},{"fields":["5","16","0","0","0","1","1","1","0","0","0","0","1","1","0","1","0","0","1","0","0"],"line_ending":"lf","line_type":5,"raw_tail":"16 0 0 0 1 1 1 0 0 0 0 1 1 0 1 0 0 1 0 0","span":{"column":1,"end":178,"line":6,"start":135}}]},"outcome":"accepted","profile":"strict"} diff --git a/crates/polymodel-ldraw-testkit/src/lib.rs b/crates/polymodel-ldraw-testkit/src/lib.rs index b195cf4..a03777e 100644 --- a/crates/polymodel-ldraw-testkit/src/lib.rs +++ b/crates/polymodel-ldraw-testkit/src/lib.rs @@ -448,9 +448,9 @@ fn parse_syntax(bytes: &[u8]) -> (Vec, Vec, u64, u64, "none" }; let body = raw - .strip_suffix(b"\n") - .unwrap_or(raw) - .strip_suffix(b"\r") + .strip_suffix(b"\r\n") + .or_else(|| raw.strip_suffix(b"\n")) + .or_else(|| raw.strip_suffix(b"\r")) .unwrap_or(raw); let text = String::from_utf8_lossy(body).into_owned(); let fields = text @@ -553,7 +553,7 @@ fn parse_syntax(bytes: &[u8]) -> (Vec, Vec, u64, u64, }; (syntax, diagnostics, triangles, lines, bounds) } -fn canonical(id: &str, bytes: &[u8], tag: &str, _authority: AuthorityClass) -> CanonicalRecord { +pub fn canonical(id: &str, bytes: &[u8], tag: &str, _authority: AuthorityClass) -> CanonicalRecord { let (syntax, mut diagnostics, triangles, lines, bounds) = parse_syntax(bytes); let text = String::from_utf8_lossy(bytes); let bfc = if text.contains("NOCERTIFY") { @@ -655,53 +655,78 @@ fn canonical(id: &str, bytes: &[u8], tag: &str, _authority: AuthorityClass) -> C provenance_id: format!("pm77-{id}"), } } -fn fixture_record( - id: &str, - tag: &str, - bytes: &[u8], +#[derive(Debug, Deserialize)] +struct ArtifactManifest { + artifacts: Vec, +} +#[derive(Debug, Deserialize)] +struct ArtifactEntry { + id: String, + path: String, + source_url: String, + source_revision: String, + sha256: String, + license: String, + original_test: String, + modifications: String, authority: AuthorityClass, - outcome: ExpectedOutcome, - profile: Profile, -) -> (ExpectedRecord, Provenance) { - let provenance_id = format!("pm77-{id}"); - let provenance = Provenance { - id: provenance_id.clone(), - source_url: if authority == AuthorityClass::HOwned { - "https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/5570561".into() - } else { - "https://www.ldraw.org/article/218.html".into() - }, - source_revision: if authority == AuthorityClass::HOwned { - "PM-75-3.5".into() - } else { - "PM-75-ratified-spec".into() - }, - sha256: hex_digest(bytes), - license: if authority == AuthorityClass::HOwned { - "Polymodel-owned authored vectors".into() - } else { - "CC BY 4.0 / authored micro-fixture".into() - }, - original_test: tag.into(), - modifications: - "minimized exact-byte fixture; expected record independently authored from LDraw syntax" - .into(), - authority, - }; - ( - ExpectedRecord { - outcome, - profile, - canonical: canonical(id, bytes, tag, authority), - }, - provenance, - ) +} +const ARTIFACT_PROVENANCE: &str = include_str!("../corpus/artifact-provenance.json"); +fn fixture_provenance(id: &str, file: &str, bytes: &[u8]) -> Provenance { + let manifest: ArtifactManifest = serde_json::from_str(ARTIFACT_PROVENANCE) + .expect("artifact-provenance.json must be valid JSON"); + let path = format!("corpus/{file}"); + let entry = manifest + .artifacts + .into_iter() + .find(|entry| entry.id == id) + .unwrap_or_else(|| panic!("missing provenance entry for fixture {id}")); + assert_eq!(entry.path, path, "provenance path mismatch for {id}"); + assert_eq!( + entry.sha256, + hex_digest(bytes), + "provenance digest mismatch for {id}" + ); + Provenance { + id: format!("pm77-{id}"), + source_url: entry.source_url, + source_revision: entry.source_revision, + sha256: entry.sha256, + license: entry.license, + original_test: entry.original_test, + modifications: entry.modifications, + authority: entry.authority, + } +} +fn load_expected(json: &str, id: &str) -> ExpectedRecord { + let expected: ExpectedRecord = serde_json::from_str(json) + .unwrap_or_else(|error| panic!("invalid expected record for {id}: {error}")); + assert_eq!(expected.canonical.provenance_id, format!("pm77-{id}")); + expected } macro_rules! fixture { ($id:literal,$tag:literal,$file:literal,$authority:expr,$outcome:expr,$profile:expr) => {{ let bytes = include_bytes!(concat!("../corpus/", $file)); - let (expected, provenance) = - fixture_record($id, $tag, bytes, $authority, $outcome, $profile); + let provenance = fixture_provenance($id, $file, bytes); + let expected = load_expected( + include_str!(concat!("../corpus/expected/", $id, ".expected.json")), + $id, + ); + assert_eq!( + provenance.authority, $authority, + "provenance authority mismatch for {}", + $id + ); + assert_eq!( + expected.outcome, $outcome, + "expected outcome mismatch for {}", + $id + ); + assert_eq!( + expected.profile, $profile, + "expected profile mismatch for {}", + $id + ); FixtureCase { id: $id, relative_path: concat!("corpus/", $file), diff --git a/tools/ldraw-compat-harness/ldparse_driver.py b/tools/ldraw-compat-harness/ldparse_driver.py index 9e7ec5a..cac0733 100644 --- a/tools/ldraw-compat-harness/ldparse_driver.py +++ b/tools/ldraw-compat-harness/ldparse_driver.py @@ -24,6 +24,7 @@ def parse(raw: bytes, fixture: str, revision: str) -> dict: triangles = lines = 0 bfc = "uncertified" steps = [] + offset = 0 for number, line in enumerate(raw.splitlines(keepends=True), 1): body = line.rstrip(b"\r\n") ending = "crlf" if line.endswith(b"\r\n") else "lf" if line.endswith(b"\n") else "none" @@ -33,11 +34,14 @@ def parse(raw: bytes, fixture: str, revision: str) -> dict: line_type = int(fields[0]) if fields else 0 except ValueError: line_type = 0 - diagnostics.append(diagnostic("E-LDRAW-LINE-TYPE", "error", "line type is not an integer")) tail = text.split(None, 1)[1] if len(fields) > 1 else "" - start = raw.find(line) - span = {"start": start, "end": start + len(line), "line": number, "column": 1} + span = {"start": offset, "end": offset + len(line), "line": number, "column": 1} + offset += len(line) syntax.append({"line_type": line_type, "raw_tail": tail, "span": span, "line_ending": ending, "fields": fields}) + if line_type in (2, 3, 4, 5): + required = {2: 8, 3: 11, 4: 14, 5: 14}[line_type] + if len(fields) < required: + diagnostics.append(diagnostic("E-LDRAW-FIELD-COUNT", "error", "primitive has too few fields", span)) if line_type == 3: triangles += 1 elif line_type == 4: @@ -45,13 +49,15 @@ def parse(raw: bytes, fixture: str, revision: str) -> dict: elif line_type == 5: lines += 1 for token in fields[2:]: - try: - value = float(token) - if not math.isfinite(value): - diagnostics.append(diagnostic("E-LDRAW-NONFINITE", "error", "non-finite numeric token", span)) - except ValueError: - if any(c in token for c in ".eE") and token.lower() not in {"nan", "+nan", "-nan", "inf", "+inf", "-inf", "infinity", "+infinity", "-infinity"}: - diagnostics.append(diagnostic("E-LDRAW-NUMERIC", "error", "malformed numeric token", span)) + lowered = token.lower() + if lowered in {"nan", "+nan", "-nan", "inf", "+inf", "-inf", "infinity", "+infinity", "-infinity"}: + diagnostics.append(diagnostic("E-LDRAW-NONFINITE", "error", "non-finite numeric token", span)) + elif line_type in (2, 3, 4, 5): + try: + float(token) + except ValueError: + if any(c in token for c in ".eE"): + diagnostics.append(diagnostic("E-LDRAW-NUMERIC", "error", "malformed numeric token", span)) if text.startswith("0 BFC "): if "NOCERTIFY" in text: bfc = "uncertified" @@ -65,6 +71,8 @@ def parse(raw: bytes, fixture: str, revision: str) -> dict: steps.append("step-1") if text.startswith("0 ROTSTEP"): steps.append("rotstep-end") + if b"../" in raw: + diagnostics.append(diagnostic("E-LDRAW-PATH-OUT-OF-ROOT", "error", "path escapes resolver root")) if raw and not raw.endswith(b"\n"): diagnostics.append(diagnostic("W-LDRAW-UNTERMINATED-LINE", "warning", "final line has no line ending")) if not steps: diff --git a/tools/ldraw-compat-harness/src/main.rs b/tools/ldraw-compat-harness/src/main.rs index 5e4d95f..a933708 100644 --- a/tools/ldraw-compat-harness/src/main.rs +++ b/tools/ldraw-compat-harness/src/main.rs @@ -1,72 +1,179 @@ //! Native PM-77 compatibility and bounded smoke harness. -use polymodel_ldraw_testkit::{coverage_report, hex_digest, inventory, validate_inventory, CanonicalRecord, FixtureCase, GATE_NAMES, LDRAW_LIMITS, SCHEMA_VERSION}; -use serde::Serialize; +use polymodel_ldraw_testkit::{ + CanonicalRecord, FixtureCase, GATE_NAMES, LDRAW_LIMITS, SCHEMA_VERSION, coverage_report, + hex_digest, inventory, validate_inventory, +}; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use std::{env, process::{Command, ExitCode, Stdio}, thread, time::{Duration, Instant}}; +use std::{ + env, + process::{Command, ExitCode, Stdio}, + thread, + time::{Duration, Instant}, +}; const LD_PARSE_MASTER: &str = "877667417d6cab57c04aed9cb3d5bfe7efa0589c"; const LD_PARSE_MODERNIZATION: &str = "0fe78dcee22982f26cceced1b483340faa4d04e9"; const PYTHON_VERSION: &str = "3.12.8"; #[derive(Debug, Clone, Serialize)] -struct HarnessReport { schema_version: &'static str, command: String, fixtures: usize, bytes: usize, gates: usize, profiles: usize, seeds: Vec, iterations: u32, adapter: AdapterEvidence, status: &'static str } +struct HarnessReport { + schema_version: &'static str, + command: String, + fixtures: usize, + bytes: usize, + gates: usize, + profiles: usize, + seeds: Vec, + iterations: u32, + adapter: AdapterEvidence, + status: &'static str, +} #[derive(Debug, Clone, Serialize)] -struct AdapterEvidence { kind: &'static str, interpreter: &'static str, master_revision: &'static str, modernization_revision: &'static str, network: bool, timeout_ms: u64 } +struct AdapterEvidence { + kind: &'static str, + interpreter: &'static str, + master_revision: &'static str, + modernization_revision: &'static str, + network: bool, + timeout_ms: u64, +} +#[derive(Debug, Clone, Deserialize)] +struct DriverOutput { + schema_version: String, + fixture: String, + #[serde(rename = "revision")] + revision: String, + sha256: String, + syntax: Vec, + triangles: u64, + lines: u64, + bfc_state: String, + steps: Vec, + diagnostics: Vec, +} -pub trait InProcessRustAdapter { fn identity(&self) -> &'static str; fn parse(&self, fixture: &FixtureCase) -> Result; } -pub trait OutOfProcessAdapter { fn identity(&self) -> &str; fn run(&self, fixture: &FixtureCase, timeout_ms: u64) -> Result, HarnessError>; } +pub trait InProcessRustAdapter { + fn identity(&self) -> &'static str; + fn parse(&self, fixture: &FixtureCase) -> Result; +} +pub trait OutOfProcessAdapter { + fn identity(&self) -> &str; + fn run(&self, fixture: &FixtureCase, timeout_ms: u64) -> Result, HarnessError>; +} #[derive(Debug, Clone)] -pub struct LDParseAdapter { pub revision: String, pub python: String, pub network: bool } +pub struct LDParseAdapter { + pub revision: String, + pub python: String, + pub network: bool, +} #[derive(Debug, thiserror::Error)] pub enum HarnessError { - #[error("wrong LDParse revision: {0}")] WrongRevision(String), - #[error("wrong interpreter: {0}")] WrongInterpreter(String), - #[error("network acquisition is forbidden")] NetworkEnabled, - #[error("adapter timed out after {0}ms")] Timeout(u64), - #[error("adapter crashed: {0}")] Crash(String), - #[error("unsupported LDParse surface: {0}")] UnsupportedSurface(String), - #[error("malformed driver output: {0}")] MalformedOutput(String), - #[error("driver failed with status {status}: {stderr}")] Exit { status: String, stderr: String }, - #[error("driver could not be started: {0}")] Spawn(String), + #[error("wrong LDParse revision: {0}")] + WrongRevision(String), + #[error("wrong interpreter: {0}")] + WrongInterpreter(String), + #[error("network acquisition is forbidden")] + NetworkEnabled, + #[error("adapter timed out after {0}ms")] + Timeout(u64), + #[error("adapter crashed: {0}")] + Crash(String), + #[error("unsupported LDParse surface: {0}")] + UnsupportedSurface(String), + #[error("malformed driver output: {0}")] + MalformedOutput(String), + #[error("driver failed with status {status}: {stderr}")] + Exit { status: String, stderr: String }, + #[error("driver could not be started: {0}")] + Spawn(String), } impl LDParseAdapter { fn validate(&self) -> Result<(), HarnessError> { - if self.revision != LD_PARSE_MASTER && self.revision != LD_PARSE_MODERNIZATION { return Err(HarnessError::WrongRevision(self.revision.clone())); } - if self.python != PYTHON_VERSION { return Err(HarnessError::WrongInterpreter(self.python.clone())); } - if self.network { return Err(HarnessError::NetworkEnabled); } + if self.revision != LD_PARSE_MASTER && self.revision != LD_PARSE_MODERNIZATION { + return Err(HarnessError::WrongRevision(self.revision.clone())); + } + if self.python != PYTHON_VERSION { + return Err(HarnessError::WrongInterpreter(self.python.clone())); + } + if self.network { + return Err(HarnessError::NetworkEnabled); + } Ok(()) } } impl OutOfProcessAdapter for LDParseAdapter { - fn identity(&self) -> &str { &self.revision } + fn identity(&self) -> &str { + &self.revision + } fn run(&self, fixture: &FixtureCase, timeout_ms: u64) -> Result, HarnessError> { self.validate()?; - if timeout_ms == 0 { return Err(HarnessError::Timeout(timeout_ms)); } - if fixture.tags.iter().any(|tag| *tag == "colours_and_ldconfig" || *tag == "path_and_resolution_independence" || *tag == "steps_and_transforms") { + if timeout_ms == 0 { + return Err(HarnessError::Timeout(timeout_ms)); + } + if fixture.tags.iter().any(|tag| { + *tag == "colours_and_ldconfig" + || *tag == "path_and_resolution_independence" + || *tag == "steps_and_transforms" + }) { return Err(HarnessError::UnsupportedSurface(fixture.id.into())); } let driver = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("ldparse_driver.py"); let mut child = Command::new("uv") .current_dir(env!("CARGO_MANIFEST_DIR")) - .args(["run", "--offline", "python", driver.to_str().unwrap(), "--revision", &self.revision, "--fixture", fixture.id, "--bytes"]) + .args([ + "run", + "--offline", + "python", + driver.to_str().unwrap(), + "--revision", + &self.revision, + "--fixture", + fixture.id, + "--bytes", + ]) .arg(String::from_utf8_lossy(fixture.bytes).as_ref()) - .stdout(Stdio::piped()).stderr(Stdio::piped()).spawn() + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() .map_err(|e| HarnessError::Spawn(e.to_string()))?; let deadline = Instant::now() + Duration::from_millis(timeout_ms); loop { - match child.try_wait().map_err(|e| HarnessError::Crash(e.to_string()))? { + match child + .try_wait() + .map_err(|e| HarnessError::Crash(e.to_string()))? + { Some(status) => { - let output = child.wait_with_output().map_err(|e| HarnessError::Crash(e.to_string()))?; - if !status.success() { return Err(HarnessError::Exit { status: status.to_string(), stderr: String::from_utf8_lossy(&output.stderr).into_owned() }); } - let value: serde_json::Value = serde_json::from_slice(&output.stdout).map_err(|e| HarnessError::MalformedOutput(e.to_string()))?; - if value["schema_version"] != SCHEMA_VERSION || value["fixture"] != fixture.id || value["revision"] != self.revision { return Err(HarnessError::MalformedOutput("driver identity/schema mismatch".into())); } + let output = child + .wait_with_output() + .map_err(|e| HarnessError::Crash(e.to_string()))?; + if !status.success() { + return Err(HarnessError::Exit { + status: status.to_string(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + let value: serde_json::Value = serde_json::from_slice(&output.stdout) + .map_err(|e| HarnessError::MalformedOutput(e.to_string()))?; + if value["schema_version"] != SCHEMA_VERSION + || value["fixture"] != fixture.id + || value["revision"] != self.revision + { + return Err(HarnessError::MalformedOutput( + "driver identity/schema mismatch".into(), + )); + } return Ok(output.stdout); } - None if Instant::now() >= deadline => { let _ = child.kill(); let _ = child.wait(); return Err(HarnessError::Timeout(timeout_ms)); } + None if Instant::now() >= deadline => { + let _ = child.kill(); + let _ = child.wait(); + return Err(HarnessError::Timeout(timeout_ms)); + } None => thread::sleep(Duration::from_millis(5)), } } @@ -80,33 +187,88 @@ fn main() -> ExitCode { "fuzz" => fuzz_report(), "differential" => differential_report(), "validate" => validate(), - other => Err(format!("unknown command {other}; use inventory, fuzz, differential, or validate")), + "dump-expected" => dump_expected(), + other => Err(format!( + "unknown command {other}; use inventory, fuzz, differential, or validate" + )), }; - match result { Ok(json) => { println!("{json}"); ExitCode::SUCCESS }, Err(error) => { eprintln!("ldraw-compat-harness: {error}"); ExitCode::FAILURE } } + match result { + Ok(json) => { + println!("{json}"); + ExitCode::SUCCESS + } + Err(error) => { + eprintln!("ldraw-compat-harness: {error}"); + ExitCode::FAILURE + } + } } fn corpus_report() -> Result { let fixtures = inventory(); validate_inventory(&fixtures)?; let coverage = coverage_report(&fixtures); - let report = HarnessReport { schema_version: SCHEMA_VERSION, command: "inventory".into(), fixtures: coverage.fixture_count, bytes: coverage.byte_count, gates: GATE_NAMES.len(), profiles: coverage.profiles.len(), seeds: Vec::new(), iterations: 0, adapter: evidence(), status: "pass" }; + let report = HarnessReport { + schema_version: SCHEMA_VERSION, + command: "inventory".into(), + fixtures: coverage.fixture_count, + bytes: coverage.byte_count, + gates: GATE_NAMES.len(), + profiles: coverage.profiles.len(), + seeds: Vec::new(), + iterations: 0, + adapter: evidence(), + status: "pass", + }; serde_json::to_string_pretty(&report).map_err(|e| e.to_string()) } fn validate() -> Result { let fixtures = inventory(); validate_inventory(&fixtures)?; - if LDRAW_LIMITS.iter().any(|(name, limit)| *limit == 0 || name.is_empty()) { return Err("invalid semantic limit".into()); } - let adapter = LDParseAdapter { revision: LD_PARSE_MASTER.into(), python: PYTHON_VERSION.into(), network: false }; + if LDRAW_LIMITS + .iter() + .any(|(name, limit)| *limit == 0 || name.is_empty()) + { + return Err("invalid semantic limit".into()); + } + let adapter = LDParseAdapter { + revision: LD_PARSE_MASTER.into(), + python: PYTHON_VERSION.into(), + network: false, + }; adapter.validate().map_err(|e| e.to_string())?; Ok("{\"status\":\"pass\",\"provenance\":\"validated\",\"pins\":\"validated\"}".into()) } +fn dump_expected() -> Result { + let mut output = serde_json::Map::new(); + for fixture in inventory() { + output.insert( + fixture.id.into(), + serde_json::to_value(&fixture.expected).map_err(|e| e.to_string())?, + ); + } + serde_json::to_string_pretty(&output).map_err(|e| e.to_string()) +} fn fuzz_report() -> Result { let fixtures = inventory(); validate_inventory(&fixtures)?; let seeds = [0xD0_u64, 0x77_u64, 0x86_u64]; let iterations = 128; - for seed in seeds { deterministic_smoke(seed, iterations, &fixtures)?; } - let report = HarnessReport { schema_version: SCHEMA_VERSION, command: "fuzz".into(), fixtures: fixtures.len(), bytes: fixtures.iter().map(|f| f.bytes.len()).sum(), gates: GATE_NAMES.len(), profiles: 3, seeds: seeds.to_vec(), iterations, adapter: evidence(), status: "pass" }; + for seed in seeds { + deterministic_smoke(seed, iterations, &fixtures)?; + } + let report = HarnessReport { + schema_version: SCHEMA_VERSION, + command: "fuzz".into(), + fixtures: fixtures.len(), + bytes: fixtures.iter().map(|f| f.bytes.len()).sum(), + gates: GATE_NAMES.len(), + profiles: 3, + seeds: seeds.to_vec(), + iterations, + adapter: evidence(), + status: "pass", + }; serde_json::to_string_pretty(&report).map_err(|e| e.to_string()) } fn deterministic_smoke(seed: u64, iterations: u32, fixtures: &[FixtureCase]) -> Result<(), String> { @@ -116,37 +278,290 @@ fn deterministic_smoke(seed: u64, iterations: u32, fixtures: &[FixtureCase]) -> let fixture = &fixtures[(state as usize) % fixtures.len()]; let mut mutated = fixture.bytes.to_vec(); match step % 6 { - 0 => { mutated.extend_from_slice(b" \r\n"); } - 1 => { mutated = mutated.iter().enumerate().map(|(i, b)| if i % 7 == 0 { b'\t' } else { *b }).collect(); } - 2 => { mutated.extend_from_slice(b"3 16 nan 0 0 1 0 0 0 0 1 0\n"); } - 3 => { mutated.extend_from_slice(b"0 FILE ../escape.ldr\n"); } - 4 => { mutated.extend_from_slice(b"0 FILE cycle.ldr\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 cycle.ldr\n0 NOFILE\n"); } - _ => { mutated.extend(std::iter::repeat_n(b'0', 1024)); } - } - if mutated.len() > 2 * 1024 * 1024 { return Err(format!("mutation budget exceeded at seed {seed}, iteration {step}")); } + 0 => { + mutated.extend_from_slice(b" \r\n"); + } + 1 => { + mutated = mutated + .iter() + .enumerate() + .map(|(i, b)| if i % 7 == 0 { b'\t' } else { *b }) + .collect(); + } + 2 => { + mutated.extend_from_slice(b"3 16 nan 0 0 1 0 0 0 0 1 0\n"); + } + 3 => { + mutated.extend_from_slice(b"0 FILE ../escape.ldr\n"); + } + 4 => { + mutated.extend_from_slice( + b"0 FILE cycle.ldr\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 cycle.ldr\n0 NOFILE\n", + ); + } + _ => { + mutated.extend(std::iter::repeat_n(b'0', 1024)); + } + } + if mutated.len() > 2 * 1024 * 1024 { + return Err(format!( + "mutation budget exceeded at seed {seed}, iteration {step}" + )); + } let digest = Sha256::digest(&mutated); - if hex_digest(&mutated) != digest.iter().map(|b| format!("{b:02x}")).collect::() { return Err(format!("digest instability at seed {seed}, iteration {step}")); } - if step % 6 == 3 && !mutated.windows(2).any(|w| w == b"..") { return Err(format!("path traversal mutation lost at seed {seed}, iteration {step}")); } + if hex_digest(&mutated) + != digest + .iter() + .map(|b| format!("{b:02x}")) + .collect::() + { + return Err(format!( + "digest instability at seed {seed}, iteration {step}" + )); + } + let parsed = std::panic::catch_unwind(|| { + polymodel_ldraw_testkit::canonical( + fixture.id, + &mutated, + fixture.tags[0], + fixture.authority, + ) + }) + .map_err(|_| format!("parser panic at seed {seed}, iteration {step}"))?; + let repeated = polymodel_ldraw_testkit::canonical( + fixture.id, + &mutated, + fixture.tags[0], + fixture.authority, + ); + if parsed != repeated { + return Err(format!( + "parser nondeterminism at seed {seed}, iteration {step}" + )); + } + if step % 6 == 3 && !mutated.windows(2).any(|w| w == b"..") { + return Err(format!( + "path traversal mutation lost at seed {seed}, iteration {step}" + )); + } } Ok(()) } +fn driver_record(output: DriverOutput, fixture: &FixtureCase) -> Result { + if output.schema_version != SCHEMA_VERSION + || output.fixture != fixture.id + || output.revision != LD_PARSE_MASTER + { + return Err(format!( + "driver identity/schema mismatch for {}", + fixture.id + )); + } + if output.sha256 != fixture.sha256() { + return Err(format!("driver digest mismatch for {}", fixture.id)); + } + let text = String::from_utf8_lossy(fixture.bytes); + let bounds = geometry_bounds(&output.syntax); + Ok(CanonicalRecord { + schema_version: output.schema_version, + syntax: output.syntax, + semantic: polymodel_ldraw_testkit::SemanticRecord { + canonical_path: text + .lines() + .find_map(|line| line.strip_prefix("0 FILE ").map(|x| x.trim().to_owned())), + root_identity: None, + target_identity: None, + cache_identity: None, + bfc_state: output.bfc_state, + colour_state: "unobserved".into(), + steps: output.steps, + limits: Vec::new(), + }, + scene: polymodel_ldraw_testkit::SceneRecord { + model_id: fixture.id.into(), + instance_ids: Vec::new(), + triangles: output.triangles, + lines: output.lines, + bounds, + reflection: false, + }, + diagnostics: output.diagnostics, + provenance_id: fixture.expected.canonical.provenance_id.clone(), + }) +} +fn geometry_bounds(syntax: &[polymodel_ldraw_testkit::SyntaxRecord]) -> [String; 6] { + let mut bounds = [ + f64::INFINITY, + f64::INFINITY, + f64::INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, + ]; + for record in syntax + .iter() + .filter(|record| (2..=5).contains(&record.line_type)) + { + for field in record.fields.iter().skip(2) { + if let Ok(value) = field.parse::() { + if value.is_finite() { + bounds[0] = bounds[0].min(value); + bounds[1] = bounds[1].min(value); + bounds[2] = bounds[2].min(value); + bounds[3] = bounds[3].max(value); + bounds[4] = bounds[4].max(value); + bounds[5] = bounds[5].max(value); + } + } + } + } + if bounds[0].is_finite() { + bounds.map(|value| { + if value == 0.0 { + "0".into() + } else { + value.to_string() + } + }) + } else { + [ + "0".into(), + "0".into(), + "0".into(), + "0".into(), + "0".into(), + "0".into(), + ] + } +} +fn compare_records( + expected: &CanonicalRecord, + actual: &CanonicalRecord, + fixture: &str, +) -> Result<(), String> { + let checks = [ + (expected.syntax == actual.syntax, "syntax"), + ( + expected.semantic.bfc_state == actual.semantic.bfc_state, + "bfc", + ), + (expected.semantic.steps == actual.semantic.steps, "steps"), + ( + expected.scene.triangles == actual.scene.triangles + && expected.scene.lines == actual.scene.lines + && expected.scene.bounds == actual.scene.bounds, + "geometry", + ), + (expected.diagnostics == actual.diagnostics, "diagnostics"), + ]; + let mismatches = checks + .iter() + .filter_map(|(matches, field)| (!matches).then_some(*field)) + .collect::>(); + if mismatches.is_empty() { + Ok(()) + } else { + Err(format!( + "LDParse mismatch for {fixture}: {}", + mismatches.join(", ") + )) + } +} fn differential_report() -> Result { let fixtures = inventory(); validate_inventory(&fixtures)?; - let adapter = LDParseAdapter { revision: LD_PARSE_MASTER.into(), python: PYTHON_VERSION.into(), network: false }; + let adapter = LDParseAdapter { + revision: LD_PARSE_MASTER.into(), + python: PYTHON_VERSION.into(), + network: false, + }; let mut exercised = 0; let mut unsupported = Vec::new(); - for fixture in &fixtures { match adapter.run(fixture, 2_000) { Ok(bytes) => { serde_json::from_slice::(&bytes).map_err(|e| e.to_string())?; exercised += 1; }, Err(HarnessError::UnsupportedSurface(_)) => unsupported.push(fixture.id), Err(e) => return Err(e.to_string()) } } + for fixture in &fixtures { + match adapter.run(fixture, 10_000) { + Ok(bytes) => { + let output: DriverOutput = serde_json::from_slice(&bytes) + .map_err(|e| format!("malformed driver output for {}: {e}", fixture.id))?; + let actual = driver_record(output, fixture)?; + compare_records(&fixture.expected.canonical, &actual, fixture.id)?; + exercised += 1; + } + Err(HarnessError::UnsupportedSurface(_)) => unsupported.push(fixture.id), + Err(e) => return Err(e.to_string()), + } + } let value = serde_json::json!({"status":"pass","schema_version":SCHEMA_VERSION,"adapter":evidence(),"exercised":exercised,"unsupported_by_ldparse":unsupported,"required_surface":["types_0_to_5","raw_tails_spans","mpd_file_nofile","graph_identity","bfc_invertnext","steps","finite_numeric_diagnostics"],"ledger_rows":["LD-OUTSIDE-TEXMAP","LD-OUTSIDE-CONDITIONAL","LD-OUTSIDE-COLOUR-RESOLUTION"]}); serde_json::to_string_pretty(&value).map_err(|e| e.to_string()) } -fn evidence() -> AdapterEvidence { AdapterEvidence { kind: "out-of-process:ldparse-driver", interpreter: PYTHON_VERSION, master_revision: LD_PARSE_MASTER, modernization_revision: LD_PARSE_MODERNIZATION, network: false, timeout_ms: 2_000 } } +fn evidence() -> AdapterEvidence { + AdapterEvidence { + kind: "out-of-process:ldparse-driver", + interpreter: PYTHON_VERSION, + master_revision: LD_PARSE_MASTER, + modernization_revision: LD_PARSE_MODERNIZATION, + network: false, + timeout_ms: 2_000, + } +} #[cfg(test)] mod tests { use super::*; - #[test] fn pins_reject_drift() { let bad = LDParseAdapter { revision: "floating".into(), python: PYTHON_VERSION.into(), network: false }; assert!(matches!(bad.validate(), Err(HarnessError::WrongRevision(_)))); } - #[test] fn interpreter_and_network_are_validated() { let bad = LDParseAdapter { revision: LD_PARSE_MASTER.into(), python: "3.13".into(), network: true }; assert!(matches!(bad.validate(), Err(HarnessError::WrongInterpreter(_)))); } - #[test] fn timeout_is_killable_boundary() { let adapter = LDParseAdapter { revision: LD_PARSE_MASTER.into(), python: PYTHON_VERSION.into(), network: false }; let case = inventory().remove(0); assert!(matches!(adapter.run(&case, 0), Err(HarnessError::Timeout(0)))); } - #[test] fn differential_driver_output_is_machine_readable() { let adapter = LDParseAdapter { revision: LD_PARSE_MASTER.into(), python: PYTHON_VERSION.into(), network: false }; let case = inventory().remove(0); match adapter.run(&case, 2_000) { Ok(bytes) => assert_eq!(serde_json::from_slice::(&bytes).unwrap()["fixture"], "syntax-types"), Err(HarnessError::Exit { stderr, .. }) => assert!(stderr.contains("wrong interpreter") || stderr.contains("No virtual environment")), Err(HarnessError::Spawn(error)) => panic!("unexpected driver spawn failure: {error}"), Err(error) => panic!("unexpected adapter failure: {error}"), } } + #[test] + fn pins_reject_drift() { + let bad = LDParseAdapter { + revision: "floating".into(), + python: PYTHON_VERSION.into(), + network: false, + }; + assert!(matches!( + bad.validate(), + Err(HarnessError::WrongRevision(_)) + )); + } + #[test] + fn interpreter_and_network_are_validated() { + let bad = LDParseAdapter { + revision: LD_PARSE_MASTER.into(), + python: "3.13".into(), + network: true, + }; + assert!(matches!( + bad.validate(), + Err(HarnessError::WrongInterpreter(_)) + )); + } + #[test] + fn timeout_is_killable_boundary() { + let adapter = LDParseAdapter { + revision: LD_PARSE_MASTER.into(), + python: PYTHON_VERSION.into(), + network: false, + }; + let case = inventory().remove(0); + assert!(matches!( + adapter.run(&case, 0), + Err(HarnessError::Timeout(0)) + )); + } + #[test] + fn differential_driver_output_is_machine_readable() { + let adapter = LDParseAdapter { + revision: LD_PARSE_MASTER.into(), + python: PYTHON_VERSION.into(), + network: false, + }; + let case = inventory().remove(0); + match adapter.run(&case, 10_000) { + Ok(bytes) => assert_eq!( + serde_json::from_slice::(&bytes).unwrap()["fixture"], + "syntax-types" + ), + Err(HarnessError::Exit { stderr, .. }) => assert!( + stderr.contains("wrong interpreter") || stderr.contains("No virtual environment") + ), + Err(HarnessError::Spawn(error)) => panic!("unexpected driver spawn failure: {error}"), + Err(error) => panic!("unexpected adapter failure: {error}"), + } + } }