add producer-side constructors for RISC events#1
Merged
Conversation
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>
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.
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, sorisc.AccountDisabled{Subject: sub}would not compile (the subject is a promoted field, not a direct one). The only path wasvar e …; e.Subject = sub, which turns "forgot the subject" into a runtimeAddTofailure 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
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.sessions-revokedevent (the profile steers emitters to the CAEPsession-revokedevent).Reason,NewValue, the credential-compromise reasons/timestamp) stay exported fields set on the returned value — no functional-options layer for already-exported fields.Base. It keeps the "remember to set Subject" footgun these constructors exist to remove.Scope
identifier-changed/-recycledkeep their email-or-phone requirement enforced at theAddTo/Validatemarshal boundary.Tests / docs
AddTo→ encode SET →secevent.Parse→Events.Typedyields 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 deprecatedsessions-revoked.ExampleAddToand the README now show the constructor instead of thevar+assign dance.Examplegodoc (it described a blank import while the example uses a named one).go test ./...,go vet,gofmt -l, andgolangci-lint runall pass clean.🤖 Generated with Claude Code