Skip to content
This repository was archived by the owner on Sep 11, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
182 changes: 182 additions & 0 deletions conformance/manifest_version_rule_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package conformance

// Drift guard: the manifest-version rule the SDKs enforce IS the rule the contract
// states.
//
// WellKnownManifest.ver carries a normative receive-side rule — accept a recognised
// MAJOR, reject an unrecognised MAJOR, a malformed value, and an absent field — and
// it deliberately carries no protovalidate rule: a pattern would make the field
// structurally required on every message that embeds the manifest, and an exact
// match would refuse the additive minor revisions the rule exists to admit. So the
// contract's whole statement of the rule is prose in the field's comment, and the
// enforcement is code in three SDK endpoint resolvers.
//
// Between them sits the shared corpus, which pins the three implementations to each
// other. This file pins both to the prose, the same construction as
// endpoint_rule_test.go and for the same reason: both halves are DATA READS, and
// nothing here imports sdk/go.

import (
"encoding/json"
"errors"
"os"
"path/filepath"
"strings"
"sync"
"testing"
)

// manifestVersionVectors is the SDK's committed verdict set for the rule, read as data.
const manifestVersionVectors = "../sdk/go/helpers/testdata/manifest-version-vectors.json"

type manifestVersionCase struct {
Name string `json:"name"`
Ver string `json:"ver"`
Present bool `json:"present"`
Accepted bool `json:"accepted"`
}

var errNoManifestVersionVectors = errors.New(
"the manifest-version corpus is missing or empty — regenerate it with " +
"RAMP_UPDATE_VECTORS=1 go test ./sdk/go/helpers/ -run TestGenerateManifestVersionVectors")

var sdkManifestVersionCases = sync.OnceValues(func() ([]manifestVersionCase, error) {
b, err := os.ReadFile(manifestVersionVectors)
if err != nil {
return nil, err
}
var doc struct {
Cases []manifestVersionCase `json:"manifest_version"`
}
if err := json.Unmarshal(b, &doc); err != nil {
return nil, err
}
if len(doc.Cases) == 0 {
return nil, errNoManifestVersionVectors
}
return doc.Cases, nil
})

func mustManifestVersionCases(t *testing.T) []manifestVersionCase {
t.Helper()
cases, err := sdkManifestVersionCases()
if err != nil {
t.Fatalf("read %s: %v", manifestVersionVectors, err)
}
return cases
}

// manifestVerComment is the comment block above WellKnownManifest.ver specifically.
// `string ver = 1` appears on every message, so the shared fieldComment extractor
// (first match in file order) would return some other message's envelope comment;
// this walk is message-scoped, the same way ver_field_contract_test.go attributes
// each `ver` to its enclosing message.
func manifestVerComment(t *testing.T) string {
t.Helper()
for _, f := range collectVerFields(t) {
if f.message == "WellKnownManifest" {
// collectVerFields joins the raw lines, `//` markers included, so a
// clause that wraps across two source lines carries a marker in the
// middle. Strip them so phrases are matched against prose.
return strings.TrimPrefix(strings.ReplaceAll(f.comment, " // ", " "), "// ")
}
}
t.Fatal("WellKnownManifest.ver not found in the contract protos")
return ""
}

// TestManifestVersionCommentStatesEveryClauseTheSDKEnforces reads the rule out of
// the contract and holds it to the clauses the SDK actually applies.
func TestManifestVersionCommentStatesEveryClauseTheSDKEnforces(t *testing.T) {
comment := manifestVerComment(t)

// Guard the guard: pin the extraction to the block it is supposed to have read.
const opening = "Version of THIS MANIFEST DOCUMENT's layout"
if !strings.HasPrefix(comment, opening) {
t.Fatalf("the extracted comment does not begin with the manifest ver's own first line "+
"(%q) — this guard is reading the wrong block and would assert nothing.\n read: %.120s…",
opening, comment)
}

for _, clause := range []struct {
name string
phrase string
why string
}{
{"separate namespace", "separate from the RPC envelope",
"the manifest version is not the protocol version and is never stamped from it"},
{"own constant", "WellKnownManifestVersion",
"the value has one owner, and it is not ProtocolVersion"},
{"coupling direction", "bumps both numbers",
"a manifest change moves the protocol version; the reverse does not hold"},
{"read first", "before any other member",
"the gate runs before the endpoint is so much as looked at"},
{"recognised major accepted", "ACCEPT a recognised MAJOR version whatever the MINOR",
"a minor revision of the manifest is additive"},
{"unrecognised major rejected", "REJECT an unrecognised MAJOR",
"a layout the reader cannot classify must not supply the endpoint"},
{"malformed rejected", "not MAJOR.MINOR",
"a value the rule cannot parse is not a version it recognises"},
{"absent rejected", "an ABSENT `ver`",
"a document with no version is one whose layout the reader cannot classify"},
} {
if !strings.Contains(comment, clause.phrase) {
t.Errorf("the WellKnownManifest.ver comment no longer states %q (%s).\n"+
"The SDKs still enforce it and the corpus still pins it, so the contract and the "+
"implementations have drifted — restore the clause or change all four together.",
clause.name, clause.why)
}
}
}

// TestManifestVersionCorpusCoversEveryStatedClause holds the other direction: each
// clause the contract states must be exercised by at least one vector, so three
// SDKs cannot quietly stop enforcing it while the comment still reads correctly.
func TestManifestVersionCorpusCoversEveryStatedClause(t *testing.T) {
cases := mustManifestVersionCases(t)
var sameMajorHigherMinor, unrecognisedMajor, malformed, absent bool
for _, c := range cases {
switch {
case !c.Present:
absent = absent || !c.Accepted
case c.Accepted && strings.HasPrefix(c.Ver, "1.") && c.Ver != "1.0":
sameMajorHigherMinor = true
case !c.Accepted && isMajorMinor(c.Ver) && !strings.HasPrefix(c.Ver, "1."):
unrecognisedMajor = true
case !c.Accepted && !isMajorMinor(c.Ver):
malformed = true
}
}
for name, ok := range map[string]bool{
"a same-major higher-minor version accepted": sameMajorHigherMinor,
"an unrecognised major refused": unrecognisedMajor,
"a value that is not MAJOR.MINOR refused": malformed,
"an absent ver refused": absent,
} {
if !ok {
t.Errorf("the corpus at %s has no vector for %s — the contract states the clause, so the SDKs must be pinned to it",
filepath.Base(manifestVersionVectors), name)
}
}
}

// isMajorMinor is the corpus-side classifier used only to sort vectors into the
// clauses above. It is deliberately a loose re-statement, not the SDK's parser:
// the SDK's verdict is what the corpus records, and this only decides which
// clause a recorded verdict exercises.
func isMajorMinor(ver string) bool {
major, minor, found := strings.Cut(ver, ".")
return found && digitsOnly(major) && digitsOnly(minor)
}

func digitsOnly(s string) bool {
if s == "" {
return false
}
for i := 0; i < len(s); i++ {
if s[i] < '0' || s[i] > '9' {
return false
}
}
return true
}
101 changes: 77 additions & 24 deletions conformance/ver_field_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ package conformance

import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
Expand All @@ -59,23 +59,23 @@ var (
// stale, and an entry whose comment has since adopted the standard advisory
// wording fails as a no-longer-needed exemption.
var verExemptMessages = map[string]string{
"WellKnownManifest": "the /.well-known/ramp.json document's own schema version — a separate " +
"namespace from the RPC envelope, stated MUST-equal rather than advisory",
"WellKnownManifest": "the /.well-known/ramp.json document's own layout version — a separate " +
"namespace from the RPC envelope, gated on its MAJOR by consumers rather than advisory; " +
"its expected value is the SDK's WellKnownManifestVersion, not ProtocolVersion",
}

// wireConstantsVectors is the committed cross-language oracle for the SDK's wire
// constants, emitted from the real Go constants by the sdk/go/helpers vector
// generator. Read as data (see the file header on why this is not an import).
const wireConstantsVectors = "../sdk/go/helpers/testdata/wire-constants-vectors.json"

// protocolVersion returns the RAMP protocol version the SDK exports, read from the
// committed vector so this guard never restates the literal. Errors are fatal
// rather than skipped: a missing file or entry means the guard has lost its
// anchor, and silently passing would be worse than failing.
var protocolVersion = sync.OnceValues(func() (string, error) {
// wireVectors loads the committed vector once. Errors are fatal rather than
// skipped: a missing file means the guard has lost its anchor, and silently
// passing would be worse than failing.
var wireVectors = sync.OnceValues(func() (map[string]string, error) {
b, err := os.ReadFile(wireConstantsVectors)
if err != nil {
return "", err
return nil, err
}
var doc struct {
Vectors []struct {
Expand All @@ -84,18 +84,42 @@ var protocolVersion = sync.OnceValues(func() (string, error) {
} `json:"vectors"`
}
if err := json.Unmarshal(b, &doc); err != nil {
return "", err
return nil, err
}
byName := make(map[string]string, len(doc.Vectors))
for _, v := range doc.Vectors {
if v.Name == "ProtocolVersion" {
return v.Value, nil
}
byName[v.Name] = v.Value
}
return "", errNoProtocolVersionVector
return byName, nil
})

var errNoProtocolVersionVector = errors.New(
wireConstantsVectors + " carries no ProtocolVersion entry — this guard reads the expected `ver` value from there")
// wireConstant returns a reader for one named entry of the vector, so this guard
// never restates a literal. A missing entry is an error for the same reason a
// missing file is.
func wireConstant(name string) func() (string, error) {
return func() (string, error) {
byName, err := wireVectors()
if err != nil {
return "", err
}
v, ok := byName[name]
if !ok {
return "", fmt.Errorf("%s carries no %s entry — this guard reads the expected `ver` value from there",
wireConstantsVectors, name)
}
return v, nil
}
}

var (
// protocolVersion is the RAMP protocol version the SDK exports.
protocolVersion = wireConstant("ProtocolVersion")
// manifestVersion is the SDK's WellKnownManifestVersion. It is a separate
// lookup on purpose: the two constants are separate namespaces, and a guard
// that checked the manifest comment against ProtocolVersion would be the
// coupling the contract forbids, written into the thing that guards it.
manifestVersion = wireConstant("WellKnownManifestVersion")
)

// expectedVersionToken is the quoted form a `ver` comment must contain, e.g. `"1.0"`.
func expectedVersionToken(t *testing.T) string {
Expand All @@ -107,6 +131,18 @@ func expectedVersionToken(t *testing.T) string {
return strconv.Quote(v)
}

// expectedManifestVersionToken is the quoted form the MANIFEST's `ver` comment must
// contain. Read from its own constant, so a bump of either namespace alone goes
// red on exactly the comment it invalidates.
func expectedManifestVersionToken(t *testing.T) string {
t.Helper()
v, err := manifestVersion()
if err != nil {
t.Fatalf("read the SDK manifest version: %v", err)
}
return strconv.Quote(v)
}

// statesExpectedValue is the obligation on EVERY `ver` comment: an integrator
// reading the field must learn what to stamp. Required of exempt fields too —
// the exemption is from the advisory rule, not from naming the value. `token` is
Expand Down Expand Up @@ -176,17 +212,23 @@ func TestVerFieldsDocumentTheirContract(t *testing.T) {
}

token := expectedVersionToken(t)
manifestToken := expectedManifestVersionToken(t)
for _, f := range fields {
if f.comment == "" {
t.Errorf("%s:%d %s.ver carries no doc comment — every `ver` must state the expected value and its receive-side rule",
f.file, f.line, f.message)
continue
}
if !statesExpectedValue(f.comment, token) {
t.Errorf("%s:%d %s.ver does not state the expected value — the comment must name %s (the SDK's ProtocolVersion, per %s) so an integrator learns what to stamp",
f.file, f.line, f.message, token, wireConstantsVectors)
_, exempt := verExemptMessages[f.message]
want, owner := token, "ProtocolVersion"
if exempt {
want, owner = manifestToken, "WellKnownManifestVersion"
}
if !statesExpectedValue(f.comment, want) {
t.Errorf("%s:%d %s.ver does not state the expected value — the comment must name %s (the SDK's %s, per %s) so an integrator learns what to stamp",
f.file, f.line, f.message, want, owner, wireConstantsVectors)
}
if _, exempt := verExemptMessages[f.message]; exempt {
if exempt {
continue
}
if !statesAdvisoryContract(f.comment) {
Expand Down Expand Up @@ -234,12 +276,23 @@ func TestVerContractDetectors_MetaPositive(t *testing.T) {
if !statesAdvisoryContract(std) {
t.Error("detector missed the advisory rule in the standard comment")
}
manifest := "// RAMP protocol version of THIS MANIFEST DOCUMENT's schema. MUST equal " + token + ";"
if !statesExpectedValue(manifest, token) {
mtoken := expectedManifestVersionToken(t)
manifest := "// Version of THIS MANIFEST DOCUMENT's layout — " + mtoken + ", stamped from WellKnownManifestVersion."
if !statesExpectedValue(manifest, mtoken) {
t.Error("detector missed the expected value in the manifest comment")
}
}

// TestManifestVersionTokenIsReadSeparately pins that the manifest's expected
// value has its own source. The two read the same today, so a guard that quietly
// used ProtocolVersion for both would pass — this test fails the day the vector
// entry goes missing, which is the day the guard would silently start coupling.
func TestManifestVersionTokenIsReadSeparately(t *testing.T) {
if _, err := manifestVersion(); err != nil {
t.Fatalf("the manifest version must be readable from its own vector entry: %v", err)
}
}

// TestVerExpectedValueComesFromTheSDK pins the anchor itself: the token is READ
// from the wire-constants vector, not restated here. A comment naming some other
// version must fail — which is what makes a half-finished bump (constant moved,
Expand Down Expand Up @@ -274,8 +327,8 @@ func TestVerContractDetectors_MetaNegative(t *testing.T) {
}
// The manifest comment states a value but NOT the advisory rule — that
// asymmetry is exactly what makes its exemption necessary.
if statesAdvisoryContract("// MUST equal " + token + "; consumers REJECT unrecognised major versions.") {
t.Error("detector treated the MUST-equal manifest rule as the advisory contract")
if statesAdvisoryContract("// They REJECT an unrecognised MAJOR, a value that is not MAJOR.MINOR, and an ABSENT `ver`.") {
t.Error("detector treated the manifest's major-version gate as the advisory contract")
}
}

Expand Down
Loading
Loading