VibeVM
Contents
On this page
en
Publisher
org.vibevm.ai-native
Version
1.0.0latest
Audiences
Reading time
8 min
Rendered
Read aloud
never

GUIDE — Go under the Discipline, v0.1

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

02Framing 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.

03Scope 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

  • 04Version 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

05A 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.

  • 06Import-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).

07Cell manifest (directive carrier, §5) plus the conformance assertion:

08//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

  • 09Product 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

10R-001 binding — flag at the seam, never in the veins:

11// 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)
	}
}
  • 12Two 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

13Go already made errors values; the Discipline makes their sets part of the contract:

  • 14Each seam owns a closed, enumerated error set:
15type 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 }

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

  • 17Banned 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

18Directive 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::

19//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

20Parsed 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)

  • 21Canonical 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)

22A 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)

23
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

24Godoc 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.

25First 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.

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.ai-native/core-ai-native@1.0.0/legacy-projections/GUIDE-GO-v0.1

.md.xmlllms.txt