79 lines
3.4 KiB
Markdown
79 lines
3.4 KiB
Markdown
|
|
# 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.
|