Add an extension attribute¶
Mechanically¶
Extensions are a map[string]string on the event, keyed without the ce- prefix:
e := cloudevents.Event{
ID: "01JQ8Z3F7K2M4N6P8R0T2V4W6X",
Source: "/phpbotscout/ingest",
Type: "uk.phpboyscout.orders.created",
Extensions: map[string]string{
"scope": "urn:phpboyscout:scope:continuity:01JQ8Z3F7K2M4N6P8R0T2V4W6X",
},
}
That is written as ce-scope on the wire and read back under scope.
The name has to satisfy the grammar¶
Check it with
ValidExtensionName
if you are generating names; Marshal refuses an invalid one either way.
| Rule | Enforced | Why |
|---|---|---|
| One or more characters | Yes | MUST |
| Lower-case ASCII letters and digits only | Yes | MUST — no hyphens, no underscores, no dots |
Not the name data |
Yes | MUST — some event formats reserve it |
| Not one of the eight attribute names | Yes | Extensions are "additional context attributes with distinct names" |
| Starts with a letter | No | SHOULD |
| Twenty characters or fewer | No | SHOULD |
The fourth rule is the one with teeth. id, source, specversion, type, datacontenttype,
dataschema, subject and time are refused with
ErrReservedExtensionName — without it,
Extensions["id"] would overwrite ce-id on the way out and the event would marshal with a
hijacked identity and no error at all.
An extension value must also be non-empty and free of control characters. An empty value writes no
header, so it would vanish in transit; a control character would be rewritten by net/http rather
than refused.
The two SHOULDs are deliberately not enforced. Refusing a twenty-one character name would reject events the rest of the world accepts, which is the interoperability failure that implementing a standard rather than inventing one exists to avoid.
The constraint is narrow because some protocols treat metadata as case-sensitive and others do not, and one event may cross several of them in a single delivery.
The bar for adding one at all¶
Every attribute must be safe to log
Attributes ride in headers, headers are logged by default, and nobody audits a header the way
they audit a payload field. This estate routes free-form strings bound for telemetry through
go/redact; attributes bypass that entirely.
- Opaque identifiers only. Never a display name, a channel name, a guild name, or anything a person chose.
- Nothing a redaction pass would catch, because nothing routes attributes through one.
- A payload has a schema and a review. An attribute has neither, so the bar is higher.
If the value is not an opaque identifier, it belongs in the payload.
The one that already exists¶
scope is the estate's tenancy attribute, and it is
ADVISORY, AND NEVER THE AUTHORITY. It is a claim in a message, for
routing, filtering and legibility. Enforcement is NATS accounts with subject-scoped credentials, and
a consumer that filters on scope has isolated nothing.