Files
contracts/CHANGELOG.md
T

9.4 KiB
Raw Permalink Blame History

Changelog

All notable changes to this module will be documented in this file.

The format is based on Keep a Changelog, and this module adheres to Semantic Versioning.


[1.3.0] — 2026-08-08

Minor — coordinated framework version alignment. No code, API, or dependency changes.

[1.2.0] — 2026-08-08

Minor — coordinated framework version alignment. No code, API, or dependency changes.

[1.1.3] — 2026-08-08

Patch — coordinated framework version alignment. No code, API, or dependency changes.

[1.1.2] — 2026-08-08

Patch — coordinated framework version alignment. No code, API, or dependency changes.

[1.1.1] — 2026-08-07

Patch — coordinated framework version alignment. Released in lockstep at v1.1.1 alongside a documentation patch in the data/infra starters. No code, API, or dependency changes.

[1.1.0] — 2026-08-07

Added

security

  • SecurityBag struct — request-scoped security context that carries both the authenticated Identity and arbitrary string-keyed attributes injected during the enrichment phase (e.g. hardware IDs, grant codes). Value type — all mutation methods return a new value without modifying the receiver. The attribute map is copied on every With call to preserve immutability guarantees.
  • NewSecurityBag(id Identity) SecurityBag — constructs a SecurityBag wrapping the given Identity with an empty attribute map
  • SecurityBag.Identity() Identity — returns the authenticated Identity stored in the bag
  • SecurityBag.WithIdentity(id Identity) SecurityBag — returns a copy of the bag with the Identity replaced; used by bag enrichers that modify the Identity (e.g. applying a tenant ID from a request header)
  • SecurityBag.Get(key string) (any, bool) — returns the attribute stored under key, or (nil, false) if absent
  • SecurityBag.With(key string, value any) SecurityBag — returns a copy of the bag with the given key set to value; the receiver is not modified
  • SetBagInContext(ctx context.Context, bag SecurityBag) context.Context — stores the full SecurityBag in ctx; use instead of SetInContext when extra attributes need to travel alongside the Identity
  • BagFromContext(ctx context.Context) (SecurityBag, bool) — retrieves the SecurityBag stored by SetBagInContext or SetInContext; permission providers use this to access both the Identity and any enrichment attributes

Changed

security

  • SetInContext — now stores a SecurityBag wrapping the Identity internally, replacing direct Identity storage. The function signature is unchanged; all existing callers continue to work without modification.
  • FromContext — now extracts the Identity from the stored SecurityBag internally. The function signature is unchanged; all existing callers continue to work without modification. SetBagInContext + FromContext is valid (returns the bag's Identity). SetInContext + BagFromContext is valid (returns a bag wrapping the identity with an empty attribute map).

Design Notes

  • SecurityBag is additive — no existing interface or function signature is modified. Starters compiled against v1.0.0 continue to work against v1.1.0 without any code changes.
  • The framework defines no string key constants for bag attributes. All framework-known fields are typed fields on Identity. Callers define their own constants to avoid collisions and maintain compile-time safety at the type-assertion call site.

1.0.0 — 2026-05-27

Added

lifecycle

  • Component interface — OnInit() error (open connections, allocate resources), OnStart() error (start background goroutines and listeners), OnStop() error (graceful shutdown and resource release); the framework calls these in strict phase order and shuts down components in reverse registration order

observability

  • Level type — int-based criticality classifier for health reporting; zero value is LevelCritical, making it a safe default for any component that forgets to set it
  • LevelCritical constant — marks a component essential to application function; a failing critical component sets overall health to DOWN (HTTP 503)
  • LevelDegraded constant — marks a component important but not essential; a failing degraded component sets overall health to DEGRADED (HTTP 200), not DOWN
  • Checkable interface — HealthCheck(ctx context.Context) error (connectivity or liveness probe), Name() string (display name in health responses), Priority() Level (criticality of this component); implemented by all infrastructure components; consumed by the web starter's health handler without the data layer ever importing HTTP

logging

  • Logger interface — Debug, Info, Warn(msg string, args ...any), Error(msg string, err error, args ...any), With(args ...any) Logger, WithContext(ctx context.Context) Logger; all Einherjar starters accept Logger at their constructors and pass it to sub-components — never the concrete implementation; Error treats err as a first-class parameter to enable automatic enrichment from errs.CodedError and errs.ContextualError

errs

  • CodedError interface — ErrorCode() string; implemented by structured errors that carry a machine-readable SCREAMING_SNAKE_CASE code meaningful to frontend consumers; consumed by the core logger to append error_code automatically to every log record where the error satisfies this interface; internal or infrastructure errors must not carry a code — those get a generic fallback on the frontend
  • ContextualError interface — ErrorContext() map[string]any; implemented by structured errors that carry key-value context fields; consumed by the core logger to append all context fields to the log record automatically; the returned map must not be modified by the caller

security

  • Identity struct — UID, TenantID, DisplayName, Email string; value type, always copied never a pointer, preventing nil-check burden and accidental mutation across concurrent middleware
  • NewIdentity(uid, displayName, email string) Identity — constructs an Identity from token authentication data; TenantID is intentionally left empty for enrichment in a later middleware step
  • Identity.WithTenant(id string) Identity — returns a copy of the Identity with TenantID set; the receiver is not mutated, safe to call from concurrent middleware
  • SetInContext(ctx context.Context, id Identity) context.Context — stores an Identity in the context using a package-private key type, preventing collisions with keys from other packages
  • FromContext(ctx context.Context) (Identity, bool) — retrieves the Identity stored by SetInContext; returns the zero-value Identity and false if no identity is present
  • Permission type — named int64 bit position (062) representing a single capability; applications define their own domain constants using this type
  • MaxPermission constant — highest valid bit position (62); bit 63 is reserved for the sign bit of the underlying int64
  • PermissionMask type — resolved int64 bit-mask for a user on a specific resource; zero value means no permissions granted
  • PermissionMask.Has(p Permission) bool — reports whether the given permission bit is set; returns false for out-of-range values (p < 0 or p > MaxPermission)
  • PermissionMask.Grant(p Permission) PermissionMask — returns a new mask with the bit for p set; the receiver is not modified, safe to use in builder chains
  • PermissionProvider interface — ResolveMask(ctx context.Context, uid, resource string) (PermissionMask, error); implementations may call FromContext to retrieve the Identity and its TenantID when multi-tenancy is required

Design Notes

  • contracts has zero external dependencies and zero Einherjar dependencies. Its go.mod requires nothing beyond the Go standard library. If adding something to contracts requires a new require entry, it does not belong in contracts.

  • Changes flow outward from contracts, never inward. A starter never drives a contracts change. Before any modification, the blast radius is calculated: which interfaces are affected → which starters implement them → which starters consume those starters. Release sequence: contracts first, then implementors, then consumers.

  • The errs sub-package formalises a contract that previously existed only as private duck-typed interfaces inside micro-lib's logz. The two interfaces are intentionally separate (CodedError and ContextualError rather than one combined RichError) to honour ISP: an error may carry a code but no context fields, or context fields but no code. Implementors are never forced to provide both.

  • Checkable lives in contracts/observability, not in the web starter. Data starters (db-postgres, db-sqlite, etc.) implement Checkable without importing the HTTP layer. The health handler in web consumes Checkable — the dependency arrow points into the data layer, never out.

  • Every file in contracts exports exactly one type or interface. This is enforced structurally and mechanically by the compliance test using go/ast parsing. A reviewer reading permission_mask.go knows exactly what they are reading without opening any other file.