Files
contracts/README.md
Rene Nochebuena 098a2098f8 feat(contracts): initial implementation (v1.0.0)
Introduces code.nochebuena.dev/einherjar/contracts — the zero-dependency
foundation of the Einherjar framework. Defines the interfaces and minimal
types consumed by every starter. Zero external dependencies. Zero Einherjar
dependencies. Nothing is above it in the dependency graph.

lifecycle:
- Component — OnInit, OnStart, OnStop three-phase lifecycle hooks

observability:
- Level (LevelCritical=0, LevelDegraded); zero value is the safe default
- Checkable — HealthCheck, Name, Priority
- Identifiable — ModulePath, ModuleVersion; implemented by all starters to
  surface module identity and version in the startup banner

logging:
- Logger — Debug, Info, Warn, Error, With, WithContext

errs:
- CodedError — ErrorCode() string; satisfied by core/xerrors.Err
- ContextualError — ErrorContext() map[string]any; satisfied by core/xerrors.Err

security:
- Identity value type — UID, TenantID, DisplayName, Email; NewIdentity, WithTenant
- Permission (int64), MaxPermission=62, PermissionMask — Has, Grant
- PermissionProvider — ResolveMask(ctx, uid, resource) (PermissionMask, error)
- SecurityBag value type — immutable request-scoped security context; carries
  Identity and arbitrary typed attributes (hardware IDs, grant codes, etc.);
  With copies the attribute map on every call to preserve receiver-invariant behaviour
- NewSecurityBag, Identity, WithIdentity, Get, With
- SetBagInContext / BagFromContext — full bag context storage
- SetInContext / FromContext — backed by SecurityBag; all four cross-function
  combinations (SetInContext+BagFromContext, SetBagInContext+FromContext) are valid

One file per type; CT-6 enforced by compliance test AST walk.
2026-05-29 15:43:08 +00:00

5.3 KiB

einherjar/contracts

version license go

For those who come after.

The foundation of the Einherjar framework. Pure interfaces. Zero dependencies. A constitution, not a changelog.

contracts defines the interfaces and minimal types that every Einherjar starter depends on. Nothing is above it in the dependency graph. Its interfaces, once published, are permanent — adding a method is a breaking change in Go, and breaking changes require a major version bump and coordinated updates across the entire framework.


What Is Einherjar

In Norse mythology, the Einherjar are the chosen — warriors who fell in battle and were carried to Valhalla by the Valkyries. There they train, day after day, preparing for Ragnarök. They are not preserved for themselves. They are prepared for what comes after.

This framework carries that name deliberately. Every interface defined here, every ADR written, every pattern documented exists for the developer who will build on this code without ever being in the room where it was designed. No tribal knowledge. No implicit conventions. The documentation is the system.


Module

code.nochebuena.dev/einherjar/contracts

Dependencies: none. This module imports only the Go standard library. Stability guarantee: existing interface signatures are permanent. New interfaces may be added. Existing ones are not modified.


Sub-packages

Package Purpose
lifecycle Component — three-phase managed lifecycle (OnInit → OnStart → OnStop)
observability Checkable + Level — health reporting for infrastructure components
logging Logger — structured, leveled logging
errs CodedError + ContextualError — structured error enrichment consumed by the logger
security Identity, Permission, PermissionMask, PermissionProvider — authentication and authorization primitives

Usage

Import only the sub-packages you need. There is no root contracts package to import.

import (
    "code.nochebuena.dev/einherjar/contracts/lifecycle"
    "code.nochebuena.dev/einherjar/contracts/logging"
    "code.nochebuena.dev/einherjar/contracts/observability"
    "code.nochebuena.dev/einherjar/contracts/errs"
    "code.nochebuena.dev/einherjar/contracts/security"
)

Implementing a managed component

// MyClient satisfies lifecycle.Component and observability.Checkable.
var _ lifecycle.Component   = (*MyClient)(nil)
var _ observability.Checkable = (*MyClient)(nil)

func (c *MyClient) OnInit() error                        { /* open connection */ }
func (c *MyClient) OnStart() error                       { return nil }
func (c *MyClient) OnStop() error                        { /* close connection */ }
func (c *MyClient) HealthCheck(ctx context.Context) error { /* ping */ }
func (c *MyClient) Name() string                          { return "my-client" }
func (c *MyClient) Priority() observability.Level         { return observability.LevelCritical }

Implementing structured errors

// MyErr satisfies errs.CodedError and errs.ContextualError.
var _ errs.CodedError       = (*MyErr)(nil)
var _ errs.ContextualError  = (*MyErr)(nil)

func (e *MyErr) ErrorCode()              string         { return e.code }
func (e *MyErr) ErrorContext()           map[string]any { return e.ctx }

The logger in core consumes these interfaces to append error_code and context fields to every log record automatically — without either package importing the other.

Working with identity and permissions

// Store identity after authentication.
ctx = security.SetInContext(ctx, security.NewIdentity(uid, name, email))

// Retrieve identity downstream.
id, ok := security.FromContext(ctx)

// Resolve and check permissions.
mask, err := provider.ResolveMask(ctx, id.UID, "orders")
if !mask.Has(PermWrite) { /* deny */ }

Dependency Rules

contracts sits at the root of the Einherjar dependency graph:

contracts
    └── core          (absorbs launcher, logz, xerrors, valid)
         ├── web      (absorbs httpserver, httpmw, httputil, health)
         │    └── auth
         │         ├── auth-jwt
         │         └── auth-firebase
         └── db-*, cache-*, storage-*, telemetry, worker, httpclient

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


Compliance

go build ./...   # zero external dependencies
go vet ./...
go test ./...    # structural (one type per file) + behavioural tests
gofmt -l .       # no output

The Einherjar did not train for themselves. They trained for Ragnarök — for the battle they knew was coming, for those who would need them to be ready. Write code the same way.