StageRegistry
Map your own stage names and gates onto the fixed Change state machine, with human approval, separation of duties and timeouts.
The kind, schema and loader checks are public. Running the state machine is a proprietary engine feature.
Purpose and scopes
Every Change moves through a fixed state machine (opened, understood, classified, planned, plan gate, implementing, integrated, verifying, repairing, rehearsing, release gate, merging, releasing, observing, recovering, escalated, paused, closed, abandoned). A StageRegistry lets your organization call those states by its own names, add human gates where it wants them, and say which stage must complete before which.
A registry can be written at any scope, but only the org scope selects one: SDLCProfile.spec.stages is FORBID, so a narrower scope cannot pick a registry without the org's gates. A repo-scoped registry, like the one numi init writes, is defined but not selected until an org profile names it. See Change lifecycle.
Field reference
Prop
Type
Example: trunk with a change advisory board
apiVersion: numinance.dev/v1
kind: StageRegistry
metadata: {name: trunk-with-cab, scope: SCOPE_ORG}
spec:
stages:
- {name: triage, maps_to: CHANGE_STATE_CLASSIFIED}
- {name: design-review, maps_to: CHANGE_STATE_PLAN_GATE, gate: {kind: GATE_KIND_PLAN}, requires: [triage]}
- {name: build, maps_to: CHANGE_STATE_IMPLEMENTING, requires: [design-review]}
- name: cab
maps_to: CHANGE_STATE_RELEASE_GATE
gate:
kind: GATE_KIND_HUMAN
approver_role: change-advisory-board
sod: {distinct_from: [author-agent, reviewer-agent]}
timeout: 86400s
requires: [build]cab is your name for the release gate. A human from change-advisory-board, who is neither the author-agent nor the reviewer-agent, decides. After 86400 seconds an undecided gate escalates; it does not approve.
Example: two-person release
apiVersion: numinance.dev/v1
kind: StageRegistry
metadata: {name: two-person-release, scope: SCOPE_ORG}
# required_approvals is only preserved today; nothing enforces the count yet.
spec:
stages:
- {name: triage, maps_to: CHANGE_STATE_CLASSIFIED}
- {name: build, maps_to: CHANGE_STATE_IMPLEMENTING, requires: [triage]}
- name: release-approval
maps_to: CHANGE_STATE_RELEASE_GATE
gate:
kind: GATE_KIND_HUMAN
approver_role: release-managers
required_approvals: 2
requires: [build]required_approvals is not enforced yet
The proto and the example both say so: the loader and the profile context only preserve the count. The count a gate needs is designed to be the largest of this value, the policy approvers.count and any control pack approval floor, so the field can only raise a requirement. Do not rely on it to enforce two-person release today.
Example: PR-only registry from numi init
The generated registry names no stage for the Merging state, so no stage of it can merge. The last stage is a human gate.
apiVersion: numinance.dev/v1
kind: StageRegistry
metadata: {name: pr-only, scope: SCOPE_REPO}
spec:
stages:
- {name: triage, maps_to: CHANGE_STATE_CLASSIFIED}
- {name: plan-review, maps_to: CHANGE_STATE_PLAN_GATE, gate: {kind: GATE_KIND_PLAN}, requires: [triage]}
- {name: implement, maps_to: CHANGE_STATE_IMPLEMENTING, requires: [plan-review]}
- {name: verify-static, maps_to: CHANGE_STATE_VERIFYING, requires: [implement]}
- {name: ci-github-actions, maps_to: CHANGE_STATE_VERIFYING, requires: [verify-static]}
- name: pr-gate
maps_to: CHANGE_STATE_RELEASE_GATE
gate:
kind: GATE_KIND_HUMAN
approver_role: maintainer
sod: {distinct_from: [author-agent, reviewer-agent]}
required_approvals: 1
requires: [ci-github-actions]$ numi validate --files schemas/examples/StageRegistry
revision: "files"
ok file "trunk-with-cab.yaml" StageRegistry "trunk-with-cab"
ok file "two-person-release.yaml" StageRegistry "two-person-release"Fixed states cannot be removed
Your stages map onto states; they do not define them. The registry cannot delete a state, skip a gate state, or make a weaker gate. A stage mapped to CHANGE_STATE_PLAN_GATE or CHANGE_STATE_RELEASE_GATE still gets the policy decision for that state even if you declare no gate or a weaker one. CHANGE_STATE_PAUSED is an overlay that the circuit breaker sets and a registry cannot use as a stage (the profile context refuses it). Several stages may map to the same state, as the two verifying stages above do.
Security implications
- Only an org-scoped profile selects the registry, so a repository cannot swap in one with fewer gates.
sod.distinct_fromonly adds exclusions. The approver is never the author and the approver's run is never the author's run.- A timeout escalates; no gate approves on its own.
- GATE_KIND_HUMAN approval is a human decision, and agents cannot satisfy it.
Common misconfigurations
$ numi validate --files bad-approvals.yaml
revision: "files"
invalid file "bad-approvals.yaml": kind: invalid: required_approvals is set on a gate that is not GATE_KIND_HUMANUsing a short enum (HUMAN instead of GATE_KIND_HUMAN) fails with "/spec/stages/3/gate/kind": enum, exit 3.
What validation checks, and what it does not
Checked by the open build: keys, enum names, durations, non-empty stages, required_approvals only on human gates and never 0. Not checked: a copy where requires names a stage that does not exist, and another that maps a stage to CHANGE_STATE_PAUSED; both passed numi validate --files with exit 0. The proto says the profile context checks the requires DAG and refuses PAUSED, which is the engine build. Do not treat a green open validation as proof the registry is sound.
Next steps
Playbooks
Define the procedure for one change type as steps with capability sets, risk, evidence and verifiers, a bounded repair loop, and the qualification record that gates autonomy promotion.
Toolchains, architecture packs and indexers
Declare the language version and deterministic verifiers, bind architecture rules to checks, and choose the indexers that build the code graph.