Skip to content

Feature Specifications

A specification is the decision record a change cites. For a library consumed by other modules and by real services, the expensive mistakes are design mistakes — a public API shape that cannot be changed cheaply, or a dependency that lands in every downstream binary. A spec is where those get caught, before there is code to defend.

Specs are drafted collaboratively with an AI assistant and reviewed by a human before any implementation begins.

When a spec is required

Change Spec?
A new package Yes
Any public API addition or change Yes
A new dependency Yes — this is the one that gets waved through, and shouldn't
A change to the OTel version floor Yes — it is a cross-repo change
A new configuration or resolution rule in otelcore Yes
Bug fix with no public API change No
Documentation correction No
Internal refactor, public API untouched No
Dependency bump within the existing pins (Renovate) No

Where they live

The specs live in the project wiki, as specs/NNNN-<feature-name>, numbered in creation order. They are point-in-time decision records rather than living documentation, and keeping them in docs/ buries the documentation that is living.

This page stays in the repo because it is process documentation — the format and lifecycle a spec must follow — not a decision record itself.

# Spec Status
0001 RED/USE instrumentation helpers DRAFT

Pre-extraction specs

This module was extracted from go-tool-base; specs written before the extraction settled stayed there, including observability module extraction and the telemetry analytics/observability split.

Lifecycle

Status Meaning
DRAFT Written, not yet reviewed. Do not implement.
IN REVIEW Under human review. Do not implement.
APPROVED Reviewed and accepted. Implementation may begin.
IN PROGRESS Implementation underway.
IMPLEMENTED Shipped.
SUPERSEDED Replaced by a later spec — link to it.

Status appears in both the frontmatter and the document header, and both are updated together. A spec and its implementation share a branch and a merge request, so the rationale lands next to the code in git history. A spec-only merge request is prefixed [SPEC].

Format

Frontmatter

---
title: "<Feature name> for go/observability"
description: "<A dense paragraph: what this builds, what it deliberately does not, and why.>"
date: YYYY-MM-DD
status: DRAFT
tags:
  - specification
  - observability
author:
  - name: <Human author>
    email: <email>
  - name: <AI assistant, if one drafted it>
    role: AI drafting assistant
---

Crediting an AI drafting assistant in a spec's author block is correct and expected — a spec is a design record, and knowing how it was produced is part of reading it well. This is the one place AI attribution belongs: it must never appear in commit messages, merge request descriptions, or merge request comments, where the human who approves the change owns it entirely.

Document header

Below the # title, using Material definition-list syntax:

Authors
:   <names>

Date
:   YYYY-MM-DD

Status
:   DRAFT (YYYY-MM-DD). <What it is waiting on.>

Related
:   <Other specs, upstream issues, or external references this builds on.>

Required sections

  1. Context & goal — the problem, and what currently forces the caller to solve it themselves.
  2. What NOT to build — for this module especially, scope is defined as much by exclusion as inclusion. The OTel ecosystem already covers a great deal (runtime instrumentation, otelhttp and otelgrpc request metrics, SDK-native exemplars, resource attributes); reimplementing any of it is a defect. Name what you are deliberately leaving to upstream.
  3. Invariant check — argue the design against the three invariants in the contributor guide, explicitly:
    • Framework-free — does this add a dependency? Justify it against depfootprint_test.go and say whether the forbidden list needs extending.
    • Typed values in, providers out — does this pull config sourcing, environment scanning, or file parsing into the module? It must not.
    • Signal independence — does this make a trace-only service link the log or metric exporter? It must not.
  4. Public API — exact signatures, types, and their contracts. Prefer functional options on the existing NewProvider signatures so adoption is not a breaking change; where a break is warranted, state what it costs go-tool-base as the primary consumer.
  5. Data models — types, defaults, and resolution rules.
  6. Error cases — what fails, what it wraps, and what the caller can do about it.
  7. Testing strategy — TDD front and centre: which contracts become failing tests first, which failure paths get exporter_error_test.go coverage, and whether integration tests against a real collector are needed (with the environment-variable gate they use). If the feature needs mockery, testcontainers, or godog, resolve the structural questions here rather than in the test file — see the contributor guide.
  8. Documentation plan — a hard requirement, not a follow-up. Name the pages to add or update and the Diátaxis quadrant each belongs to. All four quadrants are in-repo: a public API addition needs a docs/reference/ page covering its keys, options, defaults and failure modes as well as doc comments, and a tutorial belongs in docs/tutorials/ rather than only as a blog post. Note any page being moved or renamed, and the docs/_redirects entry that keeps its old URL alive. Specs written before 2026-08 state the opposite on both counts — see the contributor guide for what changed.
  9. Implementation phases — a prioritised breakdown, each phase independently testable.
  10. Open questions — every unresolved design decision, gap, and ambiguity. These must be answered or explicitly deferred before implementation starts, not discovered mid-build.

Implementing an approved spec

Implementation is test-driven, phase by phase: write the failing tests for a phase from its contracts and error cases, implement the minimum code to pass them, refactor with the suite green, then run just test-race and just lint-fix before moving on.

Before marking a spec IMPLEMENTED:

  • just ci passes — tests, race detector, and lint, including the depfootprint_test.go guard.
  • New code has at least 90% coverage.
  • No //nolint was added to bypass an issue.
  • Every exported symbol has a doc comment; pkg.go.dev is this module's API reference.
  • The documentation named in the spec's documentation plan has landed.
  • Any public API break is noted in the commit body with the downstream impact.

Specs