From 1444e0c4f78eefa444a8fac56e38b5f1d1012eec Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 12 Oct 2026 04:52:44 +0200 Subject: [PATCH] thing-contract: blessed goldens (surface with all 13 arms, composition, invocation, one result per outcome and effect arm, asset catalog, surface.cc, refusal and tolerance payloads); the 64-deep chain runs on a 16 MiB thread Blessed on Yggdrasil (Linux, debug). deep_tree's decode overflows a 2 MiB stack at depth 32. Co-Authored-By: Claude Sonnet 5.5 --- crates/thing-contract/tests/goldens.rs | 48 +++++++++++++----- .../tests/goldens/asset-catalog.bin | Bin 0 -> 92 bytes .../tests/goldens/composition-all-params.bin | Bin 0 -> 812 bytes .../tests/goldens/invocation-all-inputs.bin | Bin 0 -> 211 bytes .../tests/goldens/node-unknown-arm.bin | 1 + .../tests/goldens/outcome-unknown-arm.bin | 1 + .../result-completed-open-external.bin | Bin 0 -> 84 bytes .../goldens/result-completed-open-surface.bin | Bin 0 -> 78 bytes .../tests/goldens/result-completed.bin | 1 + .../tests/goldens/result-denied-no-node.bin | 1 + .../tests/goldens/result-denied.bin | 1 + .../tests/goldens/result-extra-slot.bin | Bin 0 -> 35 bytes .../tests/goldens/result-failed.bin | 1 + .../tests/goldens/result-missing-slot.bin | 1 + .../tests/goldens/result-pending.bin | Bin 0 -> 51 bytes .../tests/goldens/result-thing1-flat.bin | 1 + .../tests/goldens/surface-all-arms.bin | Bin 0 -> 4677 bytes .../thing-contract/tests/goldens/surface.cc | Bin 0 -> 7635 bytes 18 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 crates/thing-contract/tests/goldens/asset-catalog.bin create mode 100644 crates/thing-contract/tests/goldens/composition-all-params.bin create mode 100644 crates/thing-contract/tests/goldens/invocation-all-inputs.bin create mode 100644 crates/thing-contract/tests/goldens/node-unknown-arm.bin create mode 100644 crates/thing-contract/tests/goldens/outcome-unknown-arm.bin create mode 100644 crates/thing-contract/tests/goldens/result-completed-open-external.bin create mode 100644 crates/thing-contract/tests/goldens/result-completed-open-surface.bin create mode 100644 crates/thing-contract/tests/goldens/result-completed.bin create mode 100644 crates/thing-contract/tests/goldens/result-denied-no-node.bin create mode 100644 crates/thing-contract/tests/goldens/result-denied.bin create mode 100644 crates/thing-contract/tests/goldens/result-extra-slot.bin create mode 100644 crates/thing-contract/tests/goldens/result-failed.bin create mode 100644 crates/thing-contract/tests/goldens/result-missing-slot.bin create mode 100644 crates/thing-contract/tests/goldens/result-pending.bin create mode 100644 crates/thing-contract/tests/goldens/result-thing1-flat.bin create mode 100644 crates/thing-contract/tests/goldens/surface-all-arms.bin create mode 100644 crates/thing-contract/tests/goldens/surface.cc diff --git a/crates/thing-contract/tests/goldens.rs b/crates/thing-contract/tests/goldens.rs index cf6928c..41b8a7f 100644 --- a/crates/thing-contract/tests/goldens.rs +++ b/crates/thing-contract/tests/goldens.rs @@ -565,12 +565,12 @@ fn refusal_cases() { let payloads: std::collections::BTreeMap<_, _> = refusal_payloads().into_iter().collect(); let committed = |name: &str| golden_bytes(&format!("{name}.bin"), &payloads[name]); - let cases: [(&str, &str, &str, &str); 3] = [ - ("result-thing1-flat", "gamecult.thing.command_result", "thing:result:inv-1.1", "CommandResult"), - ("node-unknown-arm", "gamecult.thing.surface", "thing:surface:provider.a.main", "Node"), - ("outcome-unknown-arm", "gamecult.thing.command_result", "thing:result:inv-1.1", "Outcome"), + let cases: [(&str, &str, &str); 3] = [ + ("result-thing1-flat", "gamecult.thing.command_result", "thing:result:inv-1.1"), + ("node-unknown-arm", "gamecult.thing.surface", "thing:surface:provider.a.main"), + ("outcome-unknown-arm", "gamecult.thing.command_result", "thing:result:inv-1.1"), ]; - for (name, kind, key, union) in cases { + for (name, kind, key) in cases { let mut cache = CultCache::new(); ThingContract.register_entries(&mut cache).unwrap(); let bytes = committed(name); @@ -582,25 +582,45 @@ fn refusal_cases() { let message = format!("{error:#}"); assert!(message.contains(kind), "{name}: the error names the kind: {message}"); assert!(message.contains(key), "{name}: the error names the record key: {message}"); - assert!(message.contains(union) || name == "result-thing1-flat", "{name}: {message}"); assert!(!message.contains(CANARY), "{name}: the error echoed a value: {message}"); } - // The unknown arm key is named; the payload never is. + // Decoded directly, the refusal names the union and the arm key, and never the payload. + for (name, union, key) in [("node-unknown-arm", "Node", "13"), ("outcome-unknown-arm", "Outcome", "4")] { + let bytes = committed(name); + let message = if union == "Node" { + rmp_serde::from_slice::(&bytes).unwrap_err().to_string() + } else { + rmp_serde::from_slice::(&bytes).unwrap_err().to_string() + }; + assert!(message.contains(union) && message.contains(key), "{name}: {message}"); + assert!(!message.contains(CANARY), "{name}: {message}"); + } + + // An extra trailing slot is read (CultLib's extra-trailing-slots ruling); a missing slot is not. + let mut answered = invocation(); + answered.invocation_id = "inv-1".into(); + let extra: CommandResult = rmp_serde::from_slice(&committed("result-extra-slot")).unwrap(); + assert_eq!(extra, CommandResult::pending(&answered, 1, "working")); + let missing = rmp_serde::from_slice::(&committed("result-missing-slot")).unwrap_err(); + println!("missing slot: {missing}"); let mut cache = CultCache::new(); ThingContract.register_entries(&mut cache).unwrap(); - let node = cache - .put_envelope::(envelope::("k", committed("node-unknown-arm"))) + let missing = cache + .put_envelope::(envelope::("thing:result:inv-1.1", committed("result-missing-slot"))) .unwrap_err(); - assert!(format!("{node:#}").contains("13"), "{node:#}"); - let outcome = cache - .put_envelope::(envelope::("k", committed("outcome-unknown-arm"))) - .unwrap_err(); - assert!(format!("{outcome:#}").contains('4'), "{outcome:#}"); + let message = format!("{missing:#}"); + assert!(message.contains("gamecult.thing.command_result") && message.contains("thing:result:inv-1.1"), "{message}"); } #[test] fn deep_tree() { + // Decoding a Node costs tens of KiB of debug stack per nesting level, so the chain runs on a + // thread that has the room; the 2 MiB default does not. + std::thread::Builder::new().stack_size(16 * 1024 * 1024).spawn(deep_tree_body).unwrap().join().unwrap(); +} + +fn deep_tree_body() { // A 64-deep Box chain. let mut chain = text("leaf", "bottom"); for depth in (0..64).rev() { diff --git a/crates/thing-contract/tests/goldens/asset-catalog.bin b/crates/thing-contract/tests/goldens/asset-catalog.bin new file mode 100644 index 0000000000000000000000000000000000000000..97943369f4333b3c4b893451fc62f016a3a68b33 GIT binary patch literal 92 zcmbQTs-P&pEHfpwNH1||W^#UBG1H`}OVU#li&kdlCZ?zA7v!a{Pb@A@Eh*LqiRuCQ f3JOz~B<1Intc9p3E=$+0$jyPN0?FtB#TXa>7=CI8VhQ3R&t0)jZ%V_BK$-NX86^f{KcqOXw-s2@ZV)`%L61 zW#B0&Q}L9+K*4K3B@8h!72z|C6p*z(TGlufLINq8`kUMs9)j?TnbZ9>v5$>!UA(df z7n8rlD>Fy;LJzSIuHDRK2rbOgy0~p2>UTd*hFxyTp*r22UBiXpDW!B>UlK-3??rz& zvF+ASE#AD9b{$Rq1y@>E5$7z$bW`hte2n<^c`8UO!|vSh5n2uMPb0p8je)NF{3gDMQH&p+3*I<+{YlPcFUEb4iBzQTj`rrh>j4GPP)YX+;a z7$)v5 h$;ix0w<<0zN=r;mwJIpeFUw3xEz(QW%S){&0RZps8$|#B literal 0 HcmV?d00001 diff --git a/crates/thing-contract/tests/goldens/result-completed.bin b/crates/thing-contract/tests/goldens/result-completed.bin new file mode 100644 index 0000000..1f92a8f --- /dev/null +++ b/crates/thing-contract/tests/goldens/result-completed.bin @@ -0,0 +1 @@ +“Ù$3f2b8c1e-5d44-4a6b-9a07-1c2d3e4f5a6b’“ÃÀ \ No newline at end of file diff --git a/crates/thing-contract/tests/goldens/result-denied-no-node.bin b/crates/thing-contract/tests/goldens/result-denied-no-node.bin new file mode 100644 index 0000000..64425b4 --- /dev/null +++ b/crates/thing-contract/tests/goldens/result-denied-no-node.bin @@ -0,0 +1 @@ +“Ù$3f2b8c1e-5d44-4a6b-9a07-1c2d3e4f5a6b’’«not allowedÀ \ No newline at end of file diff --git a/crates/thing-contract/tests/goldens/result-denied.bin b/crates/thing-contract/tests/goldens/result-denied.bin new file mode 100644 index 0000000..8d03fc3 --- /dev/null +++ b/crates/thing-contract/tests/goldens/result-denied.bin @@ -0,0 +1 @@ +“Ù$3f2b8c1e-5d44-4a6b-9a07-1c2d3e4f5a6b’’©not yours¢go \ No newline at end of file diff --git a/crates/thing-contract/tests/goldens/result-extra-slot.bin b/crates/thing-contract/tests/goldens/result-extra-slot.bin new file mode 100644 index 0000000000000000000000000000000000000000..29a7ef8a8064d598f36b3e6241a26537d898c3d9 GIT binary patch literal 35 qcmbQTG&8SE*N|}%!^Gv~`9;~8dFkt&9sL}GB6ZEvj1w&llZ^oMTMT;u literal 0 HcmV?d00001 diff --git a/crates/thing-contract/tests/goldens/result-failed.bin b/crates/thing-contract/tests/goldens/result-failed.bin new file mode 100644 index 0000000..387cae8 --- /dev/null +++ b/crates/thing-contract/tests/goldens/result-failed.bin @@ -0,0 +1 @@ +“Ù$3f2b8c1e-5d44-4a6b-9a07-1c2d3e4f5a6b’‘¥broke \ No newline at end of file diff --git a/crates/thing-contract/tests/goldens/result-missing-slot.bin b/crates/thing-contract/tests/goldens/result-missing-slot.bin new file mode 100644 index 0000000..60a1739 --- /dev/null +++ b/crates/thing-contract/tests/goldens/result-missing-slot.bin @@ -0,0 +1 @@ +’¥inv-1 \ No newline at end of file diff --git a/crates/thing-contract/tests/goldens/result-pending.bin b/crates/thing-contract/tests/goldens/result-pending.bin new file mode 100644 index 0000000000000000000000000000000000000000..21a9198b6bd8d06b49968a3624039782501e5fb8 GIT binary patch literal 51 zcmbPyQ^h#VD9Iw(Fjdzy#l%F{B+)EM*D}$-T-Pw!D8)F{B+V2k!Z?Xx;_~wRqU_AP FbO4jp5GViu literal 0 HcmV?d00001 diff --git a/crates/thing-contract/tests/goldens/result-thing1-flat.bin b/crates/thing-contract/tests/goldens/result-thing1-flat.bin new file mode 100644 index 0000000..18bf70e --- /dev/null +++ b/crates/thing-contract/tests/goldens/result-thing1-flat.bin @@ -0,0 +1 @@ +�§receipt†©commandId¥inv-1§command¢go¥state©completed§message¯CANARY-7f3a91c2­sourceVersion«issuedAtUtc´2026-01-01T00:00:00Z \ No newline at end of file diff --git a/crates/thing-contract/tests/goldens/surface-all-arms.bin b/crates/thing-contract/tests/goldens/surface-all-arms.bin new file mode 100644 index 0000000000000000000000000000000000000000..8c3b08439d6b6b863a2016a4c251dcefe9e76b18 GIT binary patch literal 4677 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 BT*LqX literal 0 HcmV?d00001