Discipline¶
TL;DR¶
- RFC 2119 vocabulary (MUST / SHOULD / MAY / MUST NOT / SHOULD NOT) is the spine of every prescription in this guide. Engineers and LLM agents MUST read each keyword in its RFC 2119 sense, not as colloquial English.
- The seven project-root principles (correctness over speed, modular by default, proto enums as single source of truth, air-gap first, real backends in tests, fix root causes not symptoms, no backward-compatibility burden in pre-production) MUST be internalized by every contributor to a guide-conformant service.
- The Uber Go Style Guide and the Google Go Style Guide are the two canonical industry references for Go code in a guide-conformant service. Conflicts between them SHOULD be resolved in favor of the rule that produces the more reviewable diff.
- The Twelve-Factor App methodology SHOULD be applied to runtime configuration, process model, port binding, and disposability. Twelve-Factor predates the cloud-native era but its discipline still travels.
- Agent-runner subdirectory content (telemetry, audit-reference, ralph
discipline, team coordination, language-specific override rules) is
intentionally out of scope in this chapter. The boundary lies at the
project-root
CLAUDE.md(or equivalent project-root marker) file; deeper subdirectories belong to whatever agent runner an adopting org chooses, not to this guide.
Why this choice¶
Engineering discipline is the most portable and the most teachable artifact a software organization produces. A new contributor who internalizes the project-root principles in the first hour of onboarding produces code that reviewers can accept on the first pass; a contributor who never internalizes them produces code that reviewers must continually re-shape, every PR, for the rest of the project's life.
The choice of RFC 2119 keywords over softer prescriptive prose ("you should
probably") follows from a single observation: LLM agents grounding their
recommendations on this guide need an unambiguous signal of what is
enforceable. "Prefer this" is rhetorical; SHOULD is enforceable. The
Introduction chapter (00-introduction.md) defines the keywords with the
canonical IETF-style boilerplate so every other chapter in this guide can
use them without re-defining the convention.
The choice to source discipline content from the project-root CLAUDE.md
rather than from any agent-runner subdirectory follows the spec's
architectural placement rule (GI-005): the project-root file describes the
engineering discipline an organization expects from every contributor —
human or agent. The agent-runner subdirectory, by contrast, describes the
tooling discipline a particular pipeline expects from a particular
runner. Tooling rules are tooling-specific; engineering rules are not.
Adopting orgs MAY choose a different agent runner without changing the
engineering discipline this chapter prescribes.
The choice to cite the Uber and Google Go style guides (rather than
proprietary internal handbooks) follows from forkability (GI-001). An
adopting org MUST be able to clone this guide, rename it, and ship it
without negotiating a license with AlphaBravoCompany or any other vendor.
The two industry references are freely licensed, maintained by their
publishing organizations, and recognized as canonical by the Go community.
Prescriptive guidance¶
RFC 2119 keyword discipline¶
- Every prescriptive statement in this guide and in any guide-conformant
service's internal documentation MUST carry exactly one RFC 2119
keyword (
MUST,MUST NOT,SHOULD,SHOULD NOT,MAY). See RFC 2119 for the canonical definitions. - A prescription written without a keyword MUST be treated as informative context only, not as binding guidance. Reviewers SHOULD politely reject PRs that paraphrase a binding rule into keyword-free prose, since paraphrase erodes the signal RFC 2119 was designed to preserve.
- A prescription MAY include parenthetical guidance after the keyword
clause (for example, "Services MUST persist user identifiers via the
domain
user_idtype, not raw UUIDs"). The keyword clause MUST appear first; the parenthetical clarification follows. - A prescription MUST NOT chain two keywords ("services SHOULD MAY persist..."). One keyword per prescription; split the prescription if two clauses with different normative strengths are needed.
- A prescription using
SHOULDMUST be readable as "do this unless there is a stated, reviewed reason not to," and a prescription usingMAYMUST be readable as "either choice is acceptable; the choice is the implementer's." Reviewers MUST NOT silently upgrade aSHOULDto aMUSTduring PR review; the upgrade requires a separate ADR.
Project-root principle: Correctness over speed¶
- Engineers MUST favor correct behavior over wall-clock throughput when the two conflict. A merged PR that produces wrong output is a regression even if every CI gate passed; a delayed PR that produces correct output is the project's nominal state.
- Reviewers SHOULD reject the framing that "we can fix correctness after shipping" for any pre-production codebase. Pre-production is the cheapest window in which to restructure for correctness; post-production drift compounds.
- A code path that is known to be incorrect on rare inputs MUST carry an explicit comment naming the rare input and a tracking issue link, even if the project has elected (in writing, in an ADR) to ship the incorrectness until v1.0.
Project-root principle: Modular by default¶
- Packages MUST own a single domain. A package whose imports cross more than three other domains is a signal the package's responsibility has drifted; reviewers SHOULD ask for a split.
- Handler → service → database layering MUST stay separate. Database queries MUST NOT appear in handler files; handler-shaped HTTP/RPC types MUST NOT appear in database query files. The boundary discipline is what makes a package reviewable.
- A package MAY co-locate small helpers (under ~30 lines, single-purpose, no external state) in the same file as the function that calls them. Larger helpers MUST live in their own file.
- A package MUST NOT export a symbol it does not need to export. Every exported symbol is part of the package's API surface and MUST be treated as load-bearing on review.
Project-root principle: Proto enums as single source of truth¶
- Wire-protocol enum constants MUST be defined exactly once, in the
.protoschema. Go code MUST consume the generated enum type (somepkg.SomeEnumType) rather than redefining a paralleltype SomeStatus stringoriotaenum. - A boundary that crosses from the database (string column or numeric
column) to the proto enum MUST happen in a dedicated
convert.go(or per-package equivalent) file. The boundary MUST be the only place conversion happens; scattering conversions across handler files guarantees drift. - A new enum value added in
.protoMUST be propagated through the conversion helpers in the same PR. Reviewers MUST reject PRs that add proto enum values without updating the conversion helpers, since the resulting build will pass while the runtime quietly mishandles the new value. - Frontend TypeScript MUST import enum types from the generated Connect-Web
output (e.g.,
@/gen/<service>/<version>/<domain>_pb), never from a hand-maintained string-union type. The Surface chapter (03-surface.md) covers the codegen pipeline that makes this possible.
Project-root principle: Air-gap first¶
- Services MUST run in disconnected environments. Every runtime code path MUST be reachable without an out-of-cluster network call. Telemetry exporters, identity providers, container registries, and software update servers MAY be remote in connected deployments but MUST have an offline fallback configured by default.
- Out-of-cluster network calls SHOULD be opt-in, gated by an explicit feature flag or configuration field. A service that silently reaches out to the public internet on first boot violates this principle, even if the call is "just" a version check.
- Container images MUST be available from the embedded / cluster-local
registry before the service references them. The Air-gap chapter
(
09-airgap.md) covers the registry resolver pattern that makes this reliable. - Telemetry SHOULD degrade gracefully when its remote exporter is unreachable: in-process buffering with bounded retention, then dropped spans with a counter increment, MUST be the default behavior over a hard-fail.
Project-root principle: Real backends in tests¶
- Integration tests MUST use real backends — real PostgreSQL via
testcontainers, real S3-compatible object storage (a RustFS or MinIO
fixture), real OCI registries (zot or distribution fixtures) — not
hand-rolled mocks. The Testing chapter (
07-testing.md) covers the fixture infrastructure. - Unit tests MAY mock collaborators inside the same module, but a mock MUST NOT replace a transport boundary. A mock of "the database interface" is a unit test of the consumer; the database itself MUST also be exercised by an integration test.
- A test that passes against a mock but fails against the real backend is a defect in the mock, not in the real backend. The mock MUST be fixed or deleted. Mocks that drift from real-backend behavior are worse than no test at all because they consume reviewer attention.
- End-to-end tests MUST exercise the production binary path, not a test-only re-implementation. Playwright tests SHOULD drive the same static assets the production build serves; in-process E2E reimplementations MUST NOT be substituted for the real binary.
Project-root principle: Fix root causes, not symptoms¶
- A defect report MUST be traced to its upstream cause before a fix lands. A symptomatic patch ("this caller now passes a default to avoid the panic") MUST NOT ship without a follow-up issue documenting the upstream defect.
- Reviewers SHOULD ask "what is the simplest change that would have prevented this defect from being introduced?" If the answer is "a different signature," the fix MUST consider the signature change before patching the call site.
- A workaround MAY be merged when a release deadline forces the hand,
but the workaround MUST be tagged with a
// WORKAROUND: <issue-url>comment and a tracking issue. Workarounds without a tracking issue become permanent technical debt. - A flaky test MUST be diagnosed before being marked
skiporretry. Marking a flake asretrywithout diagnosis is symptom-fixing; the underlying race or timing dependency MUST be understood and removed.
Project-root principle: No backward-compatibility burden in pre-production¶
- Pre-production codebases (those without a stamped v1.0 release) SHOULD tolerate schema migrations, API renames, and structural deletions without backwards-compatibility shims. Carrying compatibility code into v1.0 SHOULD be avoided because every shim is a future maintenance obligation.
- A migration that renames a column or restructures a JSONB field MAY be
written as a hard cutover during pre-production, with both an
Upand aDowndirection defined. The Data chapter (02-data.md) covers migration discipline. - An API field renamed in pre-production MUST update all known consumers in the same PR. A consumer caught later MUST be repaired by a follow-up PR rather than by re-introducing the old field name.
- Post-v1.0, the discipline reverses: deprecation cycles, dual-write windows, and feature flags MUST be the default. Pre-production is the one window when "rename, restructure, delete freely" is the cheaper policy.
Industry references¶
- Engineers MUST treat the Uber Go Style Guide as the canonical reference for Go code style in a guide-conformant service. Where the Uber guide and the Google Go Style Guide agree, that consensus is binding. Where they disagree, reviewers SHOULD select the rule that produces the more reviewable diff.
- Engineers SHOULD treat the Twelve-Factor App methodology as the canonical reference for runtime configuration discipline (factor III), process model (factor VIII), port binding (factor VII), and disposability (factor IX). Twelve-Factor was published in 2011 but its discipline travels intact into 2026 cloud-native contexts.
- A service that imports the project-root principles MUST also implement
the Infra and Tooling chapter's tooling discipline
(
04-infra-tooling.md). The principle is not portable without the tooling —make dev-up, Air, Vite, golangci-lint v2 — that turns "correctness over speed" into a live-reload feedback loop measured in seconds.
Out-of-scope: agent-runner subdirectories¶
- Engineering discipline content in this chapter MUST NOT cite, quote, or
paraphrase any file under an agent-runner subdirectory (for example
<runner-config-dir>/orchestrator.md,<runner-config-dir>/ralph-discipline.md,<runner-config-dir>/team-coordination.md,<runner-config-dir>/quality.md,<runner-config-dir>/telemetry.md,<runner-config-dir>/audit-reference.md, or<runner-config-dir>/languages/<lang>/rules.md). Those files describe a particular agent runner's pipeline; this guide stays runner-agnostic so adopting orgs MAY swap runners without losing the engineering rules. - An adopting org MAY layer its own agent-runner discipline on top of this guide. The layering MUST live in the adopter's own repository, not in the guide itself.
Industry context (informative)¶
The community has converged on
AGENTS.md as an open standard adopted by 20+ tools
and hosted under the Linux Foundation. A guide-conformant service MAY
publish an AGENTS.md at its repository root that summarizes the
engineering discipline of this chapter for agent-runner consumption.
That AGENTS.md SHOULD be a derived artifact, not the source of truth;
the source of truth is the project's own CLAUDE.md (or whichever
project-root marker the project elects) plus this chapter.
The choice to keep this guide CLAUDE.md + AGENTS.md-agnostic is
deliberate: adopting orgs select their own agent-runner conventions, and
the guide MUST remain useful regardless of that selection. This chapter
prescribes the engineering rules; the file that publishes those rules to
a runner is the adopter's call.
Reference Implementation: Pioneer
The donor codebase publishes the seven generalized principles above
in its project-root file at
/home/ubuntu/pioneer/CLAUDE.md. That file lists the principles
verbatim under a top-level Principles heading, then enumerates
project-specific rules and architecture tables (dev loop commands,
migration discipline, foundation conventions) that an adopting org
can borrow with appropriate generalization. Adopters MAY copy the
structure of that file (Principles → Rules → Architecture →
Foundation Conventions) and substitute their own organization's
rules, keeping the generalized principles above as the engineering
spine.
The same project-root file is the canonical source for this guide's
Discipline chapter; no agent-runner subdirectory under that
project's <runner-config-dir>/ is cited, quoted, or paraphrased
anywhere in this guide. The boundary lies at the project-root
CLAUDE.md file.
Pinned versions¶
Not applicable — discipline is a methodology, not a versioned dependency.
The version pins that matter to discipline (Go toolchain, golangci-lint,
RFC 2119 itself) live in the Architecture (01-architecture.md) and
Infra and Tooling (04-infra-tooling.md) chapters where they are
exercised by the build system.
| Reference | Version / revision | Source |
|---|---|---|
| RFC 2119 | March 1997 (stable) | ietf.org/rfc/rfc2119.txt |
| Uber Go Style Guide | rolling main branch | github.com/uber-go/guide |
| Google Go Style Guide | rolling published version | google.github.io/styleguide/go |
| Twelve-Factor App | 2011 publication (still authoritative) | 12factor.net |
Snapshot date: 2026-05-08. The four references above are stable enough that quarterly review SHOULD only re-confirm the URLs resolve and the upstream document has not been superseded.
Pitfalls¶
- Treating RFC 2119 keywords as decorative. A
SHOULDis not a stylistic flourish; it is a binding rule with a documented exception pathway. Reviewers MUST NOT accept PRs that ignore aSHOULDwithout citing the exception they are relying on. - Sourcing discipline from agent-runner subdirectories. The
<runner-config-dir>/family of files belongs to a particular runner. Pulling content from those files into chapter prose violatesGI-002/GI-005and welds this guide to one runner. - Mocking a transport boundary. Mocking the database interface,
the object-storage interface, or the OCI registry interface lets a
test pass against a fiction. The Testing chapter (
07-testing.md) prescribes real-backend fixtures via testcontainers; this chapter's Principle 5 is the conceptual root. - Patching a panic with a default value. Defaulting away a panic hides the upstream defect (a nil pointer, an unset field, a missing enum case). Principle 6 ("Fix root causes, not symptoms") MUST be applied; the upstream defect MUST be reported and fixed.
- Carrying a compatibility shim through pre-production. A shim
added in pre-production "for now" almost never gets removed before
v1.0. The Data chapter (
02-data.md) covers hard-cutover migration discipline; this chapter's Principle 7 is the conceptual root. - Citing one Go style guide without checking the other. Uber and Google generally agree, but where they diverge the divergence is load-bearing. A reviewer who cites only one guide is implicitly choosing for the project; that choice belongs in an ADR.
See also¶
- The Introduction chapter (
00-introduction.md) for the canonical RFC 2119 keyword definitions and the audience descriptions that frame the rest of this guide. - The Architecture chapter (
01-architecture.md) for the package layering pattern that makes Principle 2 ("Modular by default") tractable in practice. - The Data chapter (
02-data.md) for the soft-delete, org-scoping, and hard-cutover migration patterns that make Principles 3 and 7 concrete. - The Surface chapter (
03-surface.md) for the proto-enum boundary pattern that makes Principle 3 ("Proto enums as single source of truth") concrete in handler code. - The Infra and Tooling chapter (
04-infra-tooling.md) for the live-reload dev loop that makes Principle 1 ("Correctness over speed") survive the daily edit cycle. - The Testing chapter (
07-testing.md) for the real-backend fixture infrastructure that makes Principle 5 concrete in CI. - The Air-gap chapter (
09-airgap.md) for the registry resolver pattern that makes Principle 4 concrete in disconnected deployments. - The decisions subdirectory ADR
0003-rfc-2119-tone.md(when published) for the rationale behind the RFC 2119 prescription tone selected for the whole guide. - The decisions subdirectory ADR
0005-no-claude-subdir-content.md(when published) for the rationale behind excluding agent-runner subdirectory content from this chapter.