fix: omit the agent "for" key when no party is named - #13
Merged
Merged
Conversation
`omitempty` has no effect on a struct field, so `Agent.For` was always serialised. A zero ForField marshals to `null`, which put `"for": null` on the wire for every ownerless agent — a settlement address with no owner, for instance. Decoding that is worse than emitting it. `encoding/json` documents unmarshalling `null` into a non-pointer as a no-op that returns no error, so ForField.UnmarshalJSON's single-string branch accepted it, leaving `values = [""]` and `IsEmpty() == false`. A receiver reads an agent that declares an owner whose DID is the empty string, when the sender said the agent has none. Two changes: - `json:"for,omitzero"` on Agent.For, plus an IsZero method so a ForField built from an empty non-nil slice omits the key too. omitzero applies to structs, which is what omitempty could never do. - UnmarshalJSON rejects `null` up front, so a receiver that has not upgraded past a sender still emitting `"for": null` decodes it as no owner. TAIP-5 makes `for` optional on an agent, so an absent key is the correct encoding of "no party named".
momilo
marked this pull request as draft
August 30, 2026 22:18
govulncheck reports nine standard-library vulnerabilities against the toolchain the go directive selects, and CI builds with go-version-file: go.mod, so the whole matrix has been red on main since July. Every one of them is fixed by 1.26.6 — the highest "fixed in" across the set — and none come from a dependency, so the directive is the only thing that needs to move. Reachable from this module rather than theoretical: the traces run through Client.ReceiveUnverified into url.URL.Parse, tls.Conn.HandshakeContext and asn1.Unmarshal. A patch-level bump within the same minor, so consumers need a 1.26.6 toolchain rather than a new language version.
momilo
marked this pull request as ready for review
August 31, 2026 16:58
This was referenced Sep 11, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An agent with no
forparty goes on the wire as"for": null, and a receiver decodes that as an agent whose owner is the empty string.Serialisation
Agent.Foris aForFieldstruct taggedjson:"for,omitempty".omitemptyhas no effect on a struct type, so the field is always emitted, andForField.MarshalJSONreturnsjson.Marshal(nil)when it holds no values. Every ownerless agent — a settlement address with no owner, say — therefore carries"for": null.Deserialisation
encoding/jsondocuments unmarshallingnullinto a non-pointer as a no-op that returns no error.ForField.UnmarshalJSONtriesjson.Unmarshal(data, &single)first, sonulltakes that branch withsingle == "", leavingvalues = []string{""}andIsEmpty() == false.So a receiver concludes the agent declares an owner, whose DID is the empty string, when the sender said it has none. TAIP-5 makes
foroptional on an agent, so the correct encoding of "no party named" is an absent key.Changes
json:"for,omitzero"onAgent.For.omitzeroapplies to structs, which is the thingomitemptycannot do. Plus anIsZeromethod, so aForFieldbuilt from an empty non-nil slice omits the key as well as the nil-slice zero value.ForField.UnmarshalJSONhandlesnullexplicitly before the single-string branch. This half is not needed once senders stop emitting the key, but it protects receivers against senders that have not upgraded.ConfirmRelationship.Foris untouched — TAIP-9 requires that field, and it is taggedjson:"for"with no omission.Tests
Four cases in
types_test.go, each verified to fail without the change:Agentmarshals without aforkey, and round-trips back toIsEmpty();ForFieldbuilt from an empty slice omits the key;nulldecodes to no values rather than[""];Agentwhose JSON carries"for": nulldecodes to no owner.Module is already on Go 1.26, so
omitzeroneeds no toolchain bump.Second commit:
go 1.26.6govulncheckreports nine standard-library vulnerabilities against the toolchain thegodirective selects, and CI resolves its Go version fromgo-version-file: go.mod— so the Vulncheck job has been failing onmainsince July, independently of this change. All nine are fixed by 1.26.6, the highest "fixed in" across the set, and none originate in a dependency, so the directive is the only thing that needs to move. Clean locally afterwards.They are reachable from this module rather than theoretical — the traces run through
Client.ReceiveUnverifiedintourl.URL.Parse,tls.Conn.HandshakeContextandasn1.Unmarshal.It is a patch-level bump within the same minor, so consumers need a 1.26.6 toolchain rather than a new language version. Happy to split it into its own PR if you'd rather keep this one to the
forfield.