Skip to content

RFC: Anchored evidence for SAFE - #18

Open
imran-siddique wants to merge 3 commits into
OpenSecureAIAlliance:mainfrom
imran-siddique:rfc-anchored-evidence
Open

RFC: Anchored evidence for SAFE#18
imran-siddique wants to merge 3 commits into
OpenSecureAIAlliance:mainfrom
imran-siddique:rfc-anchored-evidence

Conversation

@imran-siddique

Copy link
Copy Markdown

Adds rfc-anchored-evidence.md, a proposed addition to the SAFE draft.

What it proposes

SAFE's Evidence Preservation section specifies what members must retain. It does not specify any property establishing when that evidence came into being. Every item on the list is produced by systems the reporting member owns, held in storage the member controls, and provided on a schedule running from four business days to ninety, during which the questions the review will ask become progressively clearer. A reviewer today cannot distinguish evidence recorded as an incident unfolded from evidence assembled afterwards.

The RFC proposes one property: a reviewer should be able to establish, without trusting the member under review, that an evidence item existed in its provided form at a stated time. It then describes an RFC 6962 (Certificate Transparency) construction that satisfies it, and proposes an anchored / unanchored / unavailable status field on Evidence Preservation. The requirement is that the status is stated, not that it is anchored, on the same logic the thread in #11 applied to declared gaps.

Why it fits SAFE rather than being a general transparency proposal

  • The notification timeline is itself the window. Anchoring on day zero fixes evidence before the member can know what the review will focus on.
  • A hash discloses nothing, so a member can anchor evidence during an incident they are legally barred from describing. SAFE's confidentiality model and independent verification stop being a tradeoff.
  • Near misses, which the Reporting Compact requires and which have the weakest preservation incentive, become free to record.

Limits, stated in the RFC

Inclusion proves the exact bytes provided were recorded at that timestamp and cannot be quietly un-recorded. It does not validate signatures against a producer key, does not make the evidence true, does not establish completeness (the point @bobleer raised in #11), and does not authenticate the recording environment. It narrows the window in which evidence can be shaped rather than closing it.

Contribution offer

The reference registry exists and works, and its central weakness is that we operate it. Its own roadmap names the three gaps: production volume from a producer other than ours, a mirror operated by someone with no incentive to cover for us, and maintainers from outside the originating company. Those are things a consortium supplies and a vendor cannot.

The RFC therefore offers the registry to the alliance: anchor format specification, reference tooling, schema and CI validation, mirroring documentation, and the operational pipeline. Code is Apache-2.0, registry data is CC BY 4.0. We do not require that operation stay with us. If the working group would rather specify the requirement and have members implement it independently, that is also a good outcome.

Relationship to open issues

Builds on #11, which established the gap and converged on three determinations without a mechanism for the third. Complements #14 (provenance grades) and #6 (signed tool-trust verdicts), both of which produce evidence that can be anchored. Does not address #4; an anchored record of a fail-open verification is still a fail-open verification.

Raising as a PR rather than an issue since it adds a document, per CONTRIBUTING. Happy to move the discussion to an issue if the working group prefers that first. Commit is DCO signed off.

Proposes that SAFE record an anchoring status for each preserved evidence
item, so that a reviewer can establish an item existed in its provided form
at a stated time without trusting the member under review.

Describes an RFC 6962 construction that satisfies the requirement, states
what inclusion does and does not prove, and offers the reference registry
to the alliance so it is operated by someone other than its largest producer.

Builds on the discussion in issue OpenSecureAIAlliance#11.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
@lywinged

Copy link
Copy Markdown

This reads right to me, and asking for the status to be stated rather than requiring it be anchored is the part that makes it adoptable. Two things I got stuck on, both out of the canonicalization section.

The entry has ts, merkle_root, leaf_count, producer and batch_id, but nothing saying which canonicalization built its leaves. The RFC asks a verifier to use the rule declared for the context it is checking, and right now that is declared globally in docs/anchor-format.md, by revision. With one producer that is fine. With two, or after the format shifts, you can tell who produced a batch but not what rule they used, and entries from either side of the change are the same five fields meaning different things. A canonicalization field would cost one string. The repo is not public yet, so this may already be handled.

The other is excluding non-integer numbers. Since the leaf is the whole signed object, that is not really about how floats serialize, it means an evidence object containing one cannot be anchored. Evidence Preservation covers traces, tool calls and detection events, which carry latencies, costs and confidence scores, and the recommendations in From Lessons to Controls carry metrics. The workaround is rounding or stringifying before anchoring, and then the anchored bytes are not the bytes the reviewer holds.

On the reason given for it: RFC 8785 §3.2.2.3 does pin float serialization to ECMAScript Number::toString, and it is implementable. I have checked an implementation against a reference JCS library over a few hundred thousand values and they agree byte for byte, including where both refuse integers outside the safe range. Where it goes wrong is the exponent's leading zero and the thresholds where exponential form starts at each end. Happy to write those up as vectors if that is useful.

@zohebk8s

Copy link
Copy Markdown

Support this. The gap it names is real, and the sharpest version of it is the RFC's own reading of the Review Framework: the affected organization may correct factual errors but should not have veto power over learnings or recommendations, which assumes those facts can be established by someone other than the member holding them. Under the current draft they cannot be, and the proposal is scoped to that one property rather than to evidence handling in general.

Both points raised above look right to me, and I think they are the same point twice.

The RFC already says the two canonicalizations agree for records whose keys and strings are ASCII and whose numbers are integers, and that this is exactly what makes the divergence dangerous. Since the leaf is the whole signed object, putting the leaf on JCS as well follows that reasoning to its end: one rule instead of two, and the non-integer exclusion goes with it. That second part is the one that costs something today, because an evidence object carrying a latency or a confidence score cannot be anchored at all, and rounding or stringifying first breaks the property that the anchored bytes are the bytes the reviewer holds.

Naming the rule in the entry is worth doing whichever way that goes. A single rule still has revisions, and an entry cannot be reinterpreted after the fact, so the cheapest moment to record which rule built a batch is the moment the batch is written.

@qubeena07

Copy link
Copy Markdown

Both points make sense to me. On the canonicalization field, yeah, that's a gap. Right now the rule is just declared globally by doc revision, which is fine with one producer. But once you have two, or the format changes, you can't tell which rule built which entry just by looking at it. Adding one field to say which rule was used seems cheap compared to that risk.

The float thing is the one I keep thinking about though. Excluding non integer numbers isn't really solving a serialization problem, it's just saying certain evidence can't be anchored at all. And a lot of what this RFC covers, traces, tool calls, detection events, is going to have a score or a latency or a cost in it somewhere. If you have to round or stringify a number to get it anchored, then the thing you anchored isn't actually the thing you have anymore. That kind of defeats the point.

If RFC 8785 already handles this and the only real issues are small edge cases like exponent formatting, that sounds fixable rather than a reason to just ban floats. Would definitely want to see those test vectors if you write them up, that would make it a lot easier for people to just agree on the ECMAScript rule and move past this.

@CyberGuardian-XRSI

Copy link
Copy Markdown

The gap this addresses is real, and the timing argument is the sharpest version of it: the notification timeline is itself the shaping window, and anchoring on day zero fixes evidence before the member knows what the review will focus on. That is a stronger constraint than anything in the current draft.

The status-not-mandate framing is the right call and follows the same logic as declared gaps in #11: the requirement is that the state is stated, not that it is anchored. A member anchoring nothing has disclosed that, and a reviewer can weight it accordingly.

The near-miss point is undersold. Near misses have the weakest preservation incentive because nothing went wrong, so there is no trigger to preserve anything. A hash costs nothing and discloses nothing, which makes recording a near miss free. That connects directly to a gap in the Reporting Compact: near-miss reporting is required but there is no trigger that fires on one.

On the float exclusion, @lywinged and @zohebk8s have it right and it is the same problem twice. If an evidence object carrying a latency or confidence score cannot be anchored, and the workaround is rounding or stringifying before anchoring, then the anchored bytes are not the bytes the reviewer holds. That breaks the property the construction exists to provide, since Evidence Preservation explicitly covers traces, tool calls, and detection events, all of which carry non-integers. JCS on the leaf as well, as zohebk8s suggests, resolves both at once.

On independence: you have disclosed the central weakness clearly. The requirement should be specifiable independently of who operates the registry, so adoption does not depend on that governance question resolving first. The three gaps you name in the roadmap are the right three, and a consortium is the right structure to close them.

Disclosure: I am a technical contributor to the AARM specification, which produces tamper-evident receipts binding action, context, decision and outcome. I raise this as supporting comment rather than as advocacy for any particular implementation.

Reviewers on the PR converged on the same gap: the entry names who produced
a batch but not which rule built its leaves. Declaring the rule globally by
document revision holds with one producer and stops holding with two, or
after the rule is revised, because entries either side of the change carry
identical fields and mean different things.

Adds a `canonicalization` field to the entry, and points the verifier at it
rather than at the document revision in force when the batch was written.

Raised by @lywinged, seconded by @zohebk8s, @qubeena07 and @CyberGuardian-XRSI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
@imran-siddique

Copy link
Copy Markdown
Author

Thanks, all four of you. Taking the two points in the order they cost something.

The canonicalization field is in. @lywinged is right and @zohebk8s is right that it is worth doing whichever way the second question goes, because a single rule still has revisions and an entry cannot be reinterpreted after the fact. Pushed as a sixth field:

| canonicalization | string | Identifier of the rule that produced this batch's leaves |

The verifier now reads the rule off the batch instead of inferring it from the document revision in force when the batch was written. The repo is not public yet, so this was not already handled; it was a real gap.

On excluding non-integer numbers, you have all landed on the same thing and the reason stated in the RFC does not survive checking. I went to RFC 8785 rather than take it. Section 3.2.2.3 pins number serialization to ECMA-262 section 7.1.12.1 including the Note 2 rule, names V8 and Ryu as reference implementations, and Appendix B gives a sample set with the edge cases in it. The exponential-form threshold @lywinged names is visible there directly: 999999999999999900000 stays positional and the next value up serializes as 1e+21. So "cross-language float serialization is not canonical" is wrong as written, and the exclusion it justifies is doing real damage, because Evidence Preservation covers traces, tool calls and detection events and those carry latencies, costs and confidence scores. Rounding or stringifying first breaks the one property the construction exists to provide.

One correction, because it becomes a new trap if the leaf moves to JCS. @lywinged, JCS does not refuse integers outside the safe range. Appendix B note (1) says values meant as true integers SHOULD sit in -9007199254740991 to 9007199254740991 but adds that how numbers are used does not affect the algorithm, and note (2) has 2**68 serializing without complaint as 295147905179352830000. So an out-of-range integer canonicalizes cleanly and can lose precision on a round trip through a double, silently. That is the same failure class the RFC already flags: it passes every test the implementer writes and breaks when a second organization writes its own verifier. If the leaf goes to JCS the integer range has to be stated as a constraint on the evidence object, not left implicit.

@lywinged, yes to the vectors, please. Appendix B does not cover the exponent leading zero, and the thresholds at each end are the part everyone gets wrong independently. If you write them up I will run them against the reference verifier and put them in the repo as a conformance set, credited to you. That is more useful to this RFC than anything else on the list right now.

@CyberGuardian-XRSI on near misses, that is a better version of the point than the one I made. The Reporting Compact requires near-miss reporting and has no trigger that fires on one, and I had it as a benefit of anchoring rather than as a gap in the Compact. Worth raising separately from this PR since it stands whether or not anchoring is adopted.

What I am not doing unilaterally. Putting the leaf on JCS is the right direction and I am not going to pretend otherwise, but it changes every leaf hash the reference registry has already written, so it lands as a new rule identifier under the field above rather than as a rewrite. Existing entries keep saying which rule built them and stay verifiable; new batches move. I would rather the working group decide whether the RFC should specify JCS on the leaf now or state it as the intended revision pending the vectors, since that is a question about what SAFE requires and not about my implementation.

@zohebk8s, on the Review Framework reading: agreed, and that is the argument I should have led with. The clause says the affected organization may correct factual errors but has no veto over learnings, which only means something if those facts can be established by someone other than the member holding them.

@lywinged

Copy link
Copy Markdown

@imran-siddique You are right about the spec, and I was loose about what I had measured. I said both refuse out-of-range integers. What I had was one library's behaviour stated as if it were the algorithm's.

That library is rfc8785 0.1.4. It raises IntegerDomainError at exactly 9007199254740992. Appendix B gives the entry as IEEE 754 hex 4430000000000000 with output 295147905179352830000, and V8 agrees, so the library is the one deviating. It is also what agentrust-telemetry pins, under a profile it names rfc8785-jcs-v1.

Your line about stating the integer range as a constraint rather than leaving it implicit is carrying more than it looks like, and writing the vectors is what showed me why.

Appendix B note 2 settles what the algorithm does: even where an integer like 2**68 could be regarded as having extended precision, the serialization does not take that into consideration. It is a double. So there is one conformant answer here, not a choice, and the conformant answer is the one with the hazard in it. 9007199254740992 and 9007199254740993 are different integers that both serialize to 9007199254740992, so two different evidence objects produce identical canonical bytes and one leaf hash. Verified in V8. The output lands back inside the safe range and reads as ordinary.

That is what note 1's SHOULD is protecting against, and a SHOULD on producers does not protect a verifier. Conforming exactly is not sufficient, which is why the range has to be stated as a constraint on the evidence object.

Two shipped implementations get this wrong in opposite directions. rfc8785 0.1.4 refuses, so it never produces the collision, which makes the deviating implementation the fail-closed one. A big-integer language that skips the conversion, Python int for instance, prints 9007199254740993 exactly and diverges silently from V8 on the same object: neither raises, both produce well-formed bytes, and the two leaf hashes differ. That is the failure the RFC already names, passing every test written by the implementer and failing the first time a second organization writes its own verifier.

The vectors are here, on a branch of my fork rather than attached, so the runner sits with them:

https://github.com/lywinged/RFCs/tree/9324914/conformance/jcs-numbers

python run_vectors.py jcs-number-vectors.json

Point serialize at whatever your reference verifier uses. Fourteen settled vectors covering both thresholds and the exponent leading zero. Expected values agree across three oracles: rfc8785 0.1.4, V8, and a formatter I wrote from 7.1.12.1 without looking at either, and all three reproduce the Appendix B entry above. Every vector fails at least one of eight plausible-but-wrong serializers, which is the only reason I would say they measure anything. Using the host language's own repr fails eight of the fourteen.

Inputs are IEEE 754 hex, following Appendix B, so a vector does not depend on the parser reading it.

Five integer cases sit in a pending section with nothing asserted, because what to do about a conformant collision is yours and the working group's to decide rather than mine to encode.

Happy to open this as a PR against the RFC once it lands, or you can take the files.

@victor-davidenko

Copy link
Copy Markdown

This is a well-constructed proposal. The choice of RFC 6962 is right: Certificate Transparency has run at scale for over a decade with exactly the property this RFC needs (append-only, independently verifiable, no trust in the log operator required for verification). The honest limits section is particularly valuable. Stating clearly what inclusion does and does not prove prevents the proposal from being oversold during adoption, which is usually what kills transparency constructions in practice.

The anchored / un-anchored / unavailable status design is the detail that makes this adoptable. Requiring anchoring as a membership condition would create a compliance barrier that slows adoption. Requiring that the status be stated creates transparency about evidence quality without gatekeeping, and lets the review framework weight evidence accordingly. That is a pragmatic design choice.

One observation on what this proposal addresses and what sits adjacent to it. Anchoring establishes that the evidence existed in its current form at a stated time. It closes the window in which evidence can be shaped after the fact. It does not address a separate question: whether the evidence was produced by a party independent of the member under review.

Consider a member that produces evidence on its own infrastructure, signs it with its own keys, and anchors it immediately. The anchoring proves the evidence existed at that moment. It does not prove the evidence was produced by a system independent of the member's control. The member authored the evidence, anchored it, and is now presenting it for review. The temporal integrity is established. The custody independence is not.

You stated this clearly in your AAIF MCP Release Party presentation: verification running against the issuer's own key catches later tampering, but is weaker than it sounds as an answer to whether the issuer was honest at write time, and independent verification needs an anchor outside the issuer. Anchoring addresses the "later tampering" half. The "honest at write time" half is the custody-independence question.

These are two orthogonal properties. Temporal anchoring (this proposal) prevents after-the-fact fabrication. Custody-independent evidence production (where the signing keys, the governance evaluation, and the evidence storage are structurally separated from the governed party) prevents at-the-time fabrication. Both properties strengthen SAFE's evidence preservation, and neither substitutes for the other.

I raised the custody-independence question in #24 as part of a broader infrastructure-dependency argument. The evidence integrity section there describes an architectural approach where governance infrastructure independent of the reporting member produces and signs the evidence, so that the member cannot author or modify records of its own governance decisions. Your anchoring proposal and that custody model compose naturally: evidence produced by an independent governance system and anchored in a transparency log has both properties simultaneously.

The contribution offer to the alliance is the right move. A transparency registry operated by its largest evidence producer has exactly the conflict of interest you named. Moving operation to the consortium resolves it structurally.

I would be interested in how you see the relationship between anchoring and the evidence-production architecture. The combination of the two (independent production plus temporal anchoring) seems to produce a stronger evidence foundation than either property alone, and both are feasible today with existing infrastructure.

@imran-siddique

Copy link
Copy Markdown
Author

@victor-davidenko, I see them as two separate checks that work best together.

  • Independent production asks: who created the evidence, and could the party being reviewed control it?
  • Anchoring asks: did this exact evidence exist by a given time, and has it changed since?

Independent production makes it harder to create a false record at the start. Anchoring makes it harder to rewrite or replace that record later. Neither one proves that every statement inside the record is true.

The strongest setup is therefore simple: an independent system creates and signs the evidence, its hash is anchored outside that system, and the reviewer checks both the producer's signature and the anchor proof. The RFC should name these as separate properties so an anchored status is never mistaken for independently produced.

@lywinged, thank you for the vectors and the runner. Please open them as a PR against this RFC. Keeping your commit history and explanation with the files will make the review much easier. I agree that the five integer cases should stay pending until the RFC states the allowed number range and the verifier's required behavior.

@victor-davidenko

Copy link
Copy Markdown

Agreed on both the distinction and the combined model. One question on how the RFC operationalizes the separation.

If the RFC adds a field for production independence alongside the anchoring status, the field is a declaration: the member states whether its evidence was independently produced. The problem is that the declaration is made by the same party whose independence is in question. A governance platform that produces and signs its own evidence can write independent in the field as easily as it can write self-produced. The field adds vocabulary without adding verifiable trust. It is self-attestation about whether you self-attest.

Anchoring avoids this problem because it has a structural enforcement mechanism: the Certificate Transparency log is operated independently, inclusion proofs are mathematically verifiable, and the member cannot fake inclusion without the log operator's cooperation. The trust is in the construction, not in the declaration.

For production independence to have the same verifiable quality, the claim needs a similar structural anchor. One path: the governance infrastructure's ability to claim independent is tied to a certification issued before the evidence was produced, by a body that evaluated the production architecture. The evidence carries the signature of a certified entity. The reviewer verifies the certification chain, not a self-declared field.

This introduces a question the RFC does not need to answer fully but should name: what certifies the evidence producer's independence? Product architecture certification (the design was evaluated) and deployment configuration certification (the running instance preserves the design properties) are different evaluations that may require different evaluators. The combination of the two establishes that the evidence was produced by an infrastructure whose independence was verified before the incident, not declared after it.

The anchoring proposal and this certification question compose naturally. A reviewer who can verify three things:
(1) the evidence was signed by a certified-independent producer, (2) the producer's certification was valid at the time of signing, and (3) the signed evidence was anchored in a transparency log at a stated time - has an evidence integrity chain that neither the member nor the governance infrastructure can fabricate after the fact. Each property is independently verifiable. None depends on trusting the member under review.

@carloshvp

Copy link
Copy Markdown

Strong support for this proposal.

From a governance perspective, there is an important distinction between evidence that has been retained and evidence whose existence at a given point in time can be independently established. Without that property, reviewers still depend on the organization under review for the integrity of the timeline.

I particularly support the anchored / unanchored / unavailable approach. Requiring organizations to declare the status, rather than mandating anchoring from day one, makes the mechanism practical while still giving reviewers materially better information about the strength of the evidence they are assessing.

I also think keeping the requirement independent from any particular registry operator is important. SAFE should define the assurance property first, while allowing the ecosystem to evolve around interoperable implementations and independent operators.

@imran-siddique

Copy link
Copy Markdown
Author

@victor-davidenko You are right and I am dropping the field as I described it. "Independent: true" written by the party whose independence is in question carries no verifiable content, and putting it next to a status that does have structural enforcement would borrow credibility it has not earned. That is a fair hit.

Where I would push slightly is on certification as the structural anchor. A certificate is also a declaration, made by a different party at a different time. It establishes that an evaluator looked at an architecture on a date. It does not establish that the instance which produced this evidence is that architecture, which is the half you correctly separate out as deployment configuration. So certification moves the trust rather than removing it, and the RFC should not treat it as equivalent to an inclusion proof.

What I think survives is narrowing the claim to what a reviewer can actually check. Rather than a conclusion field, state the basis and let the reviewer draw the conclusion:

  • self - produced and signed by the member under review. No independence claimed.
  • distinct-party - signed by a key demonstrably not controlled by the member under review. Weaker than "independent" and it is checkable, which the stronger word is not.
  • certified - signed by a producer whose architecture was evaluated before the evidence was produced, with the certification chain and its validity window resolvable by the reviewer. Your path.
  • attested - the producing environment's measurement is carried with the evidence and verifiable against a hardware vendor root, so the reviewer checks what code was resident rather than what was certified.

Each value names an artifact the reviewer fetches and checks. None of them asserts a verdict. That is the same design as anchored / unanchored / unavailable: the requirement is that the basis is stated, not that it is the strong one.

One limit I want in the RFC rather than discovered later. attested is not custody-grade against the party that owns the machine. Published physical attacks on the memory bus recover secrets from SEV-SNP and TDX environments, so remote attestation raises the cost against a remote or software adversary and does not close it against a member with physical access to their own hardware. That is the same shape as the honest-limits section already in the RFC: the composed chain narrows the window in which evidence can be shaped, it does not close it. I would rather state that than let attested be read as proof.

On your closing question, I agree the RFC should name it and not answer it. Product architecture evaluation and deployment configuration evaluation are genuinely different evaluations, and the second one is where every scheme I have seen gets thin, because it is a continuous property being checked at audit-time intervals. Attestation is the only mechanism I know of that samples it continuously, which is why I put it in the list, and it comes with the caveat above rather than instead of it.

To be precise about what changes: the RFC as it stands has only the anchoring status, so nothing has to be removed. What I will add is a producer-basis field carrying the four values above, a limits paragraph covering the physical-access caveat, and the certification question named as open under related issues rather than resolved here. That leaves three verifiable properties which compose and none of which requires trusting the member under review: produced on a stated basis, signed by a resolvable key, anchored at a stated time.

@imran-siddique

imran-siddique commented Aug 28, 2026

Copy link
Copy Markdown
Author

One process note to add. PR #27 opened today with a chain-of-custody receipt profile that keeps producer independence and temporal anchoring separately reportable, but it provides no certification mechanism and does not reference this PR. Before adding more schema here, I think the working group should decide whether PR #18 remains the temporal-anchoring component used by that profile or whether the anchoring text moves into PR #27. I would rather have one composable mechanism than two overlapping ones.

…tion

Addresses review discussion on the PR. A field declaring the producer
"independent" would be a self-attestation by the party whose independence
is in question, so state the basis (self / distinct-party / certified /
attested) and leave the conclusion to the reviewer.

Also records that hardware attestation is not custody-grade against a
member with physical access to their own hardware, and names the open
question of what certifies a producer's independence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012yyHFd4YA35oNUjhrztJSb
Signed-off-by: Imran Siddique <imran.siddique.microsoft@gmail.com>
@victor-davidenko

Copy link
Copy Markdown

The producer-basis taxonomy is the right structural choice, and dropping the independence field was correct. One thing I notice when applying the taxonomy to a regulated audit: governance independence is not a single property a field can carry. It is a composed conclusion a reviewer draws from several independent checks. Walking a concrete case makes this visible.

Concrete scenario. A bank operates governance infrastructure for its agentic workflows. Agents run in TEE-attested execution environments that produce runtime attestation records verifiable against a hardware vendor root. A governance platform, also running in a TEE-attested environment, intercepts agent actions, evaluates them against policy, and produces signed, cryptographically chained governance evidence records. Runtime attestation records are stored and retrievable independently of the governance platform. Governance signing keys are in the bank's key management system, not in the governance platform. Governance evidence is in the bank's storage, not the governance platform's storage.

The reviewer checks four properties independently:

Infrastructure identity. The governance platform presents a verifiable identity credential the auditor can compare against the bank's authorized operating documentation. This tells the auditor which system produced the evidence.
Evidence integrity. Signatures verify against the bank's key management public key. The cryptographic chain is intact. Each record contains the policy hash, action data, and governance decision. Mathematically verifiable without trusting either party.
Signing key separation. The signing keys are held in the bank's key management infrastructure, not in the governance platform. Maps to distinct-party.
Runtime attestation. A TEE measurement proves the governance code was genuine and unmodified at execution time. Maps to attested.

The reviewer's independence conclusion is the composition of all four checks, not any single one. A governance-independent or custody-separated field would have the same problem as the original independent: true: any platform can populate it. The verifiable content lives in the individual checks, each mapping to an artifact the reviewer fetches and evaluates. The RFC could usefully name this composed evaluation pattern so reviewers in regulated contexts have a standard path through the taxonomy rather than discovering it case by case.

@DmitrL-dev

Copy link
Copy Markdown

@victor-davidenko @imran-siddique The composed evaluation is useful, but the bank example exposes a distinction the current producer-basis wording needs to preserve: separated from whom, and with respect to which capability?

If the bank is the member under review, a key held in the bank's KMS does not by itself satisfy this draft's distinct-party definition. It establishes separation from the governance platform's key storage, not necessarily from the bank's control. Non-exportability also does not answer who can request signatures or change the signing policy. If the party under review is instead the platform operator, that is a different relationship and should be stated explicitly.

Reading the current draft at 74785a7, I would make the checks composable rather than an ordered four-value assurance ladder:

  • Producer relationship and signing control: identify the reviewed party, producer, key custodian, permitted signing principals, and evidence supporting the claimed separation.
  • Certification: identify the evaluated architecture/deployment, evaluator, scope and relevant validity evidence.
  • Runtime attestation: retain the measurement, reference values/appraisal policy, freshness evidence, and binding to the producer key and evidence-producing instance.
  • Temporal anchoring: verify inclusion and the independently checkable basis for the timing/history claim, separately from the producer checks.

A self-operated producer can be attested; a distinct-party producer can also be certified and attested. Those combinations should remain representable without implying that attestation outranks organizational separation or that either proves complete capture.

I would also narrow “genuine and unmodified at execution time.” Attestation supports an appraisal of measured state under declared roots and freshness assumptions; it is not automatically continuous observation of every event. RFC 9334's trust model and freshness discussion make those dependencies explicit. For this profile, the reviewer still needs to determine what was measured and how it was bound to this record. Capture completeness and the correctness of the governance decision remain separate claims.

Likewise, Merkle inclusion alone does not establish an honest wall-clock timestamp or exclude a split view. The anchoring result should identify the checkpoint/time witness and history checks actually used; RFC 6962 §7.3 explicitly treats conflicting log views as something auditors must detect.

Candidate conformance cases: a bank-controlled KMS must not automatically become distinct-party from that bank; a valid but stale or wrong-instance attestation must not establish this record's runtime basis; valid inclusion must not silently upgrade an unwitnessed timing claim.

On consolidation, my #27 response still applies: #18 owns temporal anchoring and producer-basis semantics; the custody profile surrounds it with linked handling, transformation and durable acceptance. The branches are still separate; this comment does not claim that consolidation has landed.

@lywinged

lywinged commented Sep 5, 2026

Copy link
Copy Markdown

The five pending cases in the number vectors (imran-siddique#1) wait on one sentence, the allowed integer range and what a verifier does outside it, so here is a candidate in the RFC's own terms, for the canonicalization section of the draft at 74785a7:

Under a canonicalization rule that applies RFC 8785, every integer in an evidence object lies within -9007199254740991 to 9007199254740991, the range RFC 8785 Appendix B note 1 sets as a SHOULD for values meant as true integers. A verifier that finds an integer outside that range rejects the object rather than canonicalizing it. The reason is Appendix B note 2: serialization goes through a double, so 9007199254740992 and 9007199254740993, two distinct integers, canonicalize to the same bytes and so to the same leaf. A value that needs to be larger is carried as a JSON string, which Appendix D requires of any number without a natural place in JSON. The bound is 2^53 minus 1 rather than 2^53 because a verifier whose only number type is the double sees the parsed value and not the instance: it reads 9007199254740993 as 9007199254740992, and under a bound of 2^53 would admit the one value the range exists to exclude.

Every reference in it is to the RFC 8785 text, and the collision is the fourth pending vector, verified in V8. agentrust-io/trace-spec adopted the same bound as a MUST in section 3.2.2 of trace-v0.2 for the same reason, so a member implementing both meets one rule rather than two.

With the range stated, the pending five settle: the boundary value canonicalizes, the four above it are rejections, and the refusal rfc8785 0.1.4 already makes at 2^53 becomes the required behaviour rather than a divergence from the algorithm. The sentence applies under a rule identifier that names RFC 8785. The current sorted-key ASCII rule excludes non-integers and the RFC text says nothing about the range of the integers it keeps, so it needs the same bound whichever way the leaf question goes.

@DmitrL-dev

Copy link
Copy Markdown

@lywinged I reproduced the adjacent-integer collision and the reason for choosing 2^53 - 1, using Node v24.19.0 / V8 13.6.233.17-node.51. The remaining wording decision is what integer means at the evidence-admission boundary, separately from the serializer.

Using the 14 settled IEEE-754 inputs from your commit 932491400e15f5cef55c8b31f94e1cd3417db1c0, decoded with Buffer.readDoubleBE, JSON.stringify reproduced all 14 expected number serializations. Applying the global rejection test Number.isInteger(v) && !Number.isSafeInteger(v) rejects six of those same inputs: the four upper positional/exponential cases, 1e100, and the maximum finite double. Each is integer-valued as a binary64 number, even if a host language calls it a float.

That is not a reason to discard the bound. It is a reason to keep two suites:

  • Serialization conformance: the existing expected bytes remain correct for the RFC 8785 algorithm.
  • Evidence-profile admission: the proposed stronger domain rule has its own accept/reject expectations, checked before producing or accepting a leaf. A serialization test passing is not evidence that the profile admits that input.

If the intended rule is global, I would say “integer-valued JSON numbers, regardless of their decimal/exponent spelling or host numeric type.” If it applies only to schema-declared exact-integer fields, the profile needs to identify that schema/type evidence; a verifier cannot recover exact-integer intent from an already-rounded double. Appendix B note 1 distinguishes intended integer use from the serialization algorithm itself.

For the actual collision, both raw objects still produced leaf 3f9d3e6edd300dd569ec99916ec6270597c7b07cea1602f4ce5216b01bac65ff. A bound of 2^53 accepted both parsed values; 2^53 - 1 rejected both. Equivalent spellings such as 9007199254740992.0 and 9.007199254740992e15, and their negative counterparts, should be admission cases too. The original integer token spelling must not decide the outcome.

One small correction to the pending-vector explanation: 2^68 is exactly representable in binary64. Its shortest decimal spelling round-trips to that same binary64 value; the changed decimal digits are not themselves a floating-point loss of 4144. Preserving an exact mathematical integer in another numeric model is a separate requirement and a reason to encode it as a string. Also, the collided value 9007199254740992 lies outside the safe-integer range, not back inside it.

This checks the numeric primitive and the fixed sorted-key collision object, not the private reference registry or an end-to-end anchoring implementation. I would settle the five pending cases in an explicitly named admission profile while retaining the raw serializer tests separately.

lywinged added a commit to lywinged/RFCs that referenced this pull request Sep 5, 2026
…ements

Two corrections from review on OpenSecureAIAlliance#18. 2**68 is a power
of two and exactly representable in binary64; what the algorithm changes is the
spelling, and the 4144 is the gap between the shortest round-tripping decimal
and the integer, not a floating-point loss. And 9007199254740992 is 2**53, just
outside the safe range, not inside it; that is exactly why the bound is 2**53 - 1.

The five pending cases settle in a named admission profile, safe-integer-admission,
kept separate from the serialization vectors: a serialization vector says what
bytes RFC 8785 produces, an admission case says whether an evidence profile
accepts the value at all. Eighteen cases: the boundary in both signs, 2**53 and
2**53 + 1 and 2**68, the same values spelled with a fraction and in exponent form
and negated, the six settled inputs that serialize correctly and are integer-valued
doubles above the range, three non-integer values the rule does not reach, and negative zero.
Admission is decided on the value, not the token's spelling. The runner checks
both suites and its exit code covers both; the former pending cases are kept as
observations of what shipped serializers do.

Signed-off-by: Louielunz <48041247+lywinged@users.noreply.github.com>
lywinged added a commit to lywinged/RFCs that referenced this pull request Sep 5, 2026
…ements

Two corrections from review on OpenSecureAIAlliance#18. 2**68 is a power
of two and exactly representable in binary64; what the algorithm changes is the
spelling, and the 4144 is the gap between the shortest round-tripping decimal
and the integer, not a floating-point loss. And 9007199254740992 is 2**53, just
outside the safe range, not inside it; that is exactly why the bound is 2**53 - 1.

The five pending cases settle in a named admission profile, safe-integer-admission,
kept separate from the serialization vectors: a serialization vector says what
bytes RFC 8785 produces, an admission case says whether an evidence profile
accepts the value at all. Eighteen cases: the boundary in both signs, 2**53 and
2**53 + 1 and 2**68, the same values spelled with a fraction and in exponent form
and negated, the six settled inputs that serialize correctly and are integer-valued
doubles above the range, three non-integer values the rule does not reach, and negative zero.
Admission is decided on the value, not the token's spelling. The runner checks
both suites and its exit code covers both; the former pending cases are kept as
observations of what shipped serializers do.

Signed-off-by: Louielunz <48041247+lywinged@users.noreply.github.com>
lywinged added a commit to lywinged/RFCs that referenced this pull request Sep 5, 2026
…ements

Two corrections from review on OpenSecureAIAlliance#18. 2**68 is a power
of two and exactly representable in binary64; what the algorithm changes is the
spelling, and the 4144 is the gap between the shortest round-tripping decimal
and the integer, not a floating-point loss. And 9007199254740992 is 2**53, just
outside the safe range, not inside it; that is exactly why the bound is 2**53 - 1.

The five pending cases settle in a named admission profile, safe-integer-admission,
kept separate from the serialization vectors: a serialization vector says what
bytes RFC 8785 produces, an admission case says whether an evidence profile
accepts the value at all. Twenty cases: the boundary in both signs, 2**53 and
2**53 + 1 and 2**68, the same values spelled with a fraction and in exponent form
and negated, the six settled inputs that serialize correctly and are integer-valued
doubles above the range, three non-integer values the rule does not reach, and negative zero.
Admission is decided on the value, not the token's spelling. The runner checks
both suites and its exit code covers both; the former pending cases are kept as
observations of what shipped serializers do.

Signed-off-by: Louielunz <48041247+lywinged@users.noreply.github.com>
@lywinged

lywinged commented Sep 5, 2026

Copy link
Copy Markdown

@DmitrL-dev Both corrections are right and are in the branch now, at e2327b2 on jcs-number-vectors: 2^68 is a power of two and exactly representable, so the 4144 is the gap between the shortest round-tripping decimal and the integer, not a floating-point loss; and 9007199254740992 is 2^53, outside the safe range, which is the reason the bound is 2^53 minus 1 and not a detail the earlier text should have got backwards. Your leaf 3f9d3e6e... reproduces here as SHA-256 over 0x00 followed by the sorted-key bytes, the RFC 6962 leaf form.

The two-suite split is the right resolution and the branch now has it. The fourteen serialization vectors are untouched. A named admission profile, safe-integer-admission, marked as proposed and not adopted, carries twenty cases: the boundary in both signs, 2^53, 2^53 plus 1 and 2^68, the same values spelled 9007199254740992.0, 9.007199254740992e15 and negated, 9007199254740991.0 as the in-range spelling control, the six settled inputs you identified, which serialize correctly and are not admissible, three non-integer values the rule does not reach, and negative zero. The former pending five are settled inside it and kept as observations of what shipped serializers do. The runner checks both suites and fails on either.

On the wording, I would take your global form. The sentence in the branch reads: every integer-valued JSON number in an evidence object, whatever its spelling or the host's numeric type, lies within -9007199254740991 to 9007199254740991; a verifier that finds one outside that range rejects the object before canonicalizing it; numbers that are not integer-valued are outside the rule. One consequence worth stating in the RFC rather than leaving to be derived: every double of magnitude 2^53 or more is integer-valued, so the rule admits no JSON number of that magnitude, and a latency of 1e16 nanoseconds is carried as a string like any other exact quantity that size. The reference check parses the token exactly and decides on the value; a verifier that parses to a double first reaches the same twenty answers, because each rejected value stays rejected after rounding.

Tool-assisted: the runs and this write-up.

@DmitrL-dev

Copy link
Copy Markdown

@lywinged I checked e2327b2: all fourteen number-serialization expectations reproduce in Node v24.19.0 / V8 13.6.233.17-node.51, and all twenty published admission cases match both your Decimal admission function (Python 3.9.6) and the binary64 predicate. The two-suite change and the stated twenty-case agreement are confirmed.

There is one remaining ambiguity in the wording I suggested: integer-valued in which numeric domain? The exact-token reference and a double-first check differ just outside the current cases:

JSON number token Your exact-token admit() Parsed binary64 value Binary64 safe-integer check
9007199254740991.5 admit: not an integer 9007199254740992 reject
-9007199254740991.5 admit: not an integer -9007199254740992 reject

9.0071992547409915e15 and its negative counterpart reproduce the same disagreement. As controls, 9007199254740991.25 and its negative counterpart round inward and both checks admit them. I exercised all six additional tokens against the admission function from your pinned runner, not a reimplementation of the Decimal check.

This also means that the exact-token check admits the .5 input but rejects the number spelling emitted by JCS/V8 for that input. The twenty-vector result is correct; it does not establish that the two admission domains are interchangeable.

For the proposed global profile, I suggest making the domain explicit: validate/convert the input to the IEEE 754 binary64 value used for JCS, reject non-finite values, then reject any integer-valued value outside [-(2^53 - 1), 2^53 - 1], before canonicalization and hashing. This specifies the admission boundary on the same numeric representation used by RFC 8785 sections 3.1 and 3.2.2.3; exact quantities beyond that representation belong in strings. The corresponding finite-value predicate is:

Number.isFinite(v) && (!Number.isInteger(v) || Number.isSafeInteger(v))

If the working group instead wants an exact-decimal input policy, that is a different profile: raw tokens and its interaction with subsequent JCS rounding must remain explicit. I would add these four outward-rounding cases and the two inward-rounding controls before treating the reference check as cross-runtime admission conformance. This is still a proposed profile and a numeric-primitive check, not an end-to-end anchoring result.

lywinged added a commit to lywinged/RFCs that referenced this pull request Sep 5, 2026
Review on OpenSecureAIAlliance#18 found the two domains disagree just
outside the safe range: 9007199254740991.5 is not an integer as a decimal, so an
exact-decimal check admits it, but it rounds to 9007199254740992 as a double,
which is what JCS serializes, and that value is rejected. An exact-decimal rule
admits an input whose canonical spelling it rejects. The profile now states its
domain: the token is taken as the binary64 value RFC 8785 serializes, non-finite
is rejected, then the integer-valued test and the range apply. The reference
check in the runner does the same.

Seven cases added, twenty-seven in all: four outward-rounding tokens in both
signs and both spellings, rejected; two inward-rounding controls at .25, admitted
under either rule; and 1e999, not finite as a double. The twenty earlier cases
agree under both domains and are unchanged.

Signed-off-by: Louielunz <48041247+lywinged@users.noreply.github.com>
@lywinged

lywinged commented Sep 5, 2026

Copy link
Copy Markdown

@DmitrL-dev Confirmed, and the domain was the gap. 9007199254740991.5 is not an integer as a decimal, so the exact-token check admitted it; as a double it is 9007199254740992, which is what JCS serializes, and that value is rejected. An admission rule that admits an input whose canonical spelling it rejects is on the wrong representation. The profile now decides on the binary64 value, in 8783ca1 on jcs-number-vectors: the token is taken as the double RFC 8785 sections 3.1 and 3.2.2.3 serialize, a token that is not finite as a double is rejected, then the integer-valued test and the range apply. The runner's reference check does the same, and the README carries your predicate as the JavaScript form.

Your seven cases are in, twenty-seven in all: the four outward-rounding tokens in both signs and both spellings, rejected; the two .25 controls, admitted under either rule; and 1e999, rejected as not finite. The twenty earlier cases are unchanged, and every one of the twenty-seven matches Number.isFinite(v) && (!Number.isInteger(v) || Number.isSafeInteger(v)) in Node here. My earlier sentence that a double-first verifier reaches the same answers was true of the twenty and not of the domain, as you said. Thank you for running the check against the pinned runner rather than a reimplementation, and for looking one ulp outside the cases; that is where the two domains part, and I had tested agreement only on the inputs I had chosen.

On the exact-decimal alternative: a producer that wants a value carried exactly, at any size, has the string form under Appendix D, so I would not give the RFC a second profile for it. The rule text in the branch reads: every JSON number in an evidence object is taken as the IEEE 754 binary64 value RFC 8785 serializes, a token that does not convert to a finite double is rejected, every integer-valued such value lies within -9007199254740991 to 9007199254740991 whatever its spelling or the host's numeric type, and a verifier that finds one outside that range rejects the object before canonicalizing it.

Tool-assisted: the runs and this write-up.

@DmitrL-dev

Copy link
Copy Markdown

@lywinged Confirmed on the pinned 8783ca1036a16d0f7b497275e68997181ac73dcd vectors, with a paired baseline at e2327b221efd5dc71cbabbc7c02df1726edf6160.

On the same Linux x86_64 environment (Python 3.11.2; Node v22.20.0 / V8 12.4.254.21-node.33):

  • The old published admit() matches 23/27 expectations and incorrectly admits exactly the four outward-rounding cases.
  • The updated published admit() matches 27/27.
  • The JavaScript finite/binary64 predicate matches 27/27.
  • All fourteen serialization expectations still reproduce from the IEEE-754 hex inputs with Buffer.readDoubleBE() and JSON.stringify().

For the Python comparison I executed each pinned source's admit() definition and SAFE assignment, extracted unchanged from its AST, against the same 27-case fixture; I did not reimplement the Decimal baseline or substitute its result. The fixture SHA-256 is 73cd1b52ec58df51a8e5fb7d167b9da0fb70ae5b7eacec6b922d360442b52ec5.

That closes the admission-domain discrepancy I reported, within this fixed numeric suite. It is not a full JSON/JCS verifier, registry interoperability, or end-to-end anchoring result, and the admission profile remains proposed rather than adopted.

Tool-assisted verification and write-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants