# ADR-002: File-per-Type Naming Convention - **Date:** 2026-05-27 - **Module:** `code.nochebuena.dev/einherjar/contracts` - **Status:** Accepted ## Context Framework-level ADR-004 establishes that `contracts` sub-packages contain one file per interface or type (CT-6). It does not specify how those files are named. Without an explicit convention, names tend to drift: `types.go`, `interfaces.go`, `models.go`, or `common.go` — filenames that say nothing about what is inside. The naming problem compounds in `security`, which has five declarations across five files. A developer looking at the directory listing needs to know immediately which file to open. ## Decision Every source file in `contracts` is named after the single type or interface it declares, converted to lowercase and snake_cased for multi-word names. | Declaration | File | |---|---| | `Component` | `component.go` | | `Checkable` | `checkable.go` | | `Level` | `level.go` | | `Logger` | `logger.go` | | `CodedError` | `coded_error.go` | | `ContextualError` | `contextual_error.go` | | `Identity` | `identity.go` | | `Permission` | `permission.go` | | `PermissionMask` | `permission_mask.go` | | `PermissionProvider` | `permission_provider.go` | `doc.go` files are the sole exception — they carry the package-level doc comment and export nothing. Constants, package-level variables, and functions that are semantically part of a type's API (constructors, context helpers, builder methods) coexist in the same file as the type they serve. They are not counted as separate declarations for CT-6 purposes — CT-6 governs type declarations, not every exported symbol. Examples: - `level.go` declares `Level` and also defines `LevelCritical` and `LevelDegraded` - `permission.go` declares `Permission` and also defines `MaxPermission` - `identity.go` declares `Identity` and also defines `NewIdentity`, `WithTenant`, `SetInContext`, and `FromContext` ## Alternatives Considered **Single `types.go` per sub-package.** Rejected — a file named `types.go` provides no information to a developer scanning a directory. They must open it to know what is inside. **Interface files named `interface.go` or `contract.go`.** Rejected — same reason. The filename should answer "what contract does this file define?" not "what kind of file is this?" **Separate files for each function and constant.** Rejected — excessive fragmentation. A constructor and the type it constructs are a single conceptual unit. Splitting them forces a developer to open two files to understand one thing. ## Consequences **Easier:** `find . -name "permission_mask.go"` returns exactly the file that defines `PermissionMask`. A directory listing of `security/` reads as a vocabulary list for that sub-package's domain. Code review diffs are unambiguous — a change to `permission_provider.go` is a change to the `PermissionProvider` contract. **Harder:** Multi-word type names require a deliberate naming decision at the file level. The snake_case rule eliminates ambiguity (`permission_mask.go` is the only valid name for `PermissionMask`) but must be applied consistently across all modules. **New obligations:** Every Einherjar module inherits this convention. When a new type is added to any module, its file is named after the type. This is not optional — the compliance test enforces the one-type-per-file structural invariant, and the naming convention is the human-readable complement to that mechanical check.