# Changelog All notable changes to this module will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- ## [1.1.0] — 2026-05-28 ### 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 (0–62) 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. [1.0.0]: https://code.nochebuena.dev/einherjar/contracts/releases/tag/v1.0.0