Skip to content

ADO Work-Item Conventions Standard

This is the enforceable specification for how Azure DevOps work items are typed, titled, stated, linked, tagged, and pathed in pitlab. It consolidates conventions that were scattered across the session-tracking directive, the idea-capture and wrap-up skills, and the kanban reference into one checkable place, and fills the one previously ungoverned gap — Area and Iteration paths. Most of it ratifies existing practice; the work-intake half is a decision — who may create an Epic, and where a mid-delivery finding goes — governed by ADR-0372 and its predecessor ADR-0274. All work lives in org https://dev.azure.com/pitbun, project pitlab, and every operation is performed with the az boards CLI.

All ADO operations use the az boards CLI — the MCP server is broken

Section titled “All ADO operations use the az boards CLI — the MCP server is broken”

Every create, update, link, and query against pitlab work items goes through the az boards / az devops CLI. The azure-devops MCP server is configured but must not be used — its OAuth flow requires a proxy that is not in place, so calls fail. Do not reach for the MCP as a fallback; the CLI is the only sanctioned path. Pass --org https://dev.azure.com/pitbun (or rely on the configured default) and --project pitlab on commands that need them.

Three work-item types: Epic, Issue, Task — and nothing else

Section titled “Three work-item types: Epic, Issue, Task — and nothing else”

pitlab uses exactly three work-item types, in a fixed hierarchy. An Epic is one piece of work or one goal. Issues are the units of work under an Epic. Tasks are the optional sub-steps under an Issue. No other type (Bug, Feature, User Story, Product Backlog Item) is used. A defect in the thing currently being built is an Issue under its Epic; a defect in a capability already accepted into PROD is not an ADO item at all but a FreeScout incident (ADR-0274), and anything else noticed mid-delivery is a snag — see the intake sections below.

Epic one goalbrief, punchy titleIssue unit of workIssue unit of workTask sub-stepTask sub-step
Epic one goalbrief, punchy titleIssue unit of workIssue unit of workTask sub-stepTask sub-step

One Epic per goal; resume it across sessions, never create a new one per session

Section titled “One Epic per goal; resume it across sessions, never create a new one per session”

There is one Epic per piece of work or goal (RULE 2). At the start of a session, locate the existing Epic for the current work and resume under it — do not open a fresh Epic each session. As tasks are identified during a session, create Issues and link them as children of that one Epic; create Tasks under an Issue only when a unit of work has meaningful sub-steps. This keeps a goal’s entire history under a single, traceable parent rather than fragmenting it across per-session Epics.

The agent never creates an Epic — except on an approved, grilled design

Section titled “The agent never creates an Epic — except on an approved, grilled design”

An Epic is a commitment: a goal carrying a lifecycle, a delivery report and a close-out review. It is not a note. The agent therefore never creates one on its own initiative — an Epic exists only because Arron created it, via /idea or by directing the work in conversation (ADR-0372, extending ADR-0274’s approval rule from wrap-up-time to standing). Backlog growth is a conscious act at all times, never a reflex.

The prior wording — create a new Epic when no existing one fits — made minting an Epic the default response to any observation that didn’t fit the work in hand. Measured on 2026-08-09, that produced 119 open Epics, 50 of them with no open children at all and 45 untouched for 30+ days: intent and commitment had become indistinguishable on the board. A finding that does not belong to the Epic in hand goes to a snag list or an incident (next section), never to a new Epic.

The one carve-out: an approved /grillme design mints its Epic directly (ADR-0372 decision point 5, amended 2026-08-12). When Arron explicitly approves a design at the grill’s close-out gate, that approval is the commitment and the agent creates the Epic in the same turn — approval given against a technical-executive brief, a resolved decision tree and a named delivery plan is stronger evidence of intent than the /idea path, so asking a second time is ceremony rather than control. The carve-out authorises exactly one Epic, for that design, in that session, and every Epic minted this way carries the grilled tag. An unapproved design, an abandoned grill, an “approved with changes” awaiting re-brief, or any finding surfaced outside the design still mints nothing.

A mid-delivery finding is a snag, an incident, or a child — never a new Epic

Section titled “A mid-delivery finding is a snag, an incident, or a child — never a new Epic”

Everything noticed during a delivery run is routed by one test applied at the moment of discovery, not deferred to wrap-up (ADR-0372):

yesnoyesno / not a defectFinding discoveredmid-deliveryPart of the Epicbeing delivered?Already acceptedinto PROD, and broken?Child Issue of the currentEpicFreeScout incident(ADR-0274 lifecycle)Snag appended to theEpic's[SNAG LIST] Issue
yesnoyesno / not a defectFinding discoveredmid-deliveryPart of the Epicbeing delivered?Already acceptedinto PROD, and broken?Child Issue of the currentEpicFreeScout incident(ADR-0274 lifecycle)Snag appended to theEpic's[SNAG LIST] Issue

The incident branch applies whenever the defect is found, not only in a post-close session — discovering it while delivering something unrelated does not turn an operational defect into project work. Findings surfaced with no active Epic (a /health or /investigate sweep) go to the standing register, Epic #2259.

One [SNAG LIST] Issue per Epic holds every snag; it is excluded from the delivery burn-down

Section titled “One [SNAG LIST] Issue per Epic holds every snag; it is excluded from the delivery burn-down”

Snags accumulate as a checklist inside the description of a single Issue per Epic, titled [SNAG LIST] <topic> — findings for close-out review, tagged snag; close-out-review, parented to the Epic. One Issue, not one per finding: the board grows by exactly one row per Epic however much fallout a delivery run throws off. Per-finding state and ownership are deliberately deferred — a snag has no owner until it is adjudicated, at which point it becomes an incident, an Epic, or nothing, each carrying its own tracking.

The snag list is not deliverable work. It is excluded when /wrapup batches open children into the next-session resume prompt, and never counted as outstanding delivery scope. A snag that leaks into a delivery batch turns the detritus back into a to-do list and defeats the mechanism.

Capture and adjudication use the snag helper on control01 (ansible/scripts/snag.py/usr/local/bin/snag) — never a hand-rolled ADO edit, because a discipline that costs a multi-step dance mid-delivery silently stops happening:

Terminal window
snag add 2294 "Alloy tail on docker01 replays on restart — unrelated, pre-existing"
snag list 2294 # or bare `snag list` for every open snag list
snag route 2294 1 incident "#1402" # fixed · incident · epic · folded · wontfix
snag close 2294 # refuses (exit 2) while any finding is unadjudicated

Every snag is adjudicated at Epic close-out; an unadjudicated list blocks the Epic

Section titled “Every snag is adjudicated at Epic close-out; an unadjudicated list blocks the Epic”

The close-out review is the drain — a register without one is a backlog under a new name. At Epic close, /wrapup Check Q presents the whole list and each finding exits to exactly one named disposition:

DispositionMeaning
fixedremediated during the close-out
incidentraised in the FreeScout Incidents mailbox (the capability is now accepted/live)
epicpromoted to a new Epic — Arron’s explicit approval, as always
foldedabsorbed into an existing open Epic as a child
wontfixconsciously closed with no action

snag close refuses while any finding is unrouted, and the snag list is an ordinary open child — so the existing “a Done Epic must have no open children” invariant makes adjudication a hard precondition of Epic closure, enforced by machinery that already exists. A pile of un-routed snags holding an otherwise-finished Epic open is the intended behaviour, not a defect: it surfaces the fallout at exactly the moment there is appetite to deal with it.

Work-item type → title format → state workflow

Section titled “Work-item type → title format → state workflow”

Each type has a fixed title format and a fixed state workflow. Every type — Epic, Issue and Task — carries all three states, including the middle Doing (verified against the live process 2026-08-14; see Every type has exactly three states below). Move a child to Doing when work on it starts. A delivered Epic passes through the Hypercare board column (which maps to Doing) before it may reach Done — see Hypercare is an enforced delivery stage below.

TypeTitle formatStates (in order)
Epic (working)brief, punchy noun phrase — no agent prefix, no date stamp ([N] prefix while sequenced; + (HC DD-MM-YY) while in hypercare)To DoDoingHypercareDone
Epic (idea capture)[Idea] <clean title>To DoDoingHypercareDone
IssuePlain imperative descriptive title (e.g. Add blackbox probe for paperless)To DoDoingDone
TaskPlain imperative descriptive titleTo DoDoingDone

Set Epic state to Doing when work on it is underway. Done is not reached by delivering the goal — a delivered Epic is parked in the Hypercare column and reaches Done only after its close-out review (see below). At session end, mark completed Issues/Tasks Done and ensure the Epic title still reflects the final outcome.

An Epic may be set Done only when every descendant Issue/Task is Done. A Done Epic that still has an open (To Do/Doing) child is invalid — the epic either closed before its work finished or a child was filed against it afterwards. If any child must stay open, the Epic stays open too: move it to To Do / Up Next (state To Do, board column Up Next). This invariant is enforced at close time by the wrap-up Epic-completeness audit (Check D); a Done Epic later found carrying open work is reopened to Up Next until the children are closed or reparented.

Every type has exactly three states and there is NO Removed state

Section titled “Every type has exactly three states and there is NO Removed state”

Verified against the live process on 2026-08-14: Epic, Issue and Task each expose exactly To Do (Proposed) → Doing (InProgress) → Done (Completed), and no Removed state exists on any of them.

Two long-standing claims in this standard were wrong and are corrected here:

  • “Issues and Tasks have no Doing state.” They do. az boards work-item update --id <n> --state Doing on an Issue succeeds and moves the board column to Doing — confirmed live on Issue #2421. The earlier claim that ADO “fails or silently no-ops” on that transition was untrue, and it mattered: it discouraged the very board-accuracy discipline the estate now requires.
  • “A child may be closed Done or Removed with a documented reason.” There is no Removed state to move to, so that option was unreachable — the guidance named a procedure nobody could follow (FC-17, unreachable-control).

A child that will not be delivered therefore has exactly two honest dispositions, and neither is a state change to Removed:

  1. Close it Done with a comment recording that it was dropped and why. The comment carries the truth the state cannot; this is the default.
  2. Delete it (az boards work-item delete) if it was filed in error and holds no history worth keeping. This removes it from the board entirely rather than parking it in a state.

Either way the “Done Epic has no open children” invariant above is satisfied by a deliberate act with a written reason, not by a state that does not exist.

Terminal window
# Create an Issue (child wiring is a separate step — see the linking section)
az boards work-item create --type Issue --project pitlab \
--title "Add blackbox probe for paperless"
# Drive an Issue through its middle state, then close it
az boards work-item update --id 1412 --state "Doing"
az boards work-item update --id 1412 --state "Done"
# Drive an Epic through its middle state
az boards work-item update --id 376 --state "Doing"

Linking a child to its Epic requires relation addSystem.Parent does NOT work

Section titled “Linking a child to its Epic requires relation add — System.Parent does NOT work”

This is the highest-frequency trap. Setting --fields System.Parent=<id> on a work item does not create the parent link — the field write appears to succeed but no hierarchy relation is established, so the child never shows under the Epic on the board or in queries. The parent link is a relation, added explicitly:

Terminal window
# CORRECT — establishes the Epic→Issue (or Issue→Task) hierarchy link
az boards work-item relation add \
--id <child-id> --relation-type parent --target-id <epic-or-issue-id>
# WRONG — does NOT link; the card stays orphaned despite the field write
az boards work-item update --id <child-id> --fields System.Parent=<epic-id>

Always link with relation add --relation-type parent immediately after creating a child, and verify the relation appears before moving on.

Epic tags are four flat lifecycle labels, colour-coded on the board

Section titled “Epic tags are four flat lifecycle labels, colour-coded on the board”

An Epic may carry only groomed, grilled, hypercare and done, and nothing else (ADR-0399, Pit, 2026-08-13; amended 2026-08-17 to admit groomed). ADO tags are semicolon-separated. Each tag is a flat label — it carries no value — and each has a card colour on the Epics board, so blue → red → amber → green reads as a progression without anyone reading the words.

TagColourMeaningApplied by
groomedblue #0078D4Reviewed in a groom pass — validity ruled and a grill-ready brief written/groom
grilledred #B20B1EThe design survived a full /grillme and Pit approved it at the close-out gate/grillme close-out gate
hypercareamber #FF9D00Delivered and parked in the hypercare window, not yet handed over/wrapup Check S1
donegreen #339947Closed out and finished/wrapup Check S2

Queue position is NOT a tag — it is a Sequenced: YYYY-MM-DD description stamp plus the [N] title prefix (Epic #2503, grilled 2026-08-17). A dated stamp is self-evidently stale where a bare tag lies silently, and the vocabulary stays closed at four with nothing to reap. The stamp joins Groomed: and DepMapped: in the description.

A tag is provenance and sticks for life; a board column is a queue position and does not. An Epic keeps groomed and grilled through Doing, Hypercare and Done. This is why groomed cannot be replaced by the Up Next column: a card that is reviewed, still valid, and deliberately not next up stays in Backlog, where without the tag it is indistinguishable from one never examined.

A dropped card is Done but never carries done. When /groom kills a card it closes it as Done, prefixes the title [DROPPED] , adds a reason comment, and applies groomed while withholding the green done tag. Delivered work is therefore queryable as Done + done; dropped work as Done + [DROPPED], no done. Without that distinction, dropped cards would be indistinguishable from delivered ones in a Done column holding 272 Epics as at 2026-08-17.

Topic and domain are carried by the Epic title, the Area Path and the board column — never by tags. The vocabulary was closed because it had stopped discriminating: a 2026-08-13 census found 126 of 395 Epics tagged with 88 distinct strings, 78 of them on exactly one Epic, one (claude) on 100 Epics, and none consumed by anything machine-readable. The superseded hypercare-until-<date> tag was the same failure generating itself — one brand-new single-use tag string per delivery, permanently.

Issues and Tasks are unaffected — this clause governs Epics. The [SNAG LIST] Issue still carries snag; close-out-review.

/idea does not tag its captures. Un-triaged ideas are found by board column (Backlog) and the [Idea] title prefix, not by a tag query.

Colours are board configuration, not per-card formatting — tagStyle rules on the Epics board’s cardrulesettings:

Terminal window
TOKEN=$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)
curl -sX PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"https://dev.azure.com/pitbun/pitlab/pitlab%20Team/_apis/work/boards/Epics/cardrulesettings?api-version=7.0" \
-d '{"rules":{"tagStyle":[{"name":"done","isEnabled":"True","settings":{"background-color":"#339947"}}]}}'

⚠️ Three traps in one field, and the fix is a SINGLE replace op. az boards work-item update --fields "System.Tags=" cannot clear tags — the CLI silently drops an empty value, the item keeps every tag it had, and the call still exits 0. Worse, az boards work-item update --fields "System.Tags=<reduced set>" cannot reduce one either: the CLI translates --fields into a JSON-patch add, and add on System.Tags MERGES. So the CLI path can only ever grow an item’s tags, and a write that passes a smaller set is a silent no-op at exit 0 — the command echoes the value you intended, which reads exactly like success.

To reduce a tag set, send one replace op carrying the complete string you want to end up with:

Terminal window
# ONE op. Verified on Epic #2584, 2026-08-21: "grilled; groomed" -> "grilled".
az rest --method patch --resource 499b84ac-1321-427f-aa17-267ca6975798 \
--uri "https://dev.azure.com/pitbun/pitlab/_apis/wit/workitems/<id>?api-version=7.0" \
--headers "Content-Type=application/json-patch+json" \
--body '[{"op":"replace","path":"/fields/System.Tags","value":"grilled"}]'

--resource is not optional. Without it az rest authenticates against the ARM credential chain rather than the azure-devops extension’s, and returns az logout / az login even while az boards works normally in the same shell. 499b84ac-1321-427f-aa17-267ca6975798 is Azure DevOps’ resource GUID. A session that omits it gets an auth error and can easily conclude the capability is missing — it is not.

Do NOT send remove and add in the same document. It is rejected outright, verified on Epic #2584 on 2026-08-21:

VS403691: Update to work item <id> had two or more updates for field with reference
name 'System.Tags'. A field cannot be updated more than once in the same update.

Adding a tag while keeping the rest is a plain add — or just az boards work-item update --fields "System.Tags=<new tag>", since merge is exactly what you want in that direction. Always read the tag set back off the board afterwards. A merge-on-write field is invisible in every signal except the read-back: exit code 0, no stderr, and the echoed value is the one you asked for rather than the one stored.

Epic titles are brief and punchy — no agent prefix, no date stamp, no status commentary

Section titled “Epic titles are brief and punchy — no agent prefix, no date stamp, no status commentary”

An Epic title names the work in as few words as carry it. No [Claude] prefix — it said only “an agent typed this”, which was true of 338 of 395 Epics and therefore said nothing. No trailing — YYYY-MM-DD capture stamp — that duplicates System.CreatedDate. Both consumed the width the subject needed.

Status does not belong in the title. servicemap — tranche 1 LIVE, tranche 2 (depth + breadth) parked is a description sentence wearing a title’s clothes; the board column already says where the work is, and the description says the rest. Title it servicemap — depth and breadth (tranche 2).

A title carries two values, both written by a skill and both visible on the card. The queue position leads, as [N] [3] pve01 backup I/O stall + backup-failure visibility — written by /getready alongside a spaced Microsoft.VSTS.Common.StackRank (base 1000000, +1000 per position) so the number on the card and the physical column order cannot disagree. It is stripped with an anchored ^\[\d+\]\s+ before each rewrite, so re-runs never stack [3] [1] Title, and is paired with a Sequenced: YYYY-MM-DD description stamp — a dated record of the position, where a bare tag would go stale silently (Epic #2503 rejected a ready tag for exactly that reason). This is not a reversal of the [Claude] prohibition above: that prefix was struck for carrying no information, where [N] is unique per card and is the fact a reader of a queue most needs.

The second value is the hypercare review date, as a trailing (HC DD-MM-YY)Domestic hot water monitoring and SLO (HC 17-08-26). It is read by hypercare_countdown.py and, unlike a tag chip, it is visible on the card. Strip it when the window closes.

Kanban columns map to states per the kanban reference, set via the WEF field

Section titled “Kanban columns map to states per the kanban reference, set via the WEF field”

A board card’s Kanban column is not the same as its work-item state, and System.BoardColumn is read-only — the column is patched through a per-team WEF custom field. On the pitlab Epics board, Backlog, Up Next, Grilled and Paused all map to Epic state To Do; Doing, Blocked and Hypercare all map to Doing; DoneDone. Two of the eight columns are easy to forget precisely because they are sidings rather than stages — Paused maps to To Do deliberately, so parked work consumes no WIP, and Blocked maps to Doing because the work is still live and merely obstructed. The exact WEF field GUID, the split-column done flag, and the command to move a card live in ado-kanban-column — consult it before moving cards, and do not hand-edit a column expecting the state to follow (or vice versa).

Every Epic column transition has exactly one owning skill and one trigger moment

Section titled “Every Epic column transition has exactly one owning skill and one trigger moment”

The column is the status Arron reads, so a card whose column disagrees with reality tells him a lie he has no way to detect. Documenting the mechanism of a column move (above) is not enough: until 2026-08-19 no document said who moves a card when, so each skill implemented the slice it happened to need and three transitions were owned by nobody at all — /go delivered whole Epics without ever leaving Grilled, Blocked had a drain (/unblock) and no source, and a hypercare Epic needing remedial work had no way back to Doing. This table is the ownership register; a transition missing from it is a defect, not a discretion.

From → ToOwnerTrigger moment
(new)Backlog/ideaCapture. The default column for state To Do; no explicit write needed
BacklogUp Next/groomArron rules the card grill-ready
Up NextGrilled/grillmeClose-out gate approval, in the same turn as minting or tagging the Epic
Grilled/Up NextDoing/go, else the session-start checkThe first action before the first edit, not at wrap-up
DoingBlocked/go on haltA hard stop, a BLOCKED child, or the circuit breaker tripping — anything awaiting a decision that is not yours. Writes the Blocked on:/Cleared by:/Review: stamp /unblock reads
BlockedGrilled/Up Next/Doing/Paused/unblockThe blocker verified cleared, or the card verified misfiled
DoingPaused/pauseWork deliberately stopped, not obstructed
PausedDoing/resumeWork actually restarting this session
DoingHypercare/wrapup Check S1Every delivery child Done and every snag adjudicated. Automatic — never a question
HypercareDoing/wrapup Check S2A close-out criterion fails and clearing it needs new child Issues
HypercareDone/wrapup Check S2Every criterion re-run against the live service and passed
(any)Done + [DROPPED]/groomArron rules the work should not exist. Withholds the done tag

Five invariants govern the table:

  • Write the column, never infer it from the state. Five columns map to To Do and three to Doing, so a state-only write lands the card in whichever column ADO picks first — and on an Epic already in Blocked or Hypercare, a state write of Doing is a silent no-op that leaves the card exactly where it was. Patch the WEF_…_Kanban.Column field explicitly and read it back.
  • Blocked means obstructed, Paused means stopped. Blocked is external obstruction with the work still live; Paused is a deliberate stop. Routing an obstructed Epic to Paused because Blocked had no writer is how /unblock loses the cards it exists to drain.
  • A regression is a transition, not a failure. HypercareDoing and Blocked → anywhere are ordinary moves; a board that only ever moves rightward is a board that is lying about the leftward cases.
  • The move happens at the moment the work moves — the same discipline as updating a doc in the commit that changes the code, never deferred to wrap-up.
  • The END STATE is audited, not just the transitions — /wegood Check 3a, before every context clear. Every owner above fires when work moves, so a card whose correct move never happened is owned by nobody and checked by nothing: the column that survives a session is the one no skill looked at. Doing is a claim about now, and a session ending is the moment now stops being true. Check 3a enumerates every Epic in state Doing, attributes each against the live cc-pool peer sessions (ListAgents) so a peer’s genuinely-live card is left alone, rules the remainder against what happens to the work in the next hour, and routes each correction through the owning skill in the table rather than writing the column itself. It sweeps child Issues too, which have no paused state: one started and abandoned returns to To Do with a comment recording how far it got. This closes the residual ADR-0448 accepted when it rejected a machine reconciler — the observer is the session itself, which can see what it was doing, and only in the last moment it still can.

Reference a work item by name, not a bare number

Section titled “Reference a work item by name, not a bare number”

Always refer to a work item as “Epic #376 (Infisical Secrets Management)” — the name leads, the number trails for traceability. Never cite a bare number like “#376” alone (soul.md). This applies everywhere the item is named: in-session prose, commit messages (cicd-pipelines-standard), summaries, wrap-up reports, and notifications. Arron tracks names, not IDs; a bare number forces a lookup and breaks the thread of a report.

Area Path defaults to pitlab and Iteration to the root backlog — both MUST be set, never blank

Section titled “Area Path defaults to pitlab and Iteration to the root backlog — both MUST be set, never blank”

Area and Iteration paths were previously ungoverned; this standard sets the policy. Every work item must carry an explicit Area Path and Iteration Path — even when that value is the default — so the board stays filterable and nothing falls outside team/iteration queries. The defaults are:

  • Area Path = pitlab (the project root area).
  • Iteration Path = the project backlog / root iteration (pitlab), unless a named sprint is actively in use, in which case set the current sprint.

A blank Area or Iteration is non-conforming: such items silently drop out of scoped board and query views. Set them on create rather than relying on inheritance.

Terminal window
az boards work-item create --type Issue --project pitlab \
--title "Wire paperless logs into Loki" \
--area "pitlab" --iteration "pitlab"

Hypercare is an enforced delivery stage between delivered and Done

Section titled “Hypercare is an enforced delivery stage between delivered and Done”

Delivering an Epic’s goal does not make it Done. A delivered Epic is parked in the Hypercare board column — which maps to state Doing, so it stays live on the board — tagged hypercare with its review date in the title as (HC DD-MM-YY), and reaches Done only after a close-out review re-runs its acceptance criteria against the real service and clears the residuals. The (HC DD-MM-YY) date is a review-DUE date, not an expiry: no automation may close a window, because a timer-based exit would let a service that failed its window graduate by the passage of time.

Doing rather than Done is deliberate. A Done Epic may have no open children, so marking a delivered-but-unreviewed Epic Done would force its residual work off the board entirely — the exact disappearance the stage exists to prevent.

A window that lingers past its review date pages, and an Epic parked in the column with no review date raises its own alert (it can never become overdue, so it would sit invisible forever). The full rules are in the CI/CD & Pipelines Standard, the operational runbook is Hypercare, and the decision is ADR-0389.

A child Issue delivered by fan-out MUST carry the five-field work-order contract

Section titled “A child Issue delivered by fan-out MUST carry the five-field work-order contract”

An Issue delivered by an autonomous actuator that fans out to disposable workers — today /go (ADR-0403) — is not read by a human before it is built. The worker gets the Issue and nothing else: it cannot ask a question, and its context is discarded when it returns. So whatever the Issue fails to say is a gap the worker would have to invent, and an actuator that invents the spec it then delivers produces a fluent, self-consistent, unfalsifiable result with no seam between inventing and building (FC-19, authored-not-observed).

A child Issue is therefore fan-out-ready only when its description carries all five fields, each with substance:

FieldWhat it MUST carry
GoalThe outcome in one line — what is true afterwards that is not true now.
ChangeThe concrete change to make, specifically enough to start without interpretation.
AcceptanceOne falsifiable assertion: what to run and what must be observed. It MUST name a command, artifact or observable value, because it is what the orchestrator independently re-runs before closing the Issue.
ScopeThe repos and paths the worker may touch — its boundary. MUST name a known repo.
DependsBlocking work-item ids, or none.

Presence is not conformance. A field reading TBD, n/a or verify it works hands the worker the same blank page as an absent field, and an Acceptance that would be true whatever happened is an assertion that cannot go red (FC-02, assertion-cannot-fail). Both are refused.

The gate is go_workorder_check.py --epic <id>, run before any fan-out. It refuses non-conforming children by name, listing the gap, and the actuator does not fan out — the fix is for a human to write the missing half onto the Issue, never for the actuator to fill it in. Being refused is the gate working.

This clause binds only Issues intended for fan-out delivery. An Issue a human will deliver interactively is governed by the rest of this standard and needs no work-order block; the contract is about what a disposable worker can be trusted with, not about paperwork for its own sake.

One child is exempt by construction: the Epic’s [SNAG LIST] register (ADR-0372) is not deliverable work, so the gate skips it when expanding an Epic’s children — demanding delivery fields from it would make the gate refuse fan-out on every Epic that has ever captured a snag, a control the sanctioned procedure can never satisfy (FC-17, found and fixed 2026-08-16, FreeScout incident #2403). An explicit --issue check still evaluates a register, so the exemption cannot mask a mistyped id, and an Epic whose only open children are registers is reported as having nothing to fan out rather than as fan-out-ready.

Conformance checklist for an ADO work item

Section titled “Conformance checklist for an ADO work item”

A work item conforms when all of these hold — this is the checklist /wrapup and reviewers apply:

  • Type is one of Epic / Issue / Task — no Bug/Feature/Story/PBI.
  • It belongs to the single Epic for the goal (RULE 2) — the existing Epic was resumed, not duplicated per session.
  • The Epic was created by Arron, not by the agent — no Epic was minted for a finding discovered mid-delivery.
  • A finding outside the current Epic’s scope went to a snag (snag add) or an incident, never to a new Epic or a stray Issue.
  • The Epic’s snags are held on exactly one [SNAG LIST] Issue, tagged snag; close-out-review, excluded from delivery batching.
  • At Epic close, every snag was routed to a named disposition and snag close returned 0.
  • Title matches the format for its type — working Epic a brief punchy noun phrase (no [Claude] prefix, no date stamp), idea Epic [Idea] <clean title>, Issue/Task plain imperative.
  • State is valid for the type — every type carries To Do/Doing/Done; a child in flight is Doing, not left at To Do.
  • Parent link was made with relation add --relation-type parentnot --fields System.Parent — and the relation is verified.
  • The Epic carries only groomed, grilled, hypercare and/or done — no topic tags, no dated tag, and no ready tag (ADR-0399; queue position is the Sequenced: stamp).
  • A dropped card is Done with a [DROPPED] title prefix, a reason comment and groomed, but not the green done tag.
  • The Epic title is brief and punchy: no [Claude] prefix, no — YYYY-MM-DD stamp, no status commentary; a sequenced Epic carries a leading [N] and a hypercare window its review date as a trailing (HC DD-MM-YY).
  • A sequenced Epic’s [N] prefix agrees with its StackRank position in the column, and it carries a Sequenced: YYYY-MM-DD stamp.
  • Kanban column matches the intended state per ado-kanban-column (patched via the WEF field, not System.BoardColumn).
  • The Epic’s column matches where the work actually is, and the move was made by the owning skill at the trigger moment in Every Epic column transition has exactly one owning skill — no Epic was delivered out of Grilled, left in Doing after work stopped, or parked in Paused when it was obstructed rather than stopped.
  • No Epic or child Issue is left in a column claiming to be worked once the session working it has ended — audited by /wegood Check 3a before every context clear, with a peer-session attribution pass so a live card is not moved and an unattributable one is reported rather than cleaned.
  • Area Path and Iteration Path are explicitly set (default pitlab / root backlog) — neither is blank.
  • Every reference to the item names it — “Epic #376 (Infisical Secrets Management)”, never a bare number.
  • All operations were done via the az boards CLI — the broken azure-devops MCP was not used.
  • If the Issue is intended for fan-out delivery, it carries the five-field work-order contract (Goal / Change / Acceptance / Scope / Depends), each with substance, and go_workorder_check.py returns 0.

This standard is review-weighted: work-item field discipline lives in Azure DevOps, which has no pitlab-side config-as-code artifact a CI gate can lint, so there is deliberately no machine mechanism for any obligation here. The only ADO automation in the repos — zabbix_ado_monitoring.py — watches pipeline runs, approvals, and stuck builds, not work-item typing, titling, state, or linkage, so it enforces nothing in this standard. Conformance is instead carried by two concrete human/agent checkpoints: the /ado-session skill (which creates, titles, and parent-links Epics/Issues and resumes the single Epic per goal) at session start, and the /wrapup checklist — chiefly Check D (Epic-completeness audit: every child Done, no open children under a Done Epic) and Check I (outstanding work captured as open Epic children) — plus this section’s own conformance checklist applied at review. A few clauses are authoring conventions over prose or manual board mechanics with no inspectable board artifact and are marked advisory.

ObligationClassLayerMechanismDead-man
Exactly three work-item types — Epic / Issue / Task; no Bug / Feature / Story / PBIreviewADO work-item conformance checklist applied at /ado-session review and /wrapup; reviewer bounces any non-conforming type on the boardn/a
A child Issue intended for fan-out delivery carries the five-field work-order contract — Goal / Change / Acceptance / Scope / Depends, each with substancemachinepre-fan-outgo_workorder_check.py --epic <id> refuses any child missing a field or carrying a vacuous one, and /go does not fan out; the actuator never authors the missing half (FC-19, ADR-0403)n/a
One Epic per goal — resume the existing Epic across sessions, never open a fresh one per sessionreview/ado-session (locate + resume the existing Epic, link Issues as children) at session start; /wrapup Check In/a
The agent never creates an Epic — creation is Arron’s act, via /idea or an explicit directionreview/ado-session resumes or asks, never mints; /wrapup Check I routes findings to snag/incident and Check Q requires Arron’s approval for an epic dispositionn/a
A mid-delivery finding outside the Epic’s scope becomes a snag or an incident, never a new Epic or a stray Issuereview/wrapup Check Q (close-out adjudication) and Check I’s acceptance-boundary routing; snag add is the sanctioned capture pathn/a
Every snag is routed to a named disposition before the Epic closesmachinetoolingansible/scripts/snag.py (deployed as /usr/local/bin/snag): snag close <epic> exits 2 while any finding is unadjudicated, refusing to close the [SNAG LIST] Issue; the “a Done Epic has no open children” invariant then blocks Epic closure. Proven red 2026-08-09 (2 unrouted findings → exit 2)the open snag list is itself the dead-man — it cannot be closed silently, and snag list with no argument enumerates every open list estate-wide
The [SNAG LIST] Issue is excluded from delivery batching and outstanding-scope countsreview/wrapup resume-artifact generator (branch C) skips [SNAG LIST]-titled children when batching open children into the next-session promptn/a
Title matches the per-type format — working Epic a brief punchy noun phrase (no [Claude] prefix, no date stamp; ADR-0399), idea Epic [Idea] <clean title>, Issue/Task plain imperativereview/ado-session sets the title on create; conformance checklist at reviewn/a
State is valid for the type — every type has exactly To Do/Doing/Done, and no type has a Removed statereviewconformance checklist at /wrapup Check D; the live process API is authoritative (_apis/wit/workitemtypes/<type>/states), verified 2026-08-14n/a
Every Epic column transition has one owning skill and one trigger moment; the move happens when the work movesreviewthe ownership table in Every Epic column transition has exactly one owning skill: /idea, /groom, /grillme, /go, /unblock, /pause, /resume and /wrapup Checks S1/S2 each own their named rows and read the column back after writing it. scripts/zabbix_groom_monitoring.py already alerts on an open Epic with no board column at all, which catches the total-absence case but not a wrong onegroom.backlogdebt.age
No card is left in a column that lies once its session ends (the end state, not the transition)review/wegood Check 3a, run before every context clear and as /wrapup’s final gate: enumerate Epics in state Doing, attribute against ListAgents, rule on what happens to the work next, route corrections through the owning skill above, sweep child Issues back to To Do with a progress comment. A card left mis-columned drops /wegood to the 50–74% band, which blocks the wrap-up. Closes the residual ADR-0448 acceptedn/a
A Done Epic has no open (To Do/Doing) children — reopen to Up Next until every child is Done or deletedreview/wrapup Check D — the Epic-completeness audit; a Done Epic later found carrying open work is reopened to Up Nextn/a
Parent link is made with relation add --relation-type parent (never --fields System.Parent) and the relation is verifiedreview/ado-session links children with relation add --relation-type parent; reviewer confirms the child appears under the Epic on the boardn/a
An Epic carries only groomed, grilled, hypercare and/or done; no topic tagsreviewADR-0399 as amended 2026-08-17; applied by /groom, the /grillme close-out gate and /wrapup Checks S1/S2, the only writers of an Epic tagn/a
A sequenced Epic’s [N] prefix, StackRank and Sequenced: stamp agreereview/getready writes all three together and re-reads the column back in StackRank order to assert the prefix matches the position. Epic #2503 adds depmap_reconcile.py as the headless detectiven/a
A dropped card reads as dropped, never as delivered — Done + [DROPPED] prefix + reason comment + groomed, and never the done tagreview/groom step 4 applies it; the conformance checklist catches a card closed without the prefix. Queryable both ways: delivered is Done + done, dropped is Done + no donen/a
An Epic title is brief and punchy; a hypercare window carries (HC DD-MM-YY)machinescheduledscripts/hypercare_countdown.py reads the title date daily — a window whose title has no date raises hypercare.untaggedhypercare.countdown.age
Backlog cards are periodically re-validated rather than accumulating unreviewedmachinescheduledscripts/groom_backlog_debt.py counts Backlog-column Epics carrying no groomed tag and the age of the oldest, monthly; scripts/zabbix_groom_monitoring.py turns both into nudge triggers, plus a third for open Epics with no board column at all. The pass itself is /groom, which needs Pit in the loop and so cannot be headlessgroom.backlogdebt.age
Area Path and Iteration Path are explicitly set (default pitlab / root backlog), neither blankreviewconformance checklist at /ado-session review; blank Area/Iteration is queryable on the boardn/a
Epic title still reflects the actual outcome at session endreview/wrapup checklist row 14 (Epic title reflects outcome)n/a
Kanban column matches the intended state per ado-kanban-column, patched via the WEF field, not read-only System.BoardColumnadvisoryjustification: manual per-team WEF column patch with no config-as-code source; Backlog/Up Next/Grilled/Paused all map to To Do and Doing/Blocked/Hypercare all map to Doing, so a column/state mismatch is not reliably distinguishable by static or review inspection. The ownership of each transition is separately enforced by the row aboven/a
Reference a work item by name, not a bare number — “Epic #376 (Infisical Secrets Management)”advisoryjustification: a prose/report authoring convention (soul.md) over free text in session output, commits, and reports — no board artifact to inspectn/a
All operations use the az boards / az devops CLI — the azure-devops MCP is not usedadvisoryjustification: self-enforcing — the MCP’s OAuth flow lacks the required proxy so calls fail; no post-hoc artifact records which client made a given writen/a
A delivered Epic is parked in the Hypercare column with an (HC DD-MM-YY) review date and reaches Done only via its close-out reviewreview/wrapup Check S (S1 park at delivery / S2 close-out review); the lingering-window and missing-date alerts are declared in the CI/CD & Pipelines Standard rather than duplicated heren/a