Files
contracts/README.md

138 lines
5.3 KiB
Markdown
Raw Permalink Normal View History

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
# einherjar/contracts
[![version](https://img.shields.io/badge/version-v1.0.0-5C4EE5?style=flat-square)](https://code.nochebuena.dev/einherjar/contracts)
[![license](https://img.shields.io/badge/license-AGPL--3.0-22863A?style=flat-square)](LICENSE)
[![go](https://img.shields.io/badge/Go-1.26+-00ADD8?style=flat-square&logo=go&logoColor=white)](https://go.dev)
> *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`](lifecycle/) | `Component` — three-phase managed lifecycle (OnInit → OnStart → OnStop) |
| [`observability`](observability/) | `Checkable` + `Level` — health reporting for infrastructure components |
| [`logging`](logging/) | `Logger` — structured, leveled logging |
| [`errs`](errs/) | `CodedError` + `ContextualError` — structured error enrichment consumed by the logger |
| [`security`](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.
```go
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
```go
// 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
```go
// 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
```go
// 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
```bash
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.*