Skip to content

add producer-side constructors for RISC events#1

Merged
hstern merged 3 commits into
mainfrom
claude-event-constructors-2026-06-21
Jun 21, 2026
Merged

add producer-side constructors for RISC events#1
hstern merged 3 commits into
mainfrom
claude-event-constructors-2026-06-21

Conversation

@hstern

@hstern hstern commented Jun 21, 2026

Copy link
Copy Markdown
Owner

What

Adds a New… constructor for every non-deprecated RISC event type so a consumer can build an event in a single expression with the required fields supplied positionally.

Before, every event embedded the unexported subjectEvent, so risc.AccountDisabled{Subject: sub} would not compile (the subject is a promoted field, not a direct one). The only path was var e …; e.Subject = sub, which turns "forgot the subject" into a runtime AddTo failure instead of a compile error. A downstream consumer hit exactly this and fell back to hand-rolling the SET as raw JSON, leaving the typed producer path untested from the consumer side.

Design

  • One NewX(subject) per non-deprecated event type; the required subject is positional, so it can't be omitted at the call site.
  • NewCredentialCompromise(subject, credentialType) — its second required field is positional too.
  • No constructor for the deprecated sessions-revoked event (the profile steers emitters to the CAEP session-revoked event).
  • Optional members (Reason, NewValue, the credential-compromise reasons/timestamp) stay exported fields set on the returned value — no functional-options layer for already-exported fields.
  • Considered and rejected: exporting the embedded struct as Base. It keeps the "remember to set Subject" footgun these constructors exist to remove.

Scope

  • No wire-shape or decode-path change.
  • Construction stays lenient (Postel's law): constructors don't validate subject format; identifier-changed/-recycled keep their email-or-phone requirement enforced at the AddTo/Validate marshal boundary.

Tests / docs

  • Consumer-side round-trip test over every constructor (construct → AddTo → encode SET → secevent.ParseEvents.Typed yields a wire-equal event), plus a coverage guard that fails if a new event type ships without a constructor or if one is ever added for the deprecated sessions-revoked.
  • ExampleAddTo and the README now show the constructor instead of the var+assign dance.
  • Second commit corrects a pre-existing inaccuracy in the Example godoc (it described a blank import while the example uses a named one).

go test ./..., go vet, gofmt -l, and golangci-lint run all pass clean.

🤖 Generated with Claude Code

hstern and others added 3 commits June 21, 2026 09:22
Every RISC event embeds the unexported subjectEvent struct, so a
consumer in another package could not set the required subject in a
composite literal — risc.AccountDisabled{Subject: sub} does not compile
because Subject is promoted, not a direct field. The only path was a
two-step var declaration plus assignment, which makes "forgot the
subject" a runtime AddTo failure rather than a compile error. A
downstream consumer hit exactly this and fell back to hand-rolling the
SET as raw JSON, leaving the typed producer path untested from the
consumer side.

Each non-deprecated event type now has a New… constructor that takes the
required subject positionally, so the subject can never be omitted at the
call site. credential-compromise carries a second required field, so
NewCredentialCompromise takes credential_type positionally as well. The
deprecated sessions-revoked event deliberately has no constructor: the
RISC profile steers emitters to the CAEP session-revoked event, and the
producer API should not encourage emitting the deprecated form.

Optional members (Reason, NewValue, the credential-compromise reasons
and timestamp) stay exported fields set on the returned value rather than
constructor arguments or functional options — the only thing that needed
help was the unexported-subject footgun, and a functional-options layer
for already-exported fields would be ceremony for nothing. Exporting the
embedded struct as Base was the considered alternative; it was rejected
because it keeps the "remember to set Subject" footgun the constructors
exist to remove.

This changes no wire shape and no decode path. Construction stays lenient
in keeping with the library's Postel's-law contract: the constructors do
not validate subject format, so identifier-changed/-recycled events still
have their email-or-phone requirement enforced only at the AddTo/Validate
marshal boundary.

A consumer-side round-trip test covers every constructor (construct →
AddTo → encode SET → secevent.Parse → Events.Typed yields a wire-equal
event), with a coverage guard that fails if a new event type ships
without a constructor or if a constructor is ever added for the
deprecated sessions-revoked event. ExampleAddTo and the README now show
the constructor instead of the var-plus-assign dance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Example function's comment said "the blank import of go-risc ...
wires every RISC decoder", but the example uses a named import (it
references risc.AccountDisabledURI and the typed value). A reader
following the comment and switching to a blank import would lose the
risc. namespace the example needs. Reword to say that importing the
package — by name here, for the URI constants — registers the decoders
via the package init, and note the blank import as the side-effect-only
alternative.

Comment-only; no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the producer-side constructors from Unreleased into a [0.2.0]
section and add its compare link. 0.2.0 is a minor bump: the
constructors are a backward-compatible addition to the public API over
0.1.0, with no breaking change and no wire-shape or decode-path change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hstern hstern merged commit 9706d7b into main Jun 21, 2026
4 checks passed
@hstern hstern deleted the claude-event-constructors-2026-06-21 branch June 21, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant