Repository navigation
Description
Opening this here so that UKGovernmentBEIS/inspect_ai#4413 can stay the narrow yes/no question it started as, and so the envelope discussion has a home with the people who actually maintain the formats.
@CSOAI-ORG proposed a common receipt envelope in that thread. I think that is worth pursuing, and I would rather start from measurements than from intentions. Everything below was measured on 2026-08-19 against inspect-receipts at commit ca3fd06044b855ed58eb316a6a014b57108e14f0.
Where we are today: neither verifier reaches the signature
I generated a receipt with your own Signer and base_receipt(), confirmed it with your verify(), then ran ours against it:
ERROR: unsupported schema 'csoai.inspect-receipt/0.1', expected 'proofbundle/v0.1'
And the other direction, one of our conformance receipts through your verify():
(False, 'content_id mismatch ...')
Your verify() recomputes content_id over all fields except signature and content_id; our envelope has no such field, so it aborts before checking the signature. Both failures are structural, not incidental. Neither verifier gets as far as the cryptography.
Three things a common envelope has to decide
-
Canonicalisation. You use
json.dumps(sort_keys=True, separators=(",", ":"), ensure_ascii=False). That is JCS-like but not JCS: RFC 8785 sorts by UTF-16 code units where Python sorts by code points, and number formatting follows ECMAScript rather than Python. For ASCII keys and integers the two agree; beyond that they are not guaranteed to. This is the cheapest of the three to settle and I would settle it first: name one normative canonicalisation and require it. -
Binding. We bind samples through RFC 6962 Merkle inclusion; you bind through a content hash plus per-file log hashes. A profile can map field names, but it cannot turn a hash into an inclusion proof. If
merkleis optional in a shared envelope, a verifier can no longer state which of the two it checked, and the stronger claim becomes invisible. I think this is the point that decides whether a shared envelope is worth having. -
Key trust. Your did:web root at
csoai.org/.well-known/did.jsonexists and is well formed — two Ed25519 keys, a verifier service, even a key continuity note for a retired key. That is more than I expected and it is good work. Butverify()checks only the key carried inside the envelope, andkiddefaults to"unpublished-key", so a self-generated key verifies as VALID today. If resolvingkidis optional, "verified" means different things on each side. Making resolution part of verification, not an extra for interested readers, would fix that.
Time is a fourth item, but it is a shared gap rather than a disagreement: your issued_at is the signer's local clock, and our own time anchor is not externally witnessed either. We have been working that question in #7 (external time anchors, RFC 3161 TSA and OpenTimestamps) — your optional RFC 3161 proposal lands directly in that thread, and it is the better place for it than a fresh discussion here.
What we are taking from your design
method_hash. A receipt that does not pin the scorer/harness version
Activity
CSOAI-ORG commentedon Aug 20, 2026
Thank you — this is exactly the kind of review that makes a shared envelope worth having, and the measurements are all correct. Accepting each point in order:
1. Canonicalisation. Agreed, and this is ours to fix. Our
json.dumps(sort_keys=True, …)is JCS-like, not JCS. We'll make RFC 8785 the one normative canonicalisation (implemented via a proper JCS serializer, not Python's dumps) and pin it in the schema so both sides compute the same content_id before any signature is checked.2. Binding. You've stated the deciding question precisely: a profile can map field names, but it cannot turn a content hash into an RFC 6962 inclusion proof. Our proposal: the envelope carries a required
bindingfield declaring exactly which mechanism was used ({"type": "merkle", "root": …, "path": …}vs{"type": "content-hash", "hash": …, "log_hashes": …}), and a verifier must state which binding it checked — it can verify one, both, or reject as unsupported, but it can never silently claim the stronger proof. The profile then has a truthful answer to "which of the two did you check?".3. Key trust. Correct on both counts, and the
"unpublished-key"default is a genuine flaw, not a feature. We're changingverify()so that kid resolution is mandatory: the key inside the envelope must match thekidin the did document (or an explicitly-passed trust anchor), and a receipt whosekidresolves to nothing fails closed. The did:web root stays the default anchor, and we'll keep the retired-key continuity note you spotted working.4. Time. Agreed —
issued_atbeing the signer's local clock is a shared gap. Our optional RFC 3161 + OpenTimestamps work moves into your #7, where the external-time-anchor question is already being worked properly.method_hash — glad it's useful; it exists so the receipt pins the scorer/harness version, making "what was claimed" precise.
On repo access: the package's canonical home is being sorted (the org-side admin step is in progress — our earlier link pointed at a path that 404s, which is on us). We'll post the commit here once the canonical public repo + the fixes above land, so the interop test can be re-run against the corrected code.
Measurement, not certification — a receipt is evidence of what was claimed and when, never a claim the eval is honest.
CSOAI-ORG commentedon Aug 20, 2026
Update — the fixes are implemented and self-tested (v0.2.0 of the package):
canonical.jcs()— UTF-16 code-unit key sort, ECMAScript number formatting, non-ASCII escaped as\uXXXX(test-vector style).content_id = sha256(jcs(canonical_body))is now the single normative form; verified against known-answer vectors.verify()fails closed if the envelope'skidisunpublished-key, empty, or does not resolve in the trust anchor (defaultdid:web:csoai.org). Self-generated keys no longer verify as VALID.VerifyResultreportskey_resolved.bindingfield ({type: content-hash|merkle, …}) andverify()returnsbinding_checked— it states which proof it ran, never silently claiming a stronger one.Self-test matrix (all pass): JCS known-answers · sign/verify round-trip with resolvable kid · unpublished-key fails closed · unknown kid fails closed · tampered body (content_id mismatch) fails closed.
The canonical public repo for the package is mid-publish (org-side admin step on our end — the path we pointed at earlier 404s, which is on us). The moment it's live I'll post the commit here so the interop test can be re-run against the corrected code. If you'd rather have the two files in this thread first, say the word and I'll paste the canonical + verify modules directly.
b7n0de commentedon Aug 20, 2026
Thanks for the update, and for moving this fast. Before anything else, credit where it is due: the repository is public and clonable, the package declares Apache-2.0, there is a publish workflow whose most recent run succeeded, and the
did:webpath is now actually in the code —resolve_did_web(),public_keys_from_did_doc()andverify_against_did_web()all exist and run. Its own test suite passes at the commit I measured (6 tests). That is real work in a short time, and it is a visible step beyond where this stood on 19 August.I measured against a pinned commit so that anything below can be checked or corrected against a named state:
One note on the repo, since your last comment said the path was mid-publish and 404ing: it resolves now, which is how I was able to clone and measure. If that is not the canonical home you meant, say so and I will re-measure against the right one before anything else.
One thing up front, because it changes how the rest should be read: I could not find the three fixes on
mainat that commit. The most likely explanation is simply that they live in a branch that has not been pushed yet. So the three points below are questions about which commit to measure, not claims that the work was not done. If you name a commit, I will re-measure against it and post the result the same way.1. RFC 8785 canonicalisation — which commit carries it?
At the pinned commit there is no
canonicalmodule and nojcsfunction; the strings8785,jcsandJCSdo not occur anywhere in the repository. The canonicaliser is:That form is deterministic, which is the important half. It is not JCS, though, and the gap is observable rather than theoretical. Running the same objects through this function and through an RFC 8785 implementation, 3 of 5 test vectors diverge:
The first vector is escaped above on purpose: the two keys are U+FFFD and U+1F600. They are chosen because U+FFFD sorts above the surrogate range by code point and below it by UTF-16 code unit, so the two orderings disagree on exactly this pair. RFC 8785 sorts keys by UTF-16 code units, Python's
sort_keys=Truesorts by Unicode code point, and the two orders differ as soon as a key contains a character outside the BMP. Two conforming implementations would then compute different digests over the same object. This is a small, contained change if you want it.2. Mandatory, fail-closed kid resolution — which commit carries it?
At the pinned commit
verify()still resolves nothing by default:With
published_keyomitted it falls back to the key the receipt carries about itself (receipt.py:335-338), andverify_against_did_web()(receipt.py:346) is a separate function a caller has to choose. There is nokey_resolvedfield;verify()returns a(bool, str)pair.Concretely: I generated a key that appears in no published DID document, signed a receipt with it using your
Signerandbase_receipt(), and calledverify():Worth saying plainly: that reason string is honest. It does not pretend the key is trusted, and that is better than most implementations manage. But the return value is
True, so a caller that branches on the boolean and not on the prose accepts a self-signed receipt. If "mandatory and fail-closed" is the intent, the boolean is where it has to show up.3. A declared
bindingfield — which commit carries it?At the pinned commit there is no
bindingkey in the receipt envelope and nobinding_checkedin the verifier output;binding_checkeddoes not occur in the repository. A receipt built bybase_receipt()carries these top-level fields:For what it is worth, the binding that is there is not nothing:
content_idis a SHA-256 over the canonical body (receipt.py:295-301), andeval_log_digest()(receipt.py:106) pins a stable projection of the EvalLog object model rather than the fragile.evalbytes. That is a defensible design choice and I will say so publicly. It is just not a declaredbindingfield.4. Two things you will want to know about, unrelated to the above
These are ours to hand over rather than to score.
DEFAULT_KIDis:The published document at
https://csoai.org/.well-known/did.json(fetched 2026-08-20T11:22:56Z, sha2567bc1c8d301dbf0128bc39307059a9b8cf9e2b687c506492d26c800650c1ec8c3) lists three verification methods:There is no
#keys-1. So a receipt signed with the default configuration cannot be verified by a stranger through the path you built for them:The fallback at
receipt.py:359-363— use the only key if there is exactly one — does not engage here, because the document publishes three.A related detail while you are in there.
public_keys_from_did_doc()(receipt.py:236-260) keys its map on the verification method'sid, soverify_against_did_web()compares the receipt'skidagainst the full DID URL — not against the bare fragment and not against thekidfield inside the JWK. For two of your three methods those agree. For the third they do not:A receipt issued with
kid: csoai-board-attestation-1would therefore fail to resolve even though the key is published. Whichever convention you prefer is fine; they just have to be the same on both ends.The second thing is trivial but worth two minutes: GitHub does not detect a licence for the repository, although
pyproject.tomldeclares Apache-2.0. ALICENSEfile at the repository root would fix that, and it matters more for a package other people are meant to depend on than the effort suggests.5. Interop, both directions, at the pinned commit
Verbatim, against
proofbundle4.0.0 from PyPI:Your receipt through our verifier — signed with your
Signerandbase_receipt(), confirmed by your own verifier first:Python API, same thing, with the exact stopping point:
Our conformance fixture through your verifier —
conformance/bundle/valid-minimal, the positive anchor of our native-bundle set, which our own verifier accepts (CRYPTO: OK):That verdict falls in
_check_content_id()(receipt.py:295-301), before the signature check atreceipt.py:340, because our bundle carries nocontent_idfield at all — its top-level fields aremerkle,payload_b64,schema,sd_jwt_vc,signature.Compared with 19 August the outcome is unchanged: both directions still stop at the first structural gate, before either side reaches a signature. What did change is on your side — there now is a
did:webresolution path at all. The envelope incompatibility is the part that has not moved, and it is exactly the part this issue is about.6. The binding question stays open, deliberately
I am not going to answer the shared-envelope question in this round, and I want to be explicit about why rather than let it drift. The shape of a common envelope cannot be decided against a description of an implementation; it can only be decided against code that a verifier has read. Right now the two envelopes reject each other at the first gate in both directions, which is the worst possible position from which to standardise something.
Your proposal — a mandatory
bindingfield with a declared type, and abinding_checkedresult reported back by the verifier — is a serious approach and I am not dismissing it. Reporting what was actually checked, rather than only whether something passed, is the right instinct. I am not adopting it in this round either.The trigger for picking this back up is concrete: a commit from you carrying the three fixes. Not a comment, a commit. I will measure against it and post the result.
7. Cadence
To set expectations so nobody is waiting on the other: we measure against a named commit and report what we find. The next round runs when there is a commit to measure, not on every comment.
CSOAI-ORG commentedon Aug 24, 2026
Thanks again for the pinned measurement on
8e33f160— it was exactly the kind of review that makes a shared envelope worth having.The three fixes are on
mainnow. Please measure against:397ae3ad8714a73d1168487cf5eb192ef8d45b57Repo: https://github.com/CSOAI-ORG/inspect-receipts
What that commit carries (v0.2.1):
src/inspect_receipts/canonical.py(jcsdependency);content_id = sha256(JCS(body)). Tests include your three divergence vectors (UTF-16 key order,1e-7, integral2).verify()returnsVerifyResultwithkey_resolved; no trust anchor ⇒valid=False; self-embedded keys never pass.binding— envelope carriesbinding: {type: content-hash, …}; verifier reportsbinding_checked(content-hashtoday;merkledeclared but rejected until a profile exists).Also aligned to our live did document (2026-08-24 stack): default kid is
did:web:csoai.org#estate-chain-1(not#keys-1);public_keys_from_did_doc()indexes fullid,jwk.kid, and bare fragment; rootLICENSEadded.We know cross-format interop still stops at the schema gate (
csoai.inspect-receipt/0.2vsproofbundle/v0.1) — unchanged, and honestly the harder half. Your binding question stays open until you've read code; we're not claiming that round is settled.Whenever you've re-measured, we'd welcome teaming up on the next layer — a shared profile or adapter that lets each verifier state what it checked without silently upgrading claims. Happy to work from your measurements, not our intentions.
Measurement, not certification.
b7n0de commentedon Aug 26, 2026
Measured, against 397ae3ad
I cloned the repo, checked out
397ae3ad8714a73d1168487cf5eb192ef8d45b57, and ran control-flow probes againstverify(). All four fixes hold.valid=Truevalid=Falsevalid=Falsevalid=Falsevalid=Falsevalid=False, refused explicitlyvalid=FalseThe merkle refusal reads
merkle binding declared but not verified by this verifier. That is the separation this thread asked for, and it is built.One finding I prepared and withdrew. The README drift, 0.2.0 and
#keys-1, was already fixed in1d8fdccbefore I could post it. Measured, therefore not claimed.Two findings
E1. The schema id is written and never read. The string
schemaappears exactly once insrc/inspect_receipts/receipt.py, line 425, on the emit path.verify()never reads it. An otherwise unchanged receipt carryingschemaset toproofbundle/v0.1verifiesvalid=True,binding_checked=content-hash. So the cross-format question this thread left open now has a measured answer. The verifier neither refuses a foreign format nor judges it. It does not see it, and a consumer readingvalid=Truewill assume the receipt was checked against the schema it names.E2. A reported binding is not always a performed binding.
_check_bindingcomparesbinding.eval_log_sha256only when that key is present. Omit it and the comparison is skipped, yet the function still returnscontent-hash. A receipt whose binding is{"type": "content-hash", "digest_alg": "sha256"}with no digest verifiesvalid=True,binding_checked=content-hash, with the eval log bound to nothing. A content-hash binding without a digest has to fail closed.The probe script is sixty lines of my own work. Say the word and I paste it here, both findings are yours to re-run.
Canonicalisation
Measured with the real encoder, not assumed.
jcs 0.2.1in a fresh environment, compared againstrfc8785 0.1.4, which is the package our own emit path uses. The three divergence vectors from this thread, UTF-16 key order over the pair U+1F600 and U+FFFF, the number1e-7, and the integer2, come out byte-identical acrossjcs.canonicalize, theJSONEncoderyourcanonical.pyimports, andrfc8785.dumps. The canonicalisation base for interop holds. No finding.The probes above were then re-run with the real encoder and real dependencies,
jcs 0.2.1,cryptography 50.0.1,inspect-ai 0.3.260. Same picture. N1 green, N2 to N7 red, E1 and E2 still verify VALID. Canonicalisation does not change the control-flow findings.One note read rather than measured.
canonical.pyimportsfrom jcs._jcs import JSONEncoder, a private module of the dependency. A format's normative canonicalisation should not hang off a non-public interface.The envelope, and the terms on which we would carry one
One sentence carries everything below. A reported field is the report of a check that was performed. E1 and E2 are both violations of that sentence, and each rule ships with the counter-probe that must fail.
schemafirst and refuses an unknown one as its own outcome, distinct from invalidbinding_checkedis set only when the binding was complete, a content-hash binding without a digest fails closedpopulation_size,evaluated_count,unresolved_countpopulation_sizeof zero, distinguishable without opening the artifactR5 is not my idea. It surfaced this week in the OpenTelemetry semantic-conventions thread as the argument for a no-coverage-claim non-goal on evidence references, and I measured the same failure class in my own tooling the same day, which is why it is a rule here and not a footnote.
And one boundary that is not a rule inside the format but a condition for having one. The trust anchor is the emitter's field, never the format's property. An envelope that defaults to any party's root,
did:web:csoai.organd ours alike, turns a format into an authority assignment. We will not carry that. Each emitter names its anchor, each verifier reports which anchor it resolved against, and the format blesses neither.The profile lives in this repository, and conformance is demonstrated rather than negotiated. R6 is how.
Envelope work on our side begins once
proofbundle 5.0.0is on PyPI, which is checkable from the outside.Measurement, not certification. On both sides of the envelope.
b7n0de commentedon Aug 28, 2026
Correction to my canonicalisation note above.
I wrote that the three vectors come out byte-identical and called it no finding. I measured the wrong object, and the wrong object flattered the result.
jcs.canonicalizeandrfc8785.dumpsboth emit raw UTF-8 and do agree on those three vectors. But the content_id is not computed by either. At 397ae3ad the alias that signing and verification actually run iscanonical.canonical_bytes, and it builds the encoder asJSONEncoder(sort_keys=True, ensure_ascii=True, separators=(",",":")).ensure_ascii=Trueescapes every non-ASCII character to\uXXXX. That is your own stated design, non-ASCII escaped as \uXXXX, test-vector style. RFC 8785 does the opposite, outside the mandatory escapes it emits raw UTF-8. So the escaped form is a different convention, and the content_id diverges from a conformant RFC 8785 serializer, ours included, for every receipt that carries a non-ASCII character.{"café":1}diverges. ASCII-only receipts stay identical.Ordering and numbers are unaffected, both are RFC-8785-correct, the UTF-16 key sort holds. The divergence is string escaping alone, and the three vectors in this thread do not isolate it, which is how my earlier pass missed it. Measured against R1 this means the 397ae3ad content_id is not yet the one normative RFC 8785 form for non-ASCII, even with jcs vendored, because the escaping choice sits on top of it. E1 and E2 stand as written.
What I have not measured is whether this breaks the running interop, and that is a protocol question, not a byte question. If a verifier recomputes the content_id with a raw-UTF-8 JCS and compares, it breaks for non-ASCII. If it trusts the self-computed content_id, each side stays internally consistent. Your call, and worth naming in the profile either way.
Correcting my own record rather than leave a wrong no finding standing. Measurement, not certification.
CSOAI-ORG commentedon Aug 29, 2026
Thanks for measuring
397ae3ad— and for the 28 Aug correction on the encoder. Both findings stand, and the correction is the one that matters for R1.E1. Agreed.
verify()must readschemafirst. A foreign schema id (proofbundle/v0.1on our envelope, or ours on yours) has to come back as its own outcome, distinct from invalid. Silentvalid=Trueon an unread schema is a control-flow bug, not a profile.E2. Agreed.
binding_checkedis only true when the digest was present and compared. A content-hash binding with no digest fails closed. We will not report a check we did not run.Canonicalisation (your correction).
canonical.canonical_bytesat that commit isJSONEncoder(..., ensure_ascii=True). That is our stated test-vector style, and it is not RFC 8785 for non-ASCII.{"café":1}diverges; ASCII-only receipts do not. We will not call that JCS. Next round is a named commit that either emits raw-UTF-8 RFC 8785 through a public serializer, or pinsensure_ascii=Trueas a different named profile. Until that commit exists, treat R1 as open.Same cadence as you: we measure against a named commit and report what we find. No comment-only claims.
On a shared envelope: we would rather start from your R1–R6 probes than from a merge. Trust anchor stays the emitter's field —
did:web:csoai.orgis ours, not the format's. UKGovernmentBEIS/inspect_ai#4413 stays the narrow yes/no; time stays in #7; this issue stays the envelope. Envelope work on our side is ready to re-run the dayproofbundle 5.0.0is on PyPI, which is checkable from the outside.We are on the same bandwidth: receipts a stranger recomputes, fail-closed keys, declared bindings, empty cells empty. If you want a pairing pass on the counter-probes against the next commit, say the word — GitHub is the right home, not a new list.
Measurement, not certification.
b7n0de commentedon Aug 31, 2026
Two things from our side, both checkable.
proofbundle 5.0.0is on PyPI since 2026-08-27. That was the condition I named on 26 August, so this is a discharge rather than an announcement.R6 is now shipped rather than stated. The profile says that claiming it means shipping the executable counter-probes plus positive controls at 100 percent detection, and that a profile claim with no probes attached is the counter-probe to that rule. Until now my own claim had no probes attached. The profile is
proofbundle/receipt-envelope-profile/v0.1, published at RECEIPT_ENVELOPE_PROFILE.md and archived at 10.5281/zenodo.22209671, and the probes are at conformance/envelope_profile. They run against any envelope, ours included. Our own emit path sits in the positive controls, and where it fails it is reported as failing. To be precise about where they are: the profile and the vectors ship in the sdist and live in the repository; the wheel carries neither, which is deliberate because the wheel is the import package. They cover R1 to R4. R5 states its requirement and points at the IETF work rather than shipping a probe, by decision.On R1, your reading matches mine, and the next round is a named commit either way. Nothing to add from here until that commit exists.
On the pairing pass, yes. Bound to your named commit, because there is nothing to pair against before it. I will run the same probes against yours that I ran against
397ae3adand post the result here, pass or fail, in the same shape.One small ask, and it is a courtesy rather than a claim. Four of the requirements in this profile came out of this measurement series. R1 from the canonicalisation thread, R2 and R3 from E1 and E2, R4 from the key-resolution probes. Your README documents them as design properties of the package, which they now are. A line pointing back to this issue would let a reader arriving at the README see where they were worked out. I will do the same in reverse and link your implementation from ours.
Two conditions on anything shared, and they are the two I set out on 26 August.
The trust anchor stays the emitter's field. No shared artifact carries any party's root as a default,
did:web:csoai.organd ours alike. Your comment says the same. I am restating it because a default in code outweighs a sentence in prose, andverify()currently defaults to yours.The profile keeps its name and its home. It lives in this repository, it is versioned, and conformance is demonstrated by running the probes and posting the result, not negotiated into a third name. The profile creates no assessing party. Conformance is shown, never conferred, and no party is appointed to judge another's. Two implementations of one named profile is a stronger result for both of us than one merged format with no owner.
Thread ordering as you have it, with one change on my side. In #4413 I am withdrawing the narrow yes/no rather than repeating it, because that repository's own contribution rules already answer it; the issue itself stays open. Time stays in #7, this issue stays the envelope.
Measurement, not certification.
Prepared with AI agent involvement, reviewed and submitted under human oversight.
Agent review receipt · Tier 1, selfDeclared
proofbundle_147_comment.r2 · transparency log entry
sha256:179aa1cd79558dbec66d8cbf226c7f6740863e50627b2d83c9b614902efb039cCSOAI-ORG commentedon Sep 5, 2026
The named commit exists, and R1 went against us.
ef2ec2772606be2b53fe29720063b06db1f0e8c2onCSOAI-ORG/inspect-receipts, branchfix/r1-jcs-and-emitter-anchor(PR #3). It is a PR head, notmain, and I would rather say that than let you measure against something I implied was landed.R1. You were right and we were worse than "not JCS".
canonical.pyran the encoder withensure_ascii=Truewhile its own docstring claimed RFC 8785 and claimed verification against this issue's divergence vectors. Measured againstjcs.canonicalize, it diverged on 4 of 6:The reason it survived is worth naming, because it is the same class of defect you have been probing for:
test_jcs_known_answer_vectors, named "RFC 8785 divergence vectors from proofbundle#147", asserted the escaped form as the expected answer. The test pinned the defect while its name promised the specification. It is corrected in that commit with the reason written into the docstring rather than fixed quietly.Resolution is your first option, not a second profile: we now call
jcs.canonicalizedirectly instead of configuring an encoder, so there is no local flag left that can drift from the spec again. Key order was already right in both forms and is unchanged — UTF-16 code units, so U+1F600's high surrogate0xD83Dsorts beforeU+FFFD.Consequence, stated rather than buried.
content_idchanges for any body with a non-ASCII byte. Receipts signed under the old form do not re-verify under this one. They are not edited: the schema id moves tocsoai.inspect-receipt/0.3so a verifier reports an old receipt as its own outcome rather than as invalid — which is E1 applied to ourselves.Your second condition, and you were right that a default in code outweighs a sentence in prose. We agreed the anchor stays the emitter's field and then shipped the opposite twice:
base_receipt()stampeddid:web:csoai.orginto every receipt. Anyone emitting with our library produced receipts asserting our root over their evidence. It now readsemitter_did()/emitter_did_doc(), settable viaINSPECT_RECEIPTS_DID/INSPECT_RECEIPTS_DID_DOC. Our values remain the default only because we publish the package.verify_against_did_web()defaulted to our DID document, so a stranger checking a third party's receipt silently checked it against CSOAI's keys. It now resolves the anchor named by the receipt, and returns UNCHECKABLE — never invalid — when none is named.R6, and the profile. Noted, and the ordering is right: the profile keeps its name and its home in your repository,
proofbundle/receipt-envelope-profile/v0.1, archived at 10.5281/zenodo.22209671. Two implementations of one named profile, conformance shown by running the probes and posting the result, no assessing party — we are not asking for a third name and we are not appointing ourselves to judge yours.Your courtesy ask is fair and is done in that commit: the README now records that R1–R4 came out of this measurement series, links this issue, and says the profile lives with you. Please do link our implementation from yours.
On the pairing pass: yes, and the commit above is the binding. Run your probes against
ef2ec277and post the result here, pass or fail, in the same shape you used for397ae3adandca3fd060. We will do the same against27a84db3. If R1 now passes both directions, the interesting question becomes R2/R3 under a schema id you have never seen — which is a better test of E1 than the one we agreed to.Measurement, not certification.