[SCITT] Re: First-failure list: draft-dogru-cedulon-04

e.dogru@conarium.dev Mon, 31 August 2026 08:28 UTC

Return-Path: <e.dogru@conarium.dev>
X-Original-To: scitt@mail2.ietf.org
Delivered-To: scitt@mail2.ietf.org
Received: from localhost (localhost [127.0.0.1]) by mail2.ietf.org (Postfix) with ESMTP id 3CAE71321559A for <scitt@mail2.ietf.org>; Mon, 31 Aug 2026 01:28:43 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ietf.org; s=ietf1; t=1788164923; bh=jl1FjU2Pd6/TyvnQpoo8q2B03fIfxqhWn/Ds1abCJns=; h=Cc:From:In-Reply-To:References:Subject:To:Date; b=Sd60LmdgD+oqqxyyaCsYt/wJrtC7oLla7xyMcHsvJf2c7alVXKvTHr8Exf1wKfs4m sf8COsXL+JyjOS6XiWWc2oNB2KPJOffgdijriY2BnjGWsu5WFjELaZARKOyNlvj8lW YiEEUMIhmOamrrMUKMh0TmNejSZFIQ/JqixkyTA8=
X-Virus-Scanned: amavisd-new at ietf.org
X-Spam-Flag: NO
X-Spam-Score: -1.62
X-Spam-Level:
X-Spam-Status: No, score=-1.62 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, HTML_MESSAGE=0.001, HTML_MIME_NO_HTML_TAG=0.377, MIME_HTML_ONLY=0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001] autolearn=no autolearn_force=no
Authentication-Results: mail2.ietf.org (amavisd-new); dkim=pass (2048-bit key) header.d=conarium.dev
Received: from mail2.ietf.org ([166.84.6.31]) by localhost (mail2.ietf.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 9GRNf61CVTBq for <scitt@mail2.ietf.org>; Mon, 31 Aug 2026 01:28:41 -0700 (PDT)
Received: from azure.pear.relay.mailchannels.net (azure.pear.relay.mailchannels.net [23.83.216.7]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-256) server-digest SHA256) (No client certificate requested) by mail2.ietf.org (Postfix) with ESMTPS id 900D913215591 for <scitt@ietf.org>; Mon, 31 Aug 2026 01:28:41 -0700 (PDT)
X-Sender-Id: hostingeremail|x-authuser|e.dogru@conarium.dev
Received: from relay.mailchannels.net (localhost [127.0.0.1]) by relay.mailchannels.net (Postfix) with ESMTP id E7F9961E67 for <scitt@ietf.org>; Mon, 31 Aug 2026 08:28:33 +0000 (UTC)
Received: from fr-int-smtpout15.hostinger.io (100-96-173-63.trex-nlb.outbound.svc.cluster.local [100.96.173.63]) (Authenticated sender: hostingeremail) by relay.mailchannels.net (Postfix) with ESMTPA id 1644B62CED for <scitt@ietf.org>; Mon, 31 Aug 2026 08:28:32 +0000 (UTC)
X-Sender-Id: hostingeremail|x-authuser|e.dogru@conarium.dev
X-MC-Relay: Good
X-MailChannels-SenderId: hostingeremail|x-authuser|e.dogru@conarium.dev
X-MailChannels-Auth-Id: hostingeremail
X-Spill-Befitting: 406b779924a10296_1788164913709_598384529
X-MC-Loop-Signature: 1788164913708:3466293667
X-MC-Ingress-Time: 1788164913708
Received: from fr-int-smtpout15.hostinger.io (fr-int-smtpout15.hostinger.io [148.222.54.41]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384) by 100.96.173.63 (trex/8.0.2); Mon, 31 Aug 2026 08:28:33 +0000
Received: from localhost (34.86.89.34.bc.googleusercontent.com [IPv6:2a00:1d35:17a4:d900:3825:39b2:dfde:f1ad]) (Authenticated sender: e.dogru@conarium.dev) by smtp.hostinger.com (smtp.hostinger.com) with ESMTPSA id 4hYMZl15WWz20yh for <scitt@ietf.org>; Mon, 31 Aug 2026 08:28:31 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=conarium.dev; s=hostingermail-a; t=1788164911; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=RjQ8ZxF/g4/7mN//2+BCTUylnizrZJJQtjsjcbS0Mcw=; b=pcODpSQdAE0kdfrwtmNQH/kOvwwhNvWcHfTzmGzM66zqjwKaSkxGbsWq0+yrtLtHaeefpv ehFGJzFABaJmiodwkpJq7q4dOCyvDIxD0TtqhoYAdxsiq7HNtvIjoeuymJtU0fOjYF62fC ejgYBHtEVHsmIfBtg4pN3gutUgzWe4pwchrvFqlMXeo0GDHU8VIU5n6qzLpVbG3xszRLiC d6LV9kNgg1QMsPSeAq8zwjMyvnOxfOG4/XHJCi6my9xxWxSZjsgVVuZuTjHnv/2O5VWrLi pB0myBDaCMo6cFhoKNpq1gYf3LrQF3Uj9sl4D5+8QW8gwUW11vYJeMKdVUMkLg==
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset="utf-8"
From: e.dogru@conarium.dev
In-Reply-To: <465ITu9UnGDKZqwWdqZD94K1LFpJxYWWBF-ztLXONI909Mk8_ISvfB71dAY_PDolw0SE14uJyBQeRafcV4gBQqFjfYLf7xVrjuIewnurNJQ=@donttrustverify.pt>
Message-Id: <1788164902119403722.1788164902@conarium.dev>
Mime-Version: 1.0
References: <465ITu9UnGDKZqwWdqZD94K1LFpJxYWWBF-ztLXONI909Mk8_ISvfB71dAY_PDolw0SE14uJyBQeRafcV4gBQqFjfYLf7xVrjuIewnurNJQ=@donttrustverify.pt>
To: tiago@donttrustverify.pt
Date: Mon, 31 Aug 2026 08:28:31 +0000
X-CM-Analysis: v=2.4 cv=Gq4Q+V1C c=1 sm=1 tr=0 ts=6a953b2f a=oYqjX9uBxZ59dwsAVmhygw==:617 a=xqWC_Br6kY4A:10 a=IkcTkHD0fZMA:10 a=48vgC7mUAAAA:8 a=NEAV23lmAAAA:8 a=fJZVNl2HAAAA:8 a=B8vRJX_EJdOV9OVoLnsA:9 a=mVYWatL4oJemOBvQ:21 a=QEXdDO2ut3YA:10 a=d2OVueXrxaEwMOOcNzvm:22
X-CM-Envelope: MS4xfFZp0DPYITbrypiOUCVw/GugNFQWIKK/gi+nrc9ARKzDDptEPTO7oRgejsolW8qB/QYF/pS/MQFwmvuPRn8+R8jX9cfqo6M/gEX1SosL2PCIFUSXC3QH N7LQrwGSvH1Nzhp8mb9kwE7BkKZti3Vg6O9QkcGlHkE8Yz5xjlhK0tTKxak9CLcY2yyDa2D7ehoCYTOksr5p7JzMWfGIVxY4Q+Ebh9j76WcDWkPFTlsfXTUU vdGJ1VEpLloXhd4r0DISKQ==
X-AuthUser: e.dogru@conarium.dev
Message-ID-Hash: SRGLPVHGT53INP2HAXB7TFIYP5DG262W
X-Message-ID-Hash: SRGLPVHGT53INP2HAXB7TFIYP5DG262W
X-MailFrom: e.dogru@conarium.dev
X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header
CC: scitt@ietf.org
X-Mailman-Version: 3.3.9rc6
Precedence: list
Subject: [SCITT] Re: First-failure list: draft-dogru-cedulon-04
List-Id: "Supply Chain Integrity, Transparency, and Trust" <scitt.ietf.org>
Archived-At: <https://mailarchive.ietf.org/arch/msg/scitt/uZBmvQLwT_D8KZGDk7rg32ZgOWU>
List-Archive: <https://mailarchive.ietf.org/arch/browse/scitt>
List-Help: <mailto:scitt-request@ietf.org?subject=help>
List-Owner: <mailto:scitt-owner@ietf.org>
List-Post: <mailto:scitt@ietf.org>
List-Subscribe: <mailto:scitt-join@ietf.org>
List-Unsubscribe: <mailto:scitt-leave@ietf.org>

Tiago, all,

This is the review method working as intended: you ran the vectors before reading, and the list names the exact points where an independent implementation stops. Thank you. Disposition per item follows, with the section of -05 that carries each repair. -05 is posted: https://datatracker.ietf.org/doc/html/draft-dogru-cedulon-05. The repairs are on master at e1740d1, forty-two commits since the posted -04, and the guard for each repair was run red against the pre-repair tree before the fix landed. The full pre-release suite there is 423 cases, zero failing, on three hosted runners - Linux, macOS, and Windows, each as a non-root user - with none skipped; a fourth Linux job runs three cases only. A local Windows run without symbolic-link privilege skips four POSIX-mode cases by name. Objects signed before the repairs still verify after them where the posted text already called them well-formed: the -04 Appendix A receipt bytes are kept in a historical test and still verify as COSE while the audit refuses their claim; a manifest signed before the payee member existed, and a countersignature without deliveredHash, verify unchanged. Every addition below is an optional member, and the new refusals are for inputs the tables already named as malformed.

On the vector run first: your reproduction (archive bytes, Python 3.14.4, cbor2 6.1.4 - both signatures, the SPKI-derived kid, byte-for-byte re-encoding) is the first independent run of the -04 vectors outside this codebase. With your consent I would like to record it as such in the interop evidence, with your environment line as you stated it.

1. Transparency receipt path vs RFC 9942. Correct, and the sharpest cut in the list. Section 18's admission had leaked into the protocol text: Section 11.3 describes hash comparison while citing the RFC 9942 mechanism, and the evidence bundle never carries the candidate-entry bytes that Section 5.2.1 consumes. -05 splits this into two named tiers (Sections 10.3 and 11.3, MUST-T11-18 and MUST-T11-19). Tier 1 renames the input to what it actually is - a witness co-signature over the checkpoint statement hash, establishing "the witness signed for this hash" and nothing more; the RFC 9942 citation moves out of that path. Tier 2 defines optional evidence fields for the registered Signed Statement bytes and the inclusion proof, and specifies the full RFC 9942 verification over them; where they are not supplied, that tier reports itself as not exercised rather than passing. Tier 2 is implemented and measured: the in-process witness now maintains a real Merkle tree and issues inclusion proofs, and the audit takes the candidate statement bytes and the proof as an optional pair - a valid pair reproduces a witness-signed tree head and passes silently, a corrupted sibling is witness-inclusion-invalid by name, and the old receipt-without-proof path reports witness-inclusion-not-exercised rather than passing. One deliberate strictness worth stating: proof-to-root reproduction alone does not count - the leaf hash, index and root must match a witness-signed inclusion receipt exactly, which is what closes the usual leaf/interior ambiguities of an unseparated hash tree, and -05 writes that coupling down as normative rather than leaving it implementation folklore. The shape in the text is taken from the implementation so the two cannot diverge.

2. Key resolution in the unpinned branch. Correct - step 4 said "reject" and "report issuer-key-mismatch" in the same breath. -05 replaces it with one rule and a table reading it out per cell (Section 10.1): membership in the attested set follows verification under the pinned root, and nothing else. Carried key does not match the pin - issuer-key-mismatch, excluded, the settlement it names stays uncovered. Signature fails under the pin - the chain walk names it receipt-chain-break with a signature-failed detail (measured, not renamed), same exclusion. No pin - no signature check happens at all, because the profile deliberately carries no key (MUST-T4-9); receipts are presented-unattested and accusation-shaped findings downgrade to warnings on the MUST-T8-9 two-branch rule. One path, one name per cell, and "rejection" always means a named finding plus exclusion, never silent removal. Measuring the cells surfaced one more case of your break-6 lesson: the carried key PEM travels outside the issuer signature, so a swapped carried key on an honestly signed receipt must not be able to move it out of the attested set. That rule is implemented and measured: attestation is decided by verification under the pin, the swap leaves the finding set byte-identical and is named carried-key-mismatch as a warning, and a receipt that claims the pin but fails to verify under it is still walked and named rather than silently dropped.

3. Rail Extract construction. Correct on both counts. "The member names of that body are the rail's to define" contradicted Table 8 and loses; the Table 8 names are normative, rails may add members but not rename the core four. This is implemented and posted (Section 9, with the record schema in 9.1): one runtime schema now sits behind signing, verification, and the refusal channel alike - a renamed core member is a named refusal, an added member passes, a missing window or identity field is a named refusal - and -05 carries the complete signed body shape (record set, windowStartMs, windowEndMs, account and rail identifiers, and the signature representation) taken from that schema so the two cannot diverge. The CBOR-terminology leak in Table 8 (tstr/uint for a JSON body) is corrected in the text.

4. Issuer order, chainHeadHash, zero-checkpoint. "Issuer order" gets its missing definition (Section 11.4, step 6): the order induced by the prevReceiptHash chain. Presentation order carries no weight - the verifier reconstructs the chain from the links, and what cannot be reconstructed is receipt-chain-break. This is implemented and merged, with a guard that shuffles the presentation and requires a byte-identical finding set. timestampMs is issuer-asserted, as you note, and is not an ordering source. "Last receipt in that window" binds to the same definition: the chain's last link inside the window, measured by test. On zero checkpoints and the open epoch, -05 writes down what the implementation measurably does, which is fail-closed: the window-coverage check runs against presented checkpoint windows, and a receipt that falls under none of them - including every receipt when no checkpoint is presented, and receipts after the last closed checkpoint - is a named window-coverage finding. Nothing is skipped and nothing is silent; uncovered evidence surfaces as a finding rather than as a gap in the report. The text names the open epoch as a state so a verifier can say why the finding fired, but the behaviour is one rule, not two. The sentence "nothing else in this list feeds another step" was simply false and is replaced by the actual dependency list: step 7's index of refs feeds steps 8 and 9, step 11's verified checkpoints feed step 14, and step 15's surviving witness receipts feed steps 14 and 16.

5. Independent clocks at the window boundary. Correct, and this one is a design gap, not a text gap: both honest verifiers reach the same false finding. The decision, implemented and measured: membership follows the ref binding first - a receipt whose ref appears in the extract is reconciled against it regardless of which side of the boundary its own timestampMs fell; the timestamp scope test applies only to receipts with no matching row; and an unmatched item within a declared skew allowance of the boundary is reported as boundary-deferred rather than as settlement-without-receipt / receipt-without-settlement (Section 11.4, step 5, MUST-T10-17). The two edges resolve differently, and -05 says so: a closing-edge deferral resolves against the following window's extract when one is presented and hardens into the real finding when that extract does not name the ref; an opening-edge deferral resolves only against a receipt in the presented bag that names its ref, and a following extract does not harden it. A reading of -05 from the text alone, before posting, found the implementation hardening the opening edge while the text's reason for the deferral - ref binding - never said so; the implementation now follows the text, and the seven edge cases are locked as tests. The allowance is declared by the extract (a clockSkewMs member, a non-negative integer), with a profile default when absent. The two probes that used to document the false findings now measure their absence. I would take your reading on whether ref-first membership plus a named boundary state covers the failure you had in mind.

6. The appendable countersignature. Correct, and it is the same lesson MUST-T8-9 already encodes - we applied it to the manifest comparison and missed it one object over (Section 5.1). A countersignature that does not verify under the pinned payee key cannot be attributed to the payee, so it cannot carry evidentiary weight in either direction: it is excluded with a warning (countersign-bad / countersign-key-mismatch become warnings, and countersign-missing still fires, since an unattributable object is absence of the payee's word), and the audit verdict on the untouched issuer receipt does not move. An object outside the issuer signature must not be able to manufacture a negative result. This is implemented and merged, and measured from both directions: on the pre-repair tree, appending a junk blob or a validly-signed foreign countersignature flipped an honest audit to failing; on the repaired tree the finding set is byte-identical under both, the noise is named in warnings, and a valid countersignature under the pinned key still counts as approval.

7. Counterparty binding. Correct: a manifest published by A, a receipt paying B, and a matching extract row all pass, and nothing in the defined evidence closes that triangle. And you looked for the right missing sentence - we do not state ref-resolves-to-beneficiary as a rail property, and I do not want to assume it. The decision, implemented and measured: two optional bindings and one honest scope statement. An optional payee member on the Trade Manifest, compared on exact octets when present under the MUST-T8-9 two-branch rule; an optional beneficiary member on the extract record, compared against the receipt payee when the rail declares it (beneficiary-mismatch); and where neither is present the report carries a counterparty-unbound record - a scope statement, not a finding against anyone, because no key stands behind an accusation there, and it does not move the verdict (Sections 4 and 9.1). The probe that used to measure "a manifest by A, a payment to B, and everything passes" now measures the named finding, and a manifest signed before the member existed still verifies unchanged.

8. The delivery claim. Correct: the delivery hash rides beside the evidence, signed by nobody, bound to nothing. Both halves are done and posted (Section 5.1, MAY-T8-11): the countersignature claim map gains an optional deliveredHash label, so a payee who countersigns binds the delivered bytes to the exact issuer receipt bytes under their key, and the acceptanceCriteriaHash comparison becomes signed-to-signed - a mismatch is delivery-mismatch and fails the audit, a deliveredHash carried by an unattributable countersignature is discarded with it and cannot move the verdict (the break-6 invariant, re-measured here), and a countersignature without the member verifies exactly as before. The Introduction's "what bytes were delivered?" is conditioned on that evidence being present - without it the bundle answers the narrower question of what was promised. The claim was larger than the evidence; the claim shrinks and the evidence grows.

On your question: the intended answer is the one you inferred. In the retrospective audit, "allowed by policy" is the Receipt Issuer's signed assertion that the spend passed its gate under the stated policyHash; the Decision Token is consumed at the gate and the verification algorithm never sees it. You are right that the completeness side gets an independent leg (the rail extract) while the policy side does not, and -05 states that boundary in exactly those terms instead of leaving it to be inferred. A receipt-to-token binding for deployments that want independent verification is named as a possible extension, not smuggled into this revision.

Mechanical points, all three accepted. The vector violating Table 3 is the worst kind of bug - a normative example that breaks its own rule and teaches decoders to be lenient. The claim grammar (64 lowercase hex) is implemented and merged: every hash-shaped claim in the receipt, checkpoint and manifest claim tables runs through one grammar, signers and the audit refuse violations by name (malformed-policy-hash and its siblings), the test placeholders that taught the old leniency are replaced with computed digests, and the decoder still preserves foreign claims unchanged - validation and preservation are separate layers. The Appendix A vector is regenerated and re-signed with a computed digest in -05, and the divergence the conformance companion had registered against the posted -04 closed with the posting; the companion's counted-split list is empty again. Before posting, the vector was re-derived from the -05 text alone, without the codebase, and matched byte for byte. MAY-T8-9 becomes MAY-T8-10; the collision was a numbering error, the requirement text does not change. The lone-surrogate MAY is deleted: RFC 8785 3.2.2.2 terminates, and so do we. Before changing the encoder we measured that no existing vector or fixture byte carries a lone surrogate - that scan is now itself a guard - and then: the encoder refuses with a named refusal, producers will not sign what they cannot encode, and every verification surface reports an unreadable input as false with the refusal named beside it rather than throwing.

Section 18.1 of -05 lists every change against this list, and three more that came from readings of -05 itself before it was posted. The repair commits are on master at https://github.com/dogrucanemek-alt/cedulon (197ee90..e1740d1).

One ask, since -05 was written so that a verifier can be built from the text alone: the contribution kind the interop report still has no row for is an independent implementation with independent vectors. Your Python run of the -04 vectors is most of the way there. If you are willing to write the receipt and extract verification from the -05 text, without reading this codebase, I would treat where you stop as the next first-failure list.

Emek Can Dogru


On Sun, Aug 30, 2026 at 6:23 PM Tiago Pinto <tiago@donttrustverify.pt> wrote:
Hi Emek, all,

Taking up your offer from the thread on
draft-pinto-agent-authz-contestability. I put -04 through the same kind
of reading.

I ran the Appendix A vectors before reading for failure points. Both
Ed25519 signatures verify, the SPKI-derived kid is
06e3fd8fda29bb60, and deterministic re-encoding reproduces both
protected headers and payloads byte for byte. Run against the exact
IETF archive bytes, SHA-256
661755c600aede25451ce3a67df4a45d0d964c7b9196dc725dd310723eb8a49f,
under Python 3.14.4, cbor2 6.1.4, and cryptography 50.0.1.

My first failure is the transparency-receipt path.

1. I cannot implement steps 15 and 16 from the evidence the draft
defines.

Section 11.3 says that "A receipt binds a statement hash, not a
statement" and that "the verifier computes the statement hash of each
presented checkpoint itself."

I do not think that is the verification mechanism defined by RFC 9942.

RFC 9942 Section 5.2.1 requires the verifier first to apply the
inclusion proof to the bytes of the candidate entry. The resulting
Merkle Tree root becomes the detached COSE_Sign1 payload, and the
signature is then verified over that root.

For the SCITT use here, the candidate entry is the registered Signed
Statement, not merely the checkpoint COSE object carried inside it. The
audit evidence defined by -04 gives me the checkpoint, but I cannot find
a requirement to present the exact outer Signed Statement bytes that
were registered.

That leaves two separate failures for an implementation. I do not have
the candidate-entry bytes required to apply the RFC 9942 inclusion
proof. Even if I had them, step 15 tells me to verify the receipt
against the transparency key while the surrounding text describes this
as authenticating a statement hash. RFC 9942 instead requires candidate
entry, inclusion proof, Merkle root as the detached payload, and then
signature verification.

Section 18 already says that the current implementation treats a
receipt as a signature over a statement rather than as proof of log
membership, and that it has not been exercised against a real
Transparency Service. My point here is narrower: the same simplification
appears to have reached the protocol text and verification algorithm.

Therefore I cannot implement the RFC 9942 verification path from the
inputs and algorithm as currently stated.

2. I cannot determine the verification key or the resulting finding
consistently in the unpinned branch.

The Abstract says:

"No signed object may be verified against a key it carries itself"

MUST-T4-9 likewise requires the issuer public key out of band.

The finding table nevertheless describes the unpinned case as one in
which the receipt or checkpoint was checked using a key associated with
the presented object. The COSE profile itself carries kid, but no public
key. The unprotected header MUST be empty, and I cannot find a COSE_Key,
x5chain, or other field from which to obtain the candidate public key.

So in the no-pin case I cannot determine what key the signature is to
be checked against.

Step 4 then adds a second ambiguity. It first says to reject on Ed25519
failure or kid mismatch, and then says to apply the issuer root and
report issuer-key-mismatch.

For example, if kid differs from the configured issuer key, I cannot
tell whether that is a rejection only or an issuer-key-mismatch
finding. If kid matches but the signature is invalid under that key, I
cannot tell whether the resulting condition is issuer-key-mismatch,
receipt-chain-break, or something else.

Those choices matter because a rejected receipt is removed from the
attested set before reconciliation.

Therefore I cannot derive one key-resolution path and one finding set
from step 4.

3. I cannot construct one authenticated Rail Extract from the
specification.

Table 8 says:

"Each settlement record MUST contain"

ref, amount, currency, and timestampMs.

The Authentication text then says:

"The member names of that body are the rail's to define"

Those are different rules. If the Table 8 names are normative, a rail
cannot rename them. If the rail defines the names, Table 8 has not
defined the signed record shape.

I also cannot derive one complete signed JSON object containing the
record set, windowStartMs, windowEndMs, the account and rail identifiers
required by the scope rules, and the signature representation. Table 8
additionally uses tstr and uint, which are CBOR terminology, for a body
that the next section says is JSON.

Since the authenticated extract is the authoritative reconciliation
input, therefore I cannot construct or verify the same Rail Extract
from the text alone.

4. I cannot reconstruct one receipt order or one checkpoint-window
result.

Step 6 says:

"Walk the attested receipts in issuer order."

I cannot find a definition of issuer order.

Presentation order, timestampMs order, and hash-chain order can differ.
The draft itself notes that timestampMs is issuer-asserted, so temporal
order cannot safely be assumed to be chain order.

The same problem appears in chainHeadHash, which is the receipt hash of
the "last receipt in that window". I cannot determine what makes a
receipt last.

There are two further boundary cases in step 12. Every chained receipt
MUST fall in exactly one checkpoint window, but the document also
contemplates audits with no checkpoints. With receipts and zero
checkpoints, I cannot determine whether every receipt produces
window-coverage or whether that check is skipped. The same question
occurs for receipts in the currently open epoch after the last closed
checkpoint.

The dependency text also says "Nothing else in this list feeds another
step." Taken literally, that is not true. Step 14 consumes checkpoints
accepted by step 11, and steps 8 and 9 consume the index built by step
7. That matters because the same section permits evaluation in any
order only where the resulting finding set is unchanged.

Therefore I cannot derive one ordering rule, one definition of the
in-window chain head, or one result for zero-checkpoint and open-epoch
evidence.

5. Independent issuer and rail clocks can produce a completeness
failure for an honest payment.

Step 5 scopes receipts using the issuer's timestampMs. The extract is
scoped using the rail's timestampMs.

Consider a legitimate payment close to windowEndMs. If the rail records
it just before the boundary and the issuer records its receipt just
after, the settlement is inside the extract while its matching receipt
is outside the reconciliation set. The reverse can happen with the
clocks skewed the other way.

Step 8 then reports settlement-without-receipt or
receipt-without-settlement.

This is not a same-evidence interoperability difference. Both
verifiers can reach exactly the same false failure.

Therefore I cannot see how a deployment with independent honest clocks
avoids audit failures at window boundaries. I think one timestamp needs
to control reconciliation membership, or the boundary treatment needs
to be defined.

6. I can make an otherwise valid audit fail by appending a
countersignature.

Section 10.2 says:

"Anyone holding an honest receipt can append a countersignature of their
own"

That is the right observation.

But if the verifier has a payee key pinned, a bad countersignature or
one under the wrong key causes the audit to fail.

Take an honest issuer receipt that otherwise passes the audit. A party
presenting the evidence appends an arbitrary countersignature. The
issuer signature and receipt bytes have not changed, but the audit can
now fail.

That seems to give an appendable object that is not covered by the
issuer signature the ability to manufacture a negative result. It is
also close to the attack that the revised MUST-T8-9 avoids by refusing
to let unauthenticated evidence create an accusation.

Therefore I cannot see why a countersignature that cannot be attributed
to the pinned payee should fail the underlying audit rather than be
excluded with a finding or warning.

7. I cannot close the counterparty binding across manifest, receipt,
and rail extract.

The Trade Manifest does not identify the payee. The receipt does.

The manifest comparison checks amount, currency, and expiry. The rail
reconciliation checks ref, amount, and currency. The Rail Extract
record defined in Table 8 does not contain a payee.

Consider a valid manifest M published by A for 100 USD, a receipt
naming M but paying B, and an extract row matching the receipt's ref,
amount, and currency. The comparisons defined by the algorithm can all
succeed.

Perhaps a given rail guarantees that ref independently resolves to the
beneficiary. If Cedulon relies on that property, I cannot find it stated
as a required property of the rail profile.

Therefore I cannot determine from the defined evidence that the
counterparty named by the receipt is the counterparty to the offer and
settlement being audited.

8. I cannot establish "what bytes were delivered" from the Dispute
Evidence Bundle as defined.

The Introduction says the audit layer gives a machine-checkable answer
to:

"what bytes were delivered?"

The Trade Manifest commits to the expected bytes through
acceptanceCriteriaHash.

The Spend Receipt does not contain a delivery hash.

The Dispute Evidence Bundle then consists of the manifest, receipt, and
a delivery hash, but I cannot find a signature binding that delivery
hash to this receipt or transaction. The optional payee
countersignature signs the receipt, which also does not contain the
delivery hash.

So an auditor can verify a signed offer, a signed spend receipt, and a
hash supplied alongside them. I cannot see what establishes that the
third value was the hash of the bytes actually delivered in that
transaction.

Therefore I cannot derive the Introduction's delivery claim from the
evidence bundle. Either the claim is narrower than that wording, or the
observed delivery hash needs a signed transaction binding.

One question rather than a failure.

The Introduction also says Cedulon answers:

"was this spend allowed by policy"

I am unsure which claim is intended there.

Is the auditor intended to independently verify the PDP allow that
authorized the settlement, or to verify the Receipt Issuer's signed
assertion that the spend passed its gate under the stated policyHash?

As currently defined, I think it does the latter.

The Decision Token contains requestHash, policyHash, expiryMs, nonce,
and singleUseId, with requestHash covering the six request fields. The
Spend Receipt does not contain requestHash, singleUseId, a Decision
Token hash, or tool, and the verification algorithm does not consume a
Decision Token.

That can be a deliberate trust boundary. What made me stop on it is
that Cedulon explicitly does not trust the Receipt Issuer alone for
completeness, hence the authenticated rail extract, while I could not
find the equivalent statement that authorization in the retrospective
audit is an issuer assertion rather than independently verified PDP
evidence.

If the intended answer is the issuer assertion, I think stating that
boundary would resolve the question. If the intended answer is
independent verification of the PDP allow, I think a binding to the
Decision Token is missing.

Three mechanical points.

Appendix A makes its vectors normative on their own terms, but the
receipt vector carries policyHash = "aa" while Table 3 defines that
claim as lowercase hexadecimal SHA-256. I reproduced the vector as
signed. A validator enforcing the claim definition rejects it; a
decoder treating it as an unrestricted tstr accepts it.

T8 contains both MUST-T8-9 and MAY-T8-9, although the document defines
k as the sequence number within that threat. Those are two different
requirements sharing the same T8 sequence number.

The RFC 8785 lone-surrogate paragraph says an implementation MAY emit
the escaped form instead of failing. RFC 8785 Section 3.2.2.2 says
occurrences of lone surrogates MUST cause a compliant JCS implementation
to terminate with an error.

Those are my first failures on -04. I stopped here rather than turn this
into a prose review.

Best,

Tiago Pinto
Independent author and researcher, verifiable trust and AI governance 
https://donttrustverify.pt