Milestone convention
Milestone convention
Status: accepted (gm-root.3.1) Parent epic: gm-root.3 — Milestones as first-class stage-gate construct in Gemba
Why this doc exists
Gemba treats “milestone” as a first-class WorkItem kind — distinct from
an epic, a task, or a bug — because milestones gate work across phases
and need to be filterable, queryable, and addressable like any other
WorkItem. The underlying WorkPlane adaptor (today: bd) doesn’t have a
native milestone type, so Gemba encodes the distinction with a label
convention that projects both ways through the adaptor boundary.
This document is the canonical source of truth for that convention. Adaptor code and any downstream feature (UI badges, roll-ups, phase-gating logic) MUST follow it.
The rule
A bead is a milestone iff it carries the label type:milestone.
That’s the whole convention. Everything below is a consequence of this one rule.
Where it lives in the stack
Core
internal/core/types.go
// KindMilestone is Gemba-native: there is no native "milestone" type in// bd. The Beads adaptor encodes a milestone as `-t epic` + label// "type:milestone" and projects that convention onto KindMilestone on// read. Filtering WorkItemFilter.Kinds to {KindMilestone} returns only// the label-flagged beads.const KindMilestone = "milestone"WorkItem.Kind is a free string field. KindMilestone is the
canonical token; other kinds ("task", "epic", "bug", …) flow
through unchanged from the adaptor’s native issue_type.
bd adaptor read
internal/adapter/bd/types.go
const milestoneLabel = "type:milestone"
// inside bead → WorkItem projection:if hasLabel(b.Labels, milestoneLabel) { kind = core.KindMilestone}The label wins over b.IssueType for Kind selection — a bd issue whose
native type is "epic" becomes Kind=KindMilestone on projection iff
it carries type:milestone. The native type "epic" is preserved on
the read side only as the underlying bd storage detail; the core
surface doesn’t see it.
bd adaptor write
When CreateWorkItem receives wi.Kind == core.KindMilestone, the
adaptor rewrites the bd command:
bd create -t epic -l type:milestone[,…] <title> …i.e. native type is forced to "epic" and type:milestone is appended
to the label set (idempotently — never duplicated).
UpdateWorkItem does not currently translate Kind because
core.WorkItemPatch has no Kind field. Transitioning a bead in or
out of milestone status is done by patching Labels directly; the
read-side projection will pick up the change on the next GET.
Filter semantics
WorkItemFilter.Kinds = [core.KindMilestone] selects only beads that
project to milestone kind. The bd adaptor pushes this down as a native
--label type:milestone filter when it’s the only kind in the set; for
multi-kind requests it falls back to the in-process matchesFilter
predicate so cross-kind selections still work.
Hierarchy
Milestones can appear anywhere in the parent/child graph:
- a milestone can parent epics (the common case: “MVP ships” owns the epics that make MVP happen);
- a milestone can be a leaf of an epic (e.g. a deliverable named inside a broader roadmap epic);
- a milestone can stand alone with no parent.
The convention does not impose a shape. Any UI feature that cares about phase-gating is free to require a specific hierarchy, but the core contract does not.
Non-goals
- No milestone-specific UI yet. This document locks the data-model contract. Badges, roll-ups, phase-banner rendering are later work tracked under gm-root.3’s children past .3.4.
- No cross-adaptor federation. The label convention is specific to
bd. Jira / GitHub / Linear adaptors, when they land, can use their
native milestone primitive and project onto
KindMilestonewithout needing a label. - No progress roll-ups. “N of M child beads complete” logic is out of scope; milestones are an identity + filter primitive here, not a progress primitive.
Migration note
Existing epics that used to carry a MILESTONE: title prefix were
relabeled with type:milestone ahead of this convention landing (see
gm-root.1). New milestones MUST go through the label path; future tools
should not resurrect the title-prefix convention.
References
- Parent epic:
gm-root.3— Milestones as first-class stage-gate construct - Core constant:
internal/core/types.go(KindMilestone) - Adaptor read:
internal/adapter/bd/types.go(milestoneLabel, projection) - Adaptor write:
internal/adapter/bd/workplane.go(CreateWorkItem) - Tests:
internal/adapter/bd/workplane_test.go(TestBeadsMilestoneLabelProjectsToKindMilestone,TestListWorkItems_MilestoneKindFilter)