event.Subscribe marshals to a payload that does not validate against the WS-BaseNotification schema it cites in its own doc comment (http://docs.oasis-open.org/wsn/b-2.xsd). There are two independent defects. Both are separate from the CreatePullPointSubscription namespace problem in #64, though the first one interacts with it.
For reference, b-2.xsd declares Subscribe as an xs:sequence:
| # |
Child |
Cardinality |
Note |
| 1 |
ConsumerReference |
[1][1] |
|
| 2 |
Filter |
[0][1] |
|
| 3 |
InitialTerminationTime |
[0][1] |
nillable="true" |
| 4 |
SubscriptionPolicy |
[0][1] |
|
| 5 |
<xs:any namespace="##other"> |
[0][n] |
trailing wildcard |
The schema is elementFormDefault="qualified", so all four children are correctly wsnt:-prefixed today. The prefixes are not the problem here.
1. Children are emitted out of sequence order
event/operation.go declares SubscriptionPolicy before InitialTerminationTime, and encoding/xml emits fields in declaration order:
type Subscribe struct { //http://docs.oasis-open.org/wsn/b-2.xsd
XMLName struct{} `xml:"wsnt:Subscribe"`
ConsumerReference EndpointReferenceType `xml:"wsnt:ConsumerReference"`
Filter FilterType `xml:"wsnt:Filter"`
SubscriptionPolicy SubscriptionPolicy `xml:"wsnt:SubscriptionPolicy"`
InitialTerminationTime AbsoluteOrRelativeTimeType `xml:"wsnt:InitialTerminationTime"`
}
Reproduced bytes on current master, for a Subscribe carrying only a consumer reference and a termination time:
<wsnt:Subscribe><wsnt:ConsumerReference><Address>http://192.168.0.10:8080/notify</Address><ReferenceParameters></ReferenceParameters><Metadata></Metadata></wsnt:ConsumerReference><wsnt:Filter><wsnt:TopicExpression Dialect=""></wsnt:TopicExpression></wsnt:Filter><wsnt:SubscriptionPolicy ChangedOnly="false"></wsnt:SubscriptionPolicy><wsnt:InitialTerminationTime>PT60S</wsnt:InitialTerminationTime></wsnt:Subscribe>
The ordering is not cosmetic. Walking a validator through it:
ConsumerReference and Filter match particles 1 and 2.
- Particle 3 is
InitialTerminationTime, but SubscriptionPolicy appears instead. Particle 3 is optional, so it is skipped and SubscriptionPolicy matches particle 4.
- The only particle left is the wildcard, declared
namespace="##other". The trailing wsnt:InitialTerminationTime is in the target namespace, which ##other explicitly excludes, so it cannot match.
- No particle remains. Validation fails.
So a schema-strict device rejects the request outright rather than silently ignoring one element.
On master the policy is a value type and is therefore always emitted, which means every Subscribe request is affected. #64 makes it an optional pointer, so after that lands the violation is latent and only surfaces when a caller sets SubscriptionPolicy. Either way the field order is wrong.
2. Optional children are always emitted
Filter and InitialTerminationTime are both minOccurs="0", but the Go fields are value types, so a zero-value Subscribe still ships both:
<wsnt:Subscribe><wsnt:ConsumerReference>…</wsnt:ConsumerReference><wsnt:Filter><wsnt:TopicExpression Dialect=""></wsnt:TopicExpression></wsnt:Filter><wsnt:InitialTerminationTime></wsnt:InitialTerminationTime></wsnt:Subscribe>
- The empty
wsnt:InitialTerminationTime is invalid on its own. Its type is wsnt:AbsoluteOrRelativeTimeType, a union of xs:dateTime and xs:duration, and "" belongs to neither lexical space. The element is nillable="true", so <wsnt:InitialTerminationTime xsi:nil="true"/> would be legal, but a bare empty element is not.
- The
wsnt:Filter is schema-valid. FilterType is <xs:any minOccurs="0" maxOccurs="unbounded"/> (b-2.xsd:57-61), so its namespace defaults to ##any and its processContents to strict (XSD 1.0 Part 1 §3.10.2), and wsnt:TopicExpression is globally declared at b-2.xsd:71, so the child resolves and validates. Dialect="" is valid too: xs:anyURI takes its lexical space from RFC 2396 (XSD 1.0 Part 2 §3.2.17.1), whose URI-reference production makes both parts optional, and §4.2 reads an empty reference as a same-document reference. The defect is therefore semantic rather than structural: an empty dialect URI is not resolvable by a device, and sending it defeats the "no Filter means notify all topics" semantics.
This is the same class of defect that #44 fixed for FilterType.MessageContent and that #64 fixes for the three CreatePullPointSubscription children.
Proposed fix
type Subscribe struct { //http://docs.oasis-open.org/wsn/b-2.xsd
XMLName struct{} `xml:"wsnt:Subscribe"`
ConsumerReference EndpointReferenceType `xml:"wsnt:ConsumerReference"`
Filter *FilterType `xml:"wsnt:Filter,omitempty"`
InitialTerminationTime *AbsoluteOrRelativeTimeType `xml:"wsnt:InitialTerminationTime,omitempty"`
SubscriptionPolicy *SubscriptionPolicy `xml:"wsnt:SubscriptionPolicy,omitempty"`
}
Reordering the fields is not a source-breaking change for keyed struct literals. Making Filter and InitialTerminationTime pointers is a public API break: callers must take addresses when setting them and handle nil when reading. There are no production callers inside this repository — sdk/event/Subscribe_auto.go forwards the struct unchanged — but downstream users are affected. This mirrors the break already proposed in #64, so the two changes should land in the same release to keep the migration to a single step for downstream users.
I have this implemented locally with marshaling regressions covering the sequence order and each optional child independently, and can open a PR once #64 is resolved, since it builds on the pointer change there. Happy to take it in whatever order suits you.
Not covered here
EndpointReferenceType marshals Address, ReferenceParameters, and Metadata without a prefix, while ws-addr.xsd is elementFormDefault="qualified" and expects wsa:-qualified children; the two optional ones are also always emitted. The wsa prefix is already declared in Xlmns. That type is shared by other packages, so it seems better as its own issue rather than folded in here.
Environment
AI disclosure
I used Claude Code (Opus 5) to cross-check event.Subscribe against b-2.xsd, to reproduce the marshaled bytes quoted above, and to draft this report. The local implementation and its regression tests were written the same way. I verified the schema references, the reproduced payloads, and the validator walkthrough myself, and I take responsibility for the conclusions.
event.Subscribemarshals to a payload that does not validate against the WS-BaseNotification schema it cites in its own doc comment (http://docs.oasis-open.org/wsn/b-2.xsd). There are two independent defects. Both are separate from theCreatePullPointSubscriptionnamespace problem in #64, though the first one interacts with it.For reference,
b-2.xsddeclaresSubscribeas anxs:sequence:ConsumerReference[1][1]Filter[0][1]InitialTerminationTime[0][1]nillable="true"SubscriptionPolicy[0][1]<xs:any namespace="##other">[0][n]The schema is
elementFormDefault="qualified", so all four children are correctlywsnt:-prefixed today. The prefixes are not the problem here.1. Children are emitted out of sequence order
event/operation.godeclaresSubscriptionPolicybeforeInitialTerminationTime, andencoding/xmlemits fields in declaration order:Reproduced bytes on current
master, for aSubscribecarrying only a consumer reference and a termination time:The ordering is not cosmetic. Walking a validator through it:
ConsumerReferenceandFiltermatch particles 1 and 2.InitialTerminationTime, butSubscriptionPolicyappears instead. Particle 3 is optional, so it is skipped andSubscriptionPolicymatches particle 4.namespace="##other". The trailingwsnt:InitialTerminationTimeis in the target namespace, which##otherexplicitly excludes, so it cannot match.So a schema-strict device rejects the request outright rather than silently ignoring one element.
On
masterthe policy is a value type and is therefore always emitted, which means everySubscriberequest is affected. #64 makes it an optional pointer, so after that lands the violation is latent and only surfaces when a caller setsSubscriptionPolicy. Either way the field order is wrong.2. Optional children are always emitted
FilterandInitialTerminationTimeare bothminOccurs="0", but the Go fields are value types, so a zero-valueSubscribestill ships both:wsnt:InitialTerminationTimeis invalid on its own. Its type iswsnt:AbsoluteOrRelativeTimeType, a union ofxs:dateTimeandxs:duration, and""belongs to neither lexical space. The element isnillable="true", so<wsnt:InitialTerminationTime xsi:nil="true"/>would be legal, but a bare empty element is not.wsnt:Filteris schema-valid.FilterTypeis<xs:any minOccurs="0" maxOccurs="unbounded"/>(b-2.xsd:57-61), so itsnamespacedefaults to##anyand itsprocessContentstostrict(XSD 1.0 Part 1 §3.10.2), andwsnt:TopicExpressionis globally declared atb-2.xsd:71, so the child resolves and validates.Dialect=""is valid too:xs:anyURItakes its lexical space from RFC 2396 (XSD 1.0 Part 2 §3.2.17.1), whoseURI-referenceproduction makes both parts optional, and §4.2 reads an empty reference as a same-document reference. The defect is therefore semantic rather than structural: an empty dialect URI is not resolvable by a device, and sending it defeats the "noFiltermeans notify all topics" semantics.This is the same class of defect that #44 fixed for
FilterType.MessageContentand that #64 fixes for the threeCreatePullPointSubscriptionchildren.Proposed fix
Reordering the fields is not a source-breaking change for keyed struct literals. Making
FilterandInitialTerminationTimepointers is a public API break: callers must take addresses when setting them and handle nil when reading. There are no production callers inside this repository —sdk/event/Subscribe_auto.goforwards the struct unchanged — but downstream users are affected. This mirrors the break already proposed in #64, so the two changes should land in the same release to keep the migration to a single step for downstream users.I have this implemented locally with marshaling regressions covering the sequence order and each optional child independently, and can open a PR once #64 is resolved, since it builds on the pointer change there. Happy to take it in whatever order suits you.
Not covered here
EndpointReferenceTypemarshalsAddress,ReferenceParameters, andMetadatawithout a prefix, whilews-addr.xsdiselementFormDefault="qualified"and expectswsa:-qualified children; the two optional ones are also always emitted. Thewsaprefix is already declared inXlmns. That type is shared by other packages, so it seems better as its own issue rather than folded in here.Environment
encoding/xml; no device required.http://docs.oasis-open.org/wsn/b-2.xsd,https://www.w3.org/2005/08/addressing/ws-addr.xsd.CreatePullPointSubscriptionfails with SOAP Sender fault due to typo in XML tag #63, Fix SOAP Sender fault due to typo in XML tag #64, exports device params, fixes error in comments, adds gitignore file #44.AI disclosure
I used Claude Code (Opus 5) to cross-check
event.Subscribeagainstb-2.xsd, to reproduce the marshaled bytes quoted above, and to draft this report. The local implementation and its regression tests were written the same way. I verified the schema references, the reproduced payloads, and the validator walkthrough myself, and I take responsibility for the conclusions.