diff --git a/proof/SPEC.md b/proof/SPEC.md index 24925c2ea..d95bcfda0 100644 --- a/proof/SPEC.md +++ b/proof/SPEC.md @@ -158,7 +158,9 @@ verdict was earned. whether the **closing** solver was a stand-in. A cache hit that dropped the marker would launder an unearned proof into an unmarked one. - A relying party that will act on a proof **MUST** check it. Verifying the - envelope establishes the signature, not that anything was proved. + envelope establishes the signature, not that anything was proved. The marker + therefore rides in the signed receipt as `closed_by_stub` (§6.1), so a party + holding only the artifact can make that check without trusting the prover. - A stand-in **MUST NOT** be presented as a decision procedure in any published figure or claim derived from a run. @@ -187,6 +189,8 @@ baseline_uj = uint ; frontier-only cost of this goal energy_provenance = "HwShunt" | "ModelBased" | "Estimator" spec_grounding = "Ungrounded" | "HumanReviewed" | "CrossChecked" from_cache = 0 | 1 +closed_by_stub = 1 ; omitted when the closer was real (§5.2) +ceiling_uj = uint ; omitted when the run was unbounded steward = the signer's id (the COSE kid) ``` @@ -196,6 +200,30 @@ says, in the signed artifact itself, that the statement was not validated against intent. Proof receipts are byte-compatible with the family's JCR-1 verifiers. +### 6.1 `closed_by_stub` — the verdict's own qualifier + +An implementation MUST emit `closed_by_stub = 1` when the rung that closed the +goal was a reference stand-in (§5.2), and MUST omit the field otherwise. + +§5.2 already requires a relying party to check this before acting on a proof. +Until it entered the signed core the marker lived only on the in-process result, +so a party holding **only the receipt** — which is the entire purpose of sealing +one — could not perform the check the specification asks of them. A verifier +that must consult the prover to learn whether a proof is real is not a verifier. + +`closed_by` does not answer this. It names a **tier**, and the same tag covers a +real decision procedure on one ladder and a stand-in on another; `T0` is a +propositional decider in a real ladder and a `GoalKind` gate in the reference +one. Nor do the other qualifiers: `energy_provenance` qualifies the *energy* and +`spec_grounding` the *intent*. This is the only field that says whether the +**verdict** was earned. + +Omit-when-false is normative, not an optimization. It makes the field free for +honest proofs: a receipt closed by a real solver canonicalizes to exactly the +bytes it did before this field existed, so every such receipt sealed under an +earlier revision still verifies. The byte cost falls only on receipts that were +making an unearned claim. + ## 7. The cache (RECOMMENDED) A verified proof SHOULD be memoized by goal id. An identical goal then closes for diff --git a/proof/conformance/v1/receipts.jsonl b/proof/conformance/v1/receipts.jsonl index 9c4cebeb6..ef1d34d9a 100644 --- a/proof/conformance/v1/receipts.jsonl +++ b/proof/conformance/v1/receipts.jsonl @@ -1,4 +1,4 @@ -{"closed_by":"T0","envelope_hex":"d2845821a2012704581b6469643a6578616d706c653a70726f6f662d7265666572656e6365a0590156ac67676f616c5f69647840653161316333326461653066323564323565316134316266323432343535613139303837343130646437636339643138646230303266386336643839393735386773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403761313530353464313962343730363932663866303366666561653839653863343431353235363263303464323937336435373334343666353937323435613968746f74616c5f756a183269636c6f7365645f6279625430697761737465645f756a006a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f725840fbaa829eb27e79033ed6d80c5397972e09f4656f954565de7a6853773e0bbd28959e1a7b221e447ef56e60e6124161500ff03027f00403763bc689434ced930d","goal":"x = x","signed_core_hex":"ac67676f616c5f69647840653161316333326461653066323564323565316134316266323432343535613139303837343130646437636339643138646230303266386336643839393735386773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403761313530353464313962343730363932663866303366666561653839653863343431353235363263303464323937336435373334343666353937323435613968746f74616c5f756a183269636c6f7365645f6279625430697761737465645f756a006a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f72","total_uj":50,"wire":{"artifact":"7a15054d19b470692f8f03ffeae89e8c44152562c04d2973d573446f597245a9","baseline_uj":50000000,"closed_by":"T0","energy_provenance":"Estimator","from_cache":0,"goal_id":"e1a1c32dae0f25d25e1a41bf242455a19087410dd7cc9d18db002f8c6d899758","proof_receipt":"proof/1","spec_grounding":"Ungrounded","steward":"did:example:proof-reference","total_uj":50,"verdict":"proved","wasted_uj":0}} -{"closed_by":"T1","envelope_hex":"d2845821a2012704581b6469643a6578616d706c653a70726f6f662d7265666572656e6365a0590158ac67676f616c5f69647840346566363262656464333866613437616230643761393631383462663465353339373233383037336539353839643136653062666364316636396666363832646773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403462346430323937393136363538376231363334306364383463613338663565636434623261306636383937346435643938636262666539313730653363326268746f74616c5f756a1913ba69636c6f7365645f6279625431697761737465645f756a18326a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f7258401a1239d8c7cb9628071251bebb7609fcdc6c7053841f5fb70157ba9288851af6a3c79c8085a6c18d735695ab297069692ee75eecf7039e7a0f6a75ee02922803","goal":"decidable claim","signed_core_hex":"ac67676f616c5f69647840346566363262656464333866613437616230643761393631383462663465353339373233383037336539353839643136653062666364316636396666363832646773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403462346430323937393136363538376231363334306364383463613338663565636434623261306636383937346435643938636262666539313730653363326268746f74616c5f756a1913ba69636c6f7365645f6279625431697761737465645f756a18326a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f72","total_uj":5050,"wire":{"artifact":"4b4d02979166587b16340cd84ca38f5ecd4b2a0f68974d5d98cbbfe9170e3c2b","baseline_uj":50000000,"closed_by":"T1","energy_provenance":"Estimator","from_cache":0,"goal_id":"4ef62bedd38fa47ab0d7a96184bf4e5397238073e9589d16e0bfcd1f69ff682d","proof_receipt":"proof/1","spec_grounding":"Ungrounded","steward":"did:example:proof-reference","total_uj":5050,"verdict":"proved","wasted_uj":50}} -{"closed_by":"T2","envelope_hex":"d2845821a2012704581b6469643a6578616d706c653a70726f6f662d7265666572656e6365a059015bac67676f616c5f69647840353061373034313263326635643239306263386236336331663233376534393335383633626436306638643535613331333966313231363930373563653230656773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403832656537633362646430383431316637373531386566646138373730663231323261633638323063626536366135643732333865363936383034643730313568746f74616c5f756a1a0007b4da69636c6f7365645f6279625432697761737465645f756a1913ba6a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f72584093aa5fc768f3760f2650c6cb4bc5867d52790f107e3010457184f11536f00cbcf7f9f9ec017c04bbdc5406cc6c944040a8ebba518252b86acc171199cf04450f","goal":"needs search","signed_core_hex":"ac67676f616c5f69647840353061373034313263326635643239306263386236336331663233376534393335383633626436306638643535613331333966313231363930373563653230656773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403832656537633362646430383431316637373531386566646138373730663231323261633638323063626536366135643732333865363936383034643730313568746f74616c5f756a1a0007b4da69636c6f7365645f6279625432697761737465645f756a1913ba6a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f72","total_uj":505050,"wire":{"artifact":"82ee7c3bdd08411f77518efda8770f2122ac6820cbe66a5d7238e696804d7015","baseline_uj":50000000,"closed_by":"T2","energy_provenance":"Estimator","from_cache":0,"goal_id":"50a70412c2f5d290bc8b63c1f237e4935863bd60f8d55a3139f12169075ce20e","proof_receipt":"proof/1","spec_grounding":"Ungrounded","steward":"did:example:proof-reference","total_uj":505050,"verdict":"proved","wasted_uj":5050}} -{"closed_by":"T3","envelope_hex":"d2845821a2012704581b6469643a6578616d706c653a70726f6f662d7265666572656e6365a059015dac67676f616c5f69647840343266633262643632363965383032613639323236636134366562653962626230373462653930306164353035306135613564303466323231643165633864356773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478406165323862643362653339316438386664643231303331356337623430303666646262303862616165623431323836396464633536323962343630326531343768746f74616c5f756a1a0302a55a69636c6f7365645f6279625433697761737465645f756a1a0007b4da6a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f7258405a23963b4cee0433a518915ee74a799f72214cd2f325b82586502fac5dff194d47ce1c0093e6797652d2b36f02f4b63fa657ba5fcec926f02754b00c63ad6605","goal":"open conjecture","signed_core_hex":"ac67676f616c5f69647840343266633262643632363965383032613639323236636134366562653962626230373462653930306164353035306135613564303466323231643165633864356773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478406165323862643362653339316438386664643231303331356337623430303666646262303862616165623431323836396464633536323962343630326531343768746f74616c5f756a1a0302a55a69636c6f7365645f6279625433697761737465645f756a1a0007b4da6a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f72","total_uj":50505050,"wire":{"artifact":"ae28bd3be391d88fdd210315c7b4006fdbb08baaeb412869ddc5629b4602e147","baseline_uj":50000000,"closed_by":"T3","energy_provenance":"Estimator","from_cache":0,"goal_id":"42fc2bd6269e802a69226ca46ebe9bbb074be900ad5050a5a5d04f221d1ec8d5","proof_receipt":"proof/1","spec_grounding":"Ungrounded","steward":"did:example:proof-reference","total_uj":50505050,"verdict":"proved","wasted_uj":505050}} +{"closed_by":"T0","envelope_hex":"d2845821a2012704581b6469643a6578616d706c653a70726f6f662d7265666572656e6365a0590166ad67676f616c5f69647840653161316333326461653066323564323565316134316266323432343535613139303837343130646437636339643138646230303266386336643839393735386773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403761313530353464313962343730363932663866303366666561653839653863343431353235363263303464323937336435373334343666353937323435613968746f74616c5f756a183269636c6f7365645f6279625430697761737465645f756a006a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e636c6f7365645f62795f73747562016e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f7258409b4cd2baa6da5b230e1fb6323fd41e95e6e13f685fb57820735c8ae76ee7e4448514c4b98d1f2557028de5ecd31b3b015828de1287764db1f1f06abae65b7201","goal":"x = x","signed_core_hex":"ad67676f616c5f69647840653161316333326461653066323564323565316134316266323432343535613139303837343130646437636339643138646230303266386336643839393735386773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403761313530353464313962343730363932663866303366666561653839653863343431353235363263303464323937336435373334343666353937323435613968746f74616c5f756a183269636c6f7365645f6279625430697761737465645f756a006a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e636c6f7365645f62795f73747562016e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f72","total_uj":50,"wire":{"artifact":"7a15054d19b470692f8f03ffeae89e8c44152562c04d2973d573446f597245a9","baseline_uj":50000000,"closed_by":"T0","closed_by_stub":1,"energy_provenance":"Estimator","from_cache":0,"goal_id":"e1a1c32dae0f25d25e1a41bf242455a19087410dd7cc9d18db002f8c6d899758","proof_receipt":"proof/1","spec_grounding":"Ungrounded","steward":"did:example:proof-reference","total_uj":50,"verdict":"proved","wasted_uj":0}} +{"closed_by":"T1","envelope_hex":"d2845821a2012704581b6469643a6578616d706c653a70726f6f662d7265666572656e6365a0590168ad67676f616c5f69647840346566363262656464333866613437616230643761393631383462663465353339373233383037336539353839643136653062666364316636396666363832646773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403462346430323937393136363538376231363334306364383463613338663565636434623261306636383937346435643938636262666539313730653363326268746f74616c5f756a1913ba69636c6f7365645f6279625431697761737465645f756a18326a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e636c6f7365645f62795f73747562016e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f725840359ae6346d7f62f143539b60b32de36efa46cdfcf2108deeb20881196e07108134f5542e742ae4c7f3bc04bf9eb530efa38084d532718dd0e0213a52f7140209","goal":"decidable claim","signed_core_hex":"ad67676f616c5f69647840346566363262656464333866613437616230643761393631383462663465353339373233383037336539353839643136653062666364316636396666363832646773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403462346430323937393136363538376231363334306364383463613338663565636434623261306636383937346435643938636262666539313730653363326268746f74616c5f756a1913ba69636c6f7365645f6279625431697761737465645f756a18326a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e636c6f7365645f62795f73747562016e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f72","total_uj":5050,"wire":{"artifact":"4b4d02979166587b16340cd84ca38f5ecd4b2a0f68974d5d98cbbfe9170e3c2b","baseline_uj":50000000,"closed_by":"T1","closed_by_stub":1,"energy_provenance":"Estimator","from_cache":0,"goal_id":"4ef62bedd38fa47ab0d7a96184bf4e5397238073e9589d16e0bfcd1f69ff682d","proof_receipt":"proof/1","spec_grounding":"Ungrounded","steward":"did:example:proof-reference","total_uj":5050,"verdict":"proved","wasted_uj":50}} +{"closed_by":"T2","envelope_hex":"d2845821a2012704581b6469643a6578616d706c653a70726f6f662d7265666572656e6365a059016bad67676f616c5f69647840353061373034313263326635643239306263386236336331663233376534393335383633626436306638643535613331333966313231363930373563653230656773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403832656537633362646430383431316637373531386566646138373730663231323261633638323063626536366135643732333865363936383034643730313568746f74616c5f756a1a0007b4da69636c6f7365645f6279625432697761737465645f756a1913ba6a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e636c6f7365645f62795f73747562016e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f7258402452325ae2d140857a866374c30b5ad5dce1d39083cb42795b7e6ef20c4786c7d57c3d4b80cabd424828516ad032aa2e62ce8834289a945f12241dfb99781d0f","goal":"needs search","signed_core_hex":"ad67676f616c5f69647840353061373034313263326635643239306263386236336331663233376534393335383633626436306638643535613331333966313231363930373563653230656773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478403832656537633362646430383431316637373531386566646138373730663231323261633638323063626536366135643732333865363936383034643730313568746f74616c5f756a1a0007b4da69636c6f7365645f6279625432697761737465645f756a1913ba6a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e636c6f7365645f62795f73747562016e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f72","total_uj":505050,"wire":{"artifact":"82ee7c3bdd08411f77518efda8770f2122ac6820cbe66a5d7238e696804d7015","baseline_uj":50000000,"closed_by":"T2","closed_by_stub":1,"energy_provenance":"Estimator","from_cache":0,"goal_id":"50a70412c2f5d290bc8b63c1f237e4935863bd60f8d55a3139f12169075ce20e","proof_receipt":"proof/1","spec_grounding":"Ungrounded","steward":"did:example:proof-reference","total_uj":505050,"verdict":"proved","wasted_uj":5050}} +{"closed_by":"T3","envelope_hex":"d2845821a2012704581b6469643a6578616d706c653a70726f6f662d7265666572656e6365a059016dad67676f616c5f69647840343266633262643632363965383032613639323236636134366562653962626230373462653930306164353035306135613564303466323231643165633864356773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478406165323862643362653339316438386664643231303331356337623430303666646262303862616165623431323836396464633536323962343630326531343768746f74616c5f756a1a0302a55a69636c6f7365645f6279625433697761737465645f756a1a0007b4da6a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e636c6f7365645f62795f73747562016e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f7258404b06d1e479383f339f3aab11255f53aac6c03e1107d90666b1896f03ff4e88d54bff78358be465eeced3468c623efb1eb3d422c1e6da083a7e336208fb8c2b03","goal":"open conjecture","signed_core_hex":"ad67676f616c5f69647840343266633262643632363965383032613639323236636134366562653962626230373462653930306164353035306135613564303466323231643165633864356773746577617264781b6469643a6578616d706c653a70726f6f662d7265666572656e636567766572646963746670726f76656468617274696661637478406165323862643362653339316438386664643231303331356337623430303666646262303862616165623431323836396464633536323962343630326531343768746f74616c5f756a1a0302a55a69636c6f7365645f6279625433697761737465645f756a1a0007b4da6a66726f6d5f6361636865006b626173656c696e655f756a1a02faf0806d70726f6f665f726563656970746770726f6f662f316e636c6f7365645f62795f73747562016e737065635f67726f756e64696e676a556e67726f756e64656471656e657267795f70726f76656e616e636569457374696d61746f72","total_uj":50505050,"wire":{"artifact":"ae28bd3be391d88fdd210315c7b4006fdbb08baaeb412869ddc5629b4602e147","baseline_uj":50000000,"closed_by":"T3","closed_by_stub":1,"energy_provenance":"Estimator","from_cache":0,"goal_id":"42fc2bd6269e802a69226ca46ebe9bbb074be900ad5050a5a5d04f221d1ec8d5","proof_receipt":"proof/1","spec_grounding":"Ungrounded","steward":"did:example:proof-reference","total_uj":50505050,"verdict":"proved","wasted_uj":505050}} diff --git a/proof/proof-rs/crates/proof-receipt/src/lib.rs b/proof/proof-rs/crates/proof-receipt/src/lib.rs index 788d99f95..b104bab71 100644 --- a/proof/proof-rs/crates/proof-receipt/src/lib.rs +++ b/proof/proof-rs/crates/proof-receipt/src/lib.rs @@ -61,6 +61,23 @@ pub fn to_jcr1_wire(r: &CascadeResult, steward: &str) -> Value { if let Some(c) = r.ceiling_uj { obj.insert("ceiling_uj".into(), json!(c)); } + // Whether the rung that CLOSED the goal discharges nothing (SPEC §5.2). + // + // §5.2 requires a relying party to check this before acting on a proof, but + // until now it lived only on the in-process result — so anyone holding just + // the receipt, which is the whole point of sealing one, could not perform + // the check the spec asks of them. A stand-in-closed receipt is honestly + // signed and substantively empty; without this field nothing in the bytes + // says so, and `closed_by` does not: it names a TIER, and the same tag + // covers a real decision procedure on one ladder and a stand-in on another. + // + // Emitted only when true, so a receipt closed by a real solver + // canonicalizes to exactly the bytes it always did and every such receipt + // sealed before this field existed still verifies unchanged. The cost falls + // entirely on receipts that were making an unearned claim. + if r.closed_by_stub { + obj.insert("closed_by_stub".into(), json!(1)); + } obj.insert("steward".into(), json!(steward)); Value::Object(obj) } @@ -131,6 +148,55 @@ mod tests { assert!(o.contains_key("baseline_uj") && o.contains_key("wasted_uj")); } + /// A stand-in-closed receipt says so in the bytes a third party receives. + /// + /// SPEC §5.2 requires a relying party to check whether the closing rung + /// discharged anything; §6.1 puts the marker in the signed core so they can + /// do it holding only the artifact. Without this the pack ships a receipt + /// asserting an open conjecture is proved, with nothing to qualify it. + #[test] + fn a_stand_in_closed_receipt_declares_itself_on_the_wire() { + let r = prove_one("open conjecture", GoalKind::Hard); + assert!(r.proved && r.closed_by_stub, "fixture must be stub-closed"); + let wire = to_jcr1_wire(&r, "did:example:p"); + assert_eq!( + wire.as_object().unwrap().get("closed_by_stub"), + Some(&json!(1)), + "a receipt closed by a rung that discharges nothing must say so" + ); + } + + /// ...and the field is free for an honest proof. + /// + /// Omit-when-false is what makes this revision non-breaking: a receipt + /// closed by a real decision procedure canonicalizes to exactly the bytes it + /// did before the field existed, so every one sealed earlier still verifies. + /// The byte cost falls only on receipts making an unearned claim. + #[test] + fn a_real_closed_receipt_is_byte_identical_to_before_the_field() { + use proof_solvers::real_ladder; + let router = Router::new(real_ladder()); + let mut cache = ProofCache::new(); + let r = router.prove( + &ProofGoal::new("a | ~a", "prop", GoalKind::Trivial), + &mut cache, + ); + assert!(r.proved && !r.closed_by_stub, "fixture must be closed by a real rung"); + + let wire = to_jcr1_wire(&r, "did:example:p"); + assert!( + wire.as_object().unwrap().get("closed_by_stub").is_none(), + "the field must be absent, not false — an emitted 0 would change the bytes" + ); + + // Pin it at the level that actually matters: the canonical core is what + // gets signed and content-addressed, so compare bytes, not just the map. + let core = signed_core(&r, "did:example:p").unwrap(); + let mut expected = to_jcr1_wire(&r, "did:example:p"); + expected.as_object_mut().unwrap().remove("closed_by_stub"); + assert_eq!(core, jcr1::signed_core_bytes(&expected).unwrap()); + } + #[test] fn unproved_goal_cannot_be_sealed() { // No solver in the reference ladder is unable to close General/Hard, so