# GUIDE — Go under the Discipline, v0.1

[p01] **Status.** Beta; fifth language guide. Sections isomorphic to the Rust/TS/Python/C++ guides — diff them across languages; that is the genre's contract.

[p02] Framing note — the fifth point of the typology. Rust *enforces*; TypeScript *permits but compiles*; Python *trusts*; C++ *demands a subset to survive*; **Go prescribes**. The language ships with its discipline pre-installed: gofmt ended formatting debates, the compiler rejects unused imports, errors are values by culture, inheritance does not exist, `internal/` is compiler-enforced encapsulation. The guide's job therefore inverts — not subset selection but **gap closure**: Go's opinions stop exactly one step short of contract grade in four places, and this guide closes them: (1) errors are values but their *sets* are open; (2) interface conformance is silent; (3) goroutines are unowned by design; (4) `init()` blesses the side-effectful import. Patterns were dissolved by the language designers before we arrived: Strategy = interface + composition root; Decorator = wrapper; Observer = channel/callback seam; Visitor degrades to enum + switch (no sum types — the deepest gap, §4); Singleton = forbidden, and `http.DefaultClient` is its stdlib disguise.

[p03] **Scope honesty.** Services, CLIs, and long-lived system code. Throwaway scripts are out of scope. cgo is boundary-module territory — cells are pure Go (checkable). Code generated by `go generate` follows the generated-code rule: the generator's input is the taggable unit, outputs are excluded.

## 0. Language baseline {#baseline}

- [p04] **Version floor: Go 1.24**, target latest stable. Modules with committed `go.sum`; `GOFLAGS=-mod=readonly` in CI (the lockfile is native — A2 by default). `go.work` is the workspace analog for multi-module repos.
- **Formatting is language-owned:** gofmt is non-negotiable and costs the Discipline zero budget — the one language where the style war was won upstream.
- **Gates:** `go vet` MUST; staticcheck (MIT) MUST; `govulncheck` (BSD-3) in CI (supply-chain floor). golangci-lint as the aggregation harness — **license flag: GPL-3.0**; legal under the Charter's policy only as a separate-process dev tool, never vendored or linked; if even tool-level GPL is unwanted, invoke staticcheck + vet + individual linters directly.
- **Suppression policy (xfail-strict by construction):** bare `//nolint` is banned; only `//nolint:<linter> // <reason>` is legal, and golangci-lint's `nolintlint` (require-specific + require-explanation + flag-unused) makes stale or vague suppressions a build failure — the suppression registry shrinks truthfully, same mechanism as `@ts-expect-error` and pyright's unnecessary-ignore.
- **Race detector gates tests:** `go test -race` is the MUST configuration for any package that starts a goroutine; findings are failures.
- **Generics:** legal and bounded — type parameters for containers/algorithms in infra packages; domain seams stay interface-based unless a measured hot path says otherwise. No type-parameter theater (R-021).
- **Boundary validation (parse, don't validate):** JSON decoding is loose by default — missing fields become zero values silently, unknown fields are ignored. Boundary decode uses `DisallowUnknownFields` plus explicit validation; boundary DTO structs convert explicitly into domain types; absent-vs-zero ambiguity is resolved with pointer fields or a validation layer at the boundary, never guessed in cells.

## 1. Cells {#cells}

[p05] A cell is a **package** under `internal/cells/<name>` — `internal/` makes non-registry imports a compile error from outside the module, and the in-module sibling ban is checked at T-syn from the import graph.

- [p06] **Import-is-execution, Go edition: `init()` and blank imports.** Go's stdlib itself blesses registration-at-import (`database/sql` drivers, image codecs, `_ "net/http/pprof"`), which is exactly why the rule must be explicit: **`init()` and blank imports are banned in cells.** The single carve-out is boundary adapters that wrap stdlib-style driver registration — registration happens there or in the composition root, never as a side effect of importing domain code. Package-level `var` with non-constant initializers is banned in cells for the same reason.
- **No ambient state:** cells never touch `http.DefaultClient`, the global `log`/`slog` default, `os.Getenv`, `flag.CommandLine`, `math/rand` globals, or `time.Now` directly. Capabilities are injected at construction — and Go makes this uniquely cheap: a cell declares the narrow interface it needs *privately* (`type clock interface{ Now() time.Time }`) and structural typing does the rest. No central capability package required; no mocking framework either — tests hand in literal implementations.
- **`context.Context`** is the cancellation capability: first parameter of every potentially-blocking seam method, never stored in a struct field.
- **Exports are the surface:** a cell package exports its constructor (`New(...)`) and nothing else beyond seam-required types. Exported-but-unreferenced identifiers are findings.
- **Promotion** to a separate module on the usual triggers (heavy optional deps, independent release cadence, ~2 kLoC).

[p07] Cell manifest (directive carrier, §5) plus the conformance assertion:

[p08]
```go
//spec:implements spec://org.vibevm.core/vibevm/modules/vibe-resolver/PROP-003#solver-upgrade r=2
//spec:cell seam=DepSolver variant=sat replaces=naive flag=solver
type SatDepSolver struct{ /* ... */ }

var _ resolver.DepSolver = (*SatDepSolver)(nil) // silent conformance made loud — MUST

func New(p resolver.DepProvider, log *slog.Logger) *SatDepSolver { /* ... */ }
```

## 2. Seams {#seams}

- [p09] **Product seams are central; capability interfaces are consumer-side.** The seam (the flag-selectable, replaceable contract) lives in a neutral package; the Go idiom "define interfaces where they're consumed" is *kept* for injected capabilities inside cells. This split resolves the cultural collision instead of overruling it.
- "Accept interfaces, return structs": constructors return the concrete `*SatDepSolver`; only the registry hands out the seam interface.
- Every cell carries the compile-time conformance assertion (`var _ Seam = (*Impl)(nil)`); conform checks its presence (T-syn) — structural typing stops being silent.
- Seam methods that can fail return `(T, error)` where the error belongs to the seam's **closed error set** (§4); values crossing seams are plain structs with useful zero values or explicit constructors — no half-initialized exports.

## 3. Registry and flags {#flags}

[p10] R-001 binding — flag at the seam, never in the veins:

[p11]
```go
// internal/registry — the only flag reader and the only package
// permitted to import cell packages.
func DepSolver(cfg Config, p resolver.DepProvider, log *slog.Logger) resolver.DepSolver {
	switch cfg.Solver { // provenance: default | env | cli | lockfile
	case SolverSat:
		return satdepsolver.New(p, log)
	default:
		return naivedepsolver.New(p, log)
	}
}
```

- [p12] **Two tiers, never confused:** build tags (`//go:build`) answer *"is the code in the binary"* — the cargo-feature analog, with per-file granularity — and are confined to registry/adapter files, never inside cell bodies (T-lex); runtime flags answer *"is the cell selected"*, read once into a config struct in `main`.
- **Delivery-mode honesty:** Go has no credible lazy in-process loading (the `plugin` package is platform- and version-locked); eager is the only mode, presence is the build tier's job.
- No self-registration (now impossible in cells — `init()` is banned), no DI frameworks, no reflection-based wiring. The registry `switch` is the system's table of contents.

## 4. Errors as contract {#errors}

[p13] Go already made errors values; the Discipline makes their *sets* part of the contract:

- [p14] **Each seam owns a closed, enumerated error set:**

[p15]
```go
type SolveErrorCode int

const (
	ErrCycle SolveErrorCode = iota + 1
	ErrUnsatisfiable
)

type SolveError struct {
	Code SolveErrorCode
	Spec string // violated REQ URI: "spec://...#req-acyclic"
	Err  error  // wrapped cause
}

func (e *SolveError) Error() string { /* renders message + Spec */ }
func (e *SolveError) Unwrap() error { return e.Err }
```

[p16] Consumers use `errors.As` against the published type and switch on `Code`; rendering at the boundary appends the REQ URI (PROP-014 §2.6).

- [p17] **Banned at seams:** matching on error strings; `fmt.Errorf` without `%w` (breaks the chain); returning anonymous `errors.New` for expected failures; `error` returns that are sometimes nil-with-meaning.
- **Exhaustiveness — the deepest gap:** Go has no sum types and no exhaustive `switch`. Closed sets are const-enums, and the `exhaustive` linter (evidence provider) supplies what the compiler won't — the fifth binding of the same Discipline rule, and the only one carried entirely by a linter.
- **panic = invariant violation** — the analog is native, same word. `recover` is legal only at goroutine/boundary top level (e.g. middleware), never as control flow in cells; panicking on expected failures is banned.
- **Structured concurrency by ownership:** every goroutine a cell starts has an owner — `errgroup.Group` (BSD-3) or `WaitGroup` + context cancellation; naked `go` with cell-outliving lifetime is banned; channels are owned and closed by their spawner. The unowned goroutine is Go's unreferenced `create_task`, with no GC to even cancel it.
- **Release map for free:** every Go binary carries `runtime/debug.ReadBuildInfo` — VCS revision, dirty flag, module versions — readable from the artifact itself (`go version -m`). The A1 chain *binary → build info → specmap@commit → REQ* needs zero extra machinery; the only rule is not to strip what the runtime gave you (panic stacks stay symbolized, or symbolized copies are retained).

## 5. specmark carrier {#specmark}

[p18] **Directive comments**, not doc-comment tags — a deliberate divergence from the TS/Python choice, forced by the toolchain: since Go 1.19 gofmt *reformats doc comments* and could re-wrap prose tags, but preserves `//name:value` directive lines verbatim, and godoc hides them. Go already owns the cultural slot (`//go:generate`, `//go:embed`); the Discipline takes `//spec:`:

[p19]
```
//spec:implements <uri> r=<N>                 one edge per line; lines repeat
//spec:deviates <uri> r=<N> reason="..."      reason mandatory
//spec:verifies <uri> r=<N>                   above Test/Fuzz functions
//spec:scope <uri> r=<N>                      in the package doc block (doc.go) — package-level inheritance
```

[p20] Parsed via `go/ast` comment maps; gofmt-proof by construction. The trade-off is named honestly: provenance disappears from rendered godoc and lives in `vibe explain`/the ledger instead. ≤3 edges per item or split.

## 6. Naming (R-020/R-021 bindings) {#naming}

- [p21] Canonical cell type name is computed: `{Variant}{Seam}` → `SatDepSolver`; the package is the lower-case variant (`satdepsolver`). Linted against the manifest.
- **Forbidden in cells regardless of elegance** — the Go theater list: `init()` and blank imports (§1); reflection-based wiring and struct-tag DSLs in domain code (tags are for boundary DTOs); `interface{}`/`any` in domain signatures where a type or small interface fits; method sets split across files to obscure a type; clever channel topologies as API (channels are implementation, seams are methods); `recover` as control flow; package-level mutable state; `unsafe` outside designated boundary files.

## 7. Replacement protocol (R-040 binding) {#replacement}

[p22] A cell with `replaces=` ships a differential oracle on **native fuzzing** (`go test -fuzz` corpus + deterministic seeds in CI): one fuzz target drives both cells through the seam and asserts agreement modulo a documented divergence list, `//spec:verifies`-tagged, run with `-race`. Golden files live in `testdata/` and follow the promotion protocol — the conventional `-update` flag never runs in CI, and a local update carries a debt/intent reference. **xfail honesty:** Go has no native strict-xfail; `t.Skip` on a known-failing test is banned (skip hides both regressions and healings) — known failures live only in `tests-baseline.json`, which carries full weight here, the one language in the set without an in-source twin.

## 8. Risk table (what conform must cover for Go) {#risks}

[p23]
| Footgun | Rule | Tier |
| --- | --- | --- |
| `init()` / blank import / non-const package `var` in a cell | §1 | T-syn |
| ambient default used in a cell (`http.DefaultClient`, global log, `os.Getenv`, `time.Now`) | §1 | T-syn |
| naked `go` with cell-outliving lifetime; channel without owner | §4 | T-syn + review |
| `context.Context` stored in a struct / not first param | §1 | T-syn (vet) |
| error-string matching; `fmt.Errorf` without `%w` at a seam | §4 | T-syn |
| expected failure outside the seam's closed error set | §4 | T-sem |
| non-exhaustive switch on a closed const-enum | §4 | T-sem (exhaustive) |
| typed-nil stuffed into an interface | §2 | T-sem (staticcheck) |
| missing `var _ Seam = (*Impl)(nil)` conformance assertion | §2 | T-syn |
| `t.Skip` on a known-failing test | §7 | T-lex + test-gate |
| sibling-cell import; cell imported outside the registry | R-002 | T-syn |
| `//go:build` product tag inside a cell body | §3 | T-lex |
| bare `//nolint` / stale suppression | §0 | nolintlint |
| `unsafe` / cgo outside boundary files | §0, §6 | T-lex |
| flag read outside the registry | R-001 | T-syn |
| public export without own/inherited spec edge | PROP-014 §3.2-6 | T-syn + index |

## 9. Doc layer {#docs}

[p24] Godoc comments on every exported identifier (the language's own lint culture already expects this): behavior, the seam's error codes with their REQ URIs, goroutine and channel ownership, context semantics. The `//spec:` directives sit in the same comment block but stay out of the rendered page; human-facing provenance is the ledger's job. Spec stays thin; duplication between godoc and spec is a defect on the spec side.

[p25] **First carrier note.** No Go exists in vibevm; no carrier is designated. The guide ships genre-complete and unexercised, under the carrier-relative house clause: rules remain DRAFT until a first Go carrier exists, and at that carrier's first milestone any rule without a conform check (or explicit `WISH` mark) is removed rather than carried as aspiration.

