Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
88919b9
feat(admin): add GetTransactionEvidence read RPC to ramp.admin.v1
EugeneDymo Aug 13, 2026
6afc585
fix(admin): apply code-review fixes to the transaction-evidence contract
EugeneDymo Aug 14, 2026
c8bb863
feat(admin): tenant-scope the evidence read; close review guard gaps
EugeneDymo Aug 14, 2026
d276299
fix(admin): apply the RAMP-239 review batch — evidence read hardening…
EugeneDymo Aug 14, 2026
544a338
Merge branch 'refs/heads/main' into feature/transaction-evidence
EugeneDymo Aug 14, 2026
4f1f3d2
fix(conformance): carry the RAMP-239 review-batch sources the merge l…
EugeneDymo Aug 14, 2026
4a57fe6
refactor(conformance): centralize rule-shape inspection in contract.g…
EugeneDymo Aug 14, 2026
d85de09
refactor(proto)!: align the evidence-row naming with the store and ra…
EugeneDymo Aug 14, 2026
53083a3
Merge main into feature/transaction-evidence
EugeneDymo Aug 14, 2026
13d64c1
fix(proto)!: correct false claims in the evidence-row contract and it…
EugeneDymo Aug 15, 2026
4bada15
fix(proto)!: enforce the signature and transaction_id rules the prose…
EugeneDymo Aug 15, 2026
d226995
fix(proto): correct store-shape claims the reference Exchange disproved
EugeneDymo Aug 15, 2026
3f3594c
fix(proto)!: move broker to the evidence row and give it explicit pre…
EugeneDymo Aug 15, 2026
c60cd02
fix(proto)!: bound broker to printable ASCII and list it as a self-as…
EugeneDymo Aug 15, 2026
9740b27
build(docs): generate the changelog page from proto/CHANGELOG.md
EugeneDymo Aug 15, 2026
1efc14a
docs(security): refile the admin-plane threats and fix the duplicate …
EugeneDymo Aug 15, 2026
095df79
fix(sdk/go): stop propagating a correlation id the admin plane cannot…
EugeneDymo Aug 15, 2026
70aa9d9
test(conformance): close four guards that were narrower than they cla…
EugeneDymo Aug 15, 2026
246dfea
fix(proto)!: settle the attestation signature encoding and sweep ever…
EugeneDymo Aug 15, 2026
ab28b93
fix(proto): the obligation state is a clean projection, with no carve…
EugeneDymo Aug 15, 2026
ad480a4
docs(edge): rewrite signed-URL verification around the two real schemes
EugeneDymo Aug 15, 2026
0982933
docs(edge): stop claiming the signer adds a transaction id to the URL
EugeneDymo Aug 15, 2026
c486522
test(sdk): derive wire-constant parity scope from the shared symbol map
EugeneDymo Aug 15, 2026
4dc799a
build(ci-local): run the sdk/python and sdk/ts suites, not just the g…
EugeneDymo Aug 15, 2026
ee85da9
docs(sdk): drop citations a reader of this repository cannot follow
EugeneDymo Aug 15, 2026
836b089
fix(sdk-types): keep the first paragraph of a proto comment in Python…
EugeneDymo Aug 15, 2026
b2f3382
fix(sdk-types,sdk/go,proto): stop a wire field vanishing from two cli…
EugeneDymo Aug 16, 2026
cbb5acf
fix(proto,conformance,sdk): bind the acceptance to its offer, and set…
EugeneDymo Aug 16, 2026
03ae701
fix(proto,conformance,sdk): catch a reused acceptance, and stop claim…
EugeneDymo Aug 16, 2026
a20858c
fix(scripts): keep repo-relative changelog links working on the docs …
EugeneDymo Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ research-*.md
__pycache__/
.sdk-types-work/
*.egg-info/

# IDE-generated resolver lock for the gen/python package manifest; the pipeline pins
# its real dependencies in scripts/sdk-types/requirements-*.txt (hash-locked), and an
# untracked file under gen/ fails the generated-drift gate.
gen/python/uv.lock
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Built on [IAB Tech Lab CoMP v1.0](https://github.com/IABTechLab/CoMP) and [RSL 1
```
proto/ Protocol buffer source — the wire format
ramp/v1/ RAMP messages and services
ramp/admin/v1/ AdminService — the Exchange operator/config plane
ramp/admin/v1/ AdminService — the Exchange operator plane (configuration + forensics)
comp/v1/ IAB CoMP v1.0 (1:1 mapping; included for reference)
buf.yaml Buf module config

Expand Down
216 changes: 216 additions & 0 deletions conformance/bytes_wire_forms_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Package conformance — bytes_wire_forms_test.go is the ORACLE half of the
// shared base64 truth table (testdata/bytes_wire_forms.json).
//
// The table says, for each bytes-rule field, which base64 wire forms are
// accepted. The Pydantic and Zod harnesses assert those verdicts against the
// generated schemas; this test asserts the SAME rows against Go — protojson's
// decoder plus protovalidate — which is what the generated patterns are supposed
// to mirror. Without it, the table would be a hand-written claim about Go's
// behavior on both client sides and nothing would check the claim: a row pinned
// from a wrong belief (that protojson accepts a mixed-alphabet string, say) would
// make both clients agree with each other and disagree with the server.
//
// It also keeps the shared bases honest: each one must be a VALID message, so it
// cannot drift into a shape where a "rejected" row is rejected for an unrelated
// reason.
package conformance

import (
"encoding/json"
"fmt"
"os"
"testing"

"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
protovalidate "buf.build/go/protovalidate"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)

type bytesWireForm struct {
Value string `json:"value"`
Accepted bool `json:"accepted"`
Why string `json:"why"`
}

type bytesWireVectors struct {
Bases map[string]map[string]any `json:"bases"`
FormSets map[string]struct {
Rule string `json:"rule"`
Forms []bytesWireForm `json:"forms"`
} `json:"form_sets"`
Fields []struct {
Message string `json:"message"`
Field string `json:"field"`
FormSet string `json:"form_set"`
} `json:"fields"`
}

func loadBytesWireVectors(t *testing.T) bytesWireVectors {
t.Helper()
raw, err := os.ReadFile("testdata/bytes_wire_forms.json")
if err != nil {
t.Fatalf("read bytes wire-form vectors: %v", err)
}
var v bytesWireVectors
if err := json.Unmarshal(raw, &v); err != nil {
t.Fatalf("decode bytes wire-form vectors: %v", err)
}
if len(v.Fields) == 0 {
t.Fatal("bytes wire-form vectors carry no fields — the shared table is wired to nothing")
}
return v
}

// parseBase applies one field override to a base and returns the message, plus
// whether protojson accepted the JSON at all (a malformed base64 string is
// rejected by the DECODER, before any rule runs).
func parseBase(t *testing.T, message string, base map[string]any, field string, value any) (proto.Message, bool) {
t.Helper()
obj := map[string]any{}
for k, v := range base {
obj[k] = v
}
if field != "" {
obj[field] = value
}
raw, err := json.Marshal(obj)
if err != nil {
t.Fatalf("marshal case json: %v", err)
}
mt, err := findContractMessage(message)
if err != nil {
t.Fatalf("resolve message %s: %v", message, err)
}
m := mt.New().Interface()
if err := protojson.Unmarshal(raw, m); err != nil {
return nil, false
}
return m, true
}

// TestBytesWireFormCoverageIsComplete derives the table's scope from the
// DESCRIPTOR instead of trusting the table to list itself.
//
// The base64 axis — padded vs unpadded, standard vs url-safe, mixed alphabet,
// pure padding — is the one axis the generated corpus cannot reach, because
// corpusgen emits values through protojson and therefore only ever produces the
// canonical padded standard form. testdata/bytes_wire_forms.json is the ONLY
// coverage those wire forms have, in all three languages.
//
// That made the table's completeness load-bearing and unchecked. Adding a sixth
// bytes-length field tightens the generated Pydantic/Zod pattern automatically,
// so the new field looks covered — while zero wire-form rows exercise it and
// every gate stays green. The failure is silent in exactly the place a reviewer
// would assume coverage exists.
//
// So this fails in BOTH directions, the shape TestRuleIdenticalGroupsAreDeclared
// already uses: a ruled field with no table entry, and a table entry naming a
// field that no longer carries the rule. It also pins the form set to the rule's
// VALUE, so a bytes.len = 64 field cannot quietly point at the 32-byte set and
// collect verdicts computed for a different length.
func TestBytesWireFormCoverageIsComplete(t *testing.T) {
vectors := loadBytesWireVectors(t)

// want: every bytes-length rule in the contract, keyed as the table keys it.
// Bare message names are safe as keys because AssertUniqueBareNames proves
// they are unique contract-wide.
want := map[string]string{}
EachRuleSet(func(md protoreflect.MessageDescriptor, fd protoreflect.FieldDescriptor, prefix string, fr *validate.FieldRules) {
r := MustBytesLength(fd, fr)
if r == nil {
return
}
if prefix != "" {
// A bytes-length rule under repeated.items. (or map values) is real
// but the table cannot express it: an entry overrides ONE scalar
// field on a base message, and the JSON value here would be a list.
// Fail rather than skip — skipping is how a guard silently narrows.
t.Errorf("%s carries a bytes-length rule at %s, which the wire-form table cannot express; "+
"extend the table format to override a repeated/map element before adding this rule",
fd.FullName(), prefix)
return
}
want[string(md.Name())+"."+string(fd.Name())] = fmt.Sprintf("bytes.%s = %d", r.Kind, r.Value)
})

got := map[string]string{}
for _, f := range vectors.Fields {
key := f.Message + "." + f.Field
if prev, dup := got[key]; dup {
t.Errorf("the table lists %s twice (form sets %q and %q) — one field, one form set", key, prev, f.FormSet)
continue
}
got[key] = f.FormSet
}

// Direction 1: every ruled field must be in the table.
for key, rule := range want {
formSet, listed := got[key]
if !listed {
t.Errorf("%s carries %s but has no entry in testdata/bytes_wire_forms.json — the generated "+
"patterns already enforce it in Pydantic and Zod, so with no rows nothing checks that the "+
"three languages agree on padding and alphabet", key, rule)
continue
}
// Direction 3: the named form set must be the one for THIS rule value.
set, defined := vectors.FormSets[formSet]
if !defined {
continue // TestBytesWireFormsMatchGo reports the undefined set
}
if set.Rule != rule {
t.Errorf("%s carries %s but points at form set %q, whose rows were computed for %q — "+
"the verdicts in that set do not describe this field", key, rule, formSet, set.Rule)
}
}

// Direction 2: every table entry must name a field that still carries a rule.
for key := range got {
if _, ruled := want[key]; !ruled {
t.Errorf("testdata/bytes_wire_forms.json lists %s, which carries no bytes-length rule in the "+
"contract — the rule was removed or the field renamed, and the rows now prove nothing", key)
}
}
}

func TestBytesWireFormsMatchGo(t *testing.T) {
vectors := loadBytesWireVectors(t)
v, err := protovalidate.New()
if err != nil {
t.Fatalf("protovalidate: %v", err)
}

// The bases must be valid on their own, or a rejected row proves nothing.
for message, base := range vectors.Bases {
m, ok := parseBase(t, message, base, "", nil)
if !ok {
t.Errorf("base for %s is not decodable proto-JSON", message)
continue
}
if err := v.Validate(m); err != nil {
t.Errorf("base for %s is not a valid message: %v — a 'rejected' row would then be rejected for the wrong reason", message, err)
}
}

for _, f := range vectors.Fields {
set, ok := vectors.FormSets[f.FormSet]
if !ok {
t.Errorf("%s.%s names form set %q, which the table does not define", f.Message, f.Field, f.FormSet)
continue
}
base, ok := vectors.Bases[f.Message]
if !ok {
t.Errorf("%s.%s has no base in the table", f.Message, f.Field)
continue
}
for _, form := range set.Forms {
m, decoded := parseBase(t, f.Message, base, f.Field, form.Value)
accepted := decoded && v.Validate(m) == nil
if accepted != form.Accepted {
t.Errorf("%s.%s = %q (%s, %s): the table says accepted=%v, Go says %v — the generated client patterns mirror Go, so fix the row or the rule, not the pattern",
f.Message, f.Field, form.Value, set.Rule, form.Why, form.Accepted, accepted)
}
}
}
}
Loading
Loading