Proof of Done
- Read a work item's Definition of Done
- Add a criterion
- Reword a criterion, or stop it gating a close
- Tick a manual criterion
- Bind a criterion to evidence
- Emit the evidence from a pipeline
- Reorder the Definition of Done
- Remove a criterion
- Read the Proof-of-Done view
- Investigate a regression
- Look up what a kind takes
- Gate closing and merging on criteria
Overview
A work item's Definition of Done is a list of acceptance criteria, and on Gitmoap most of them can decide themselves. Write "the retry test passes" as a criterion bound to that test, and the box is filled in by the pipeline that ran it — against a real commit, on the branch that ships — instead of by whoever remembered to tick it. What you gain is a work item that knows whether it is done, and a merge button that can refuse a change whose criteria are red.
Criteria you cannot machine-check stay ordinary checkboxes, which is the default and the common case. And a closed item reopens itself when the test that proved it fixed goes red again.
Quick start
Add acceptance criteria
Open the issue and choose Add criterion in its Definition of Done.
Write the condition as a statement someone can check. Leave the kind on Manual for a person to tick, or bind it to a test, a metric or coverage so CI verifies it.
Save it. A required criterion that is not met holds the issue open.
Before you start
- A work item in a repository.Criteria live on items filed against a repository — a story, a bug, a task, an incident filed there. An epic in a group has no repository whose pipelines could prove anything, so every endpoint here is addressed through a repository and a group-scoped item has no Definition of Done you can author or read.
- Reporter access to read criteria, the verification view and the regression range. Reading follows the repository's visibility: a public repository answers anyone, an internal one answers anyone signed in, and a private one answers only its members — with a
404rather than a403, so its existence stays private. - Developer access to author them.Adding, editing, ticking, reordering and deleting a criterion are all Developer, one level above filing and commenting. A criterion is an assertion about the codebase that a merge gate can hang off, which is engineering authority rather than triage authority.
- A token with the right scope, if you are scripting rather than clicking:
issue:readto read criteria, the kind catalogue, the verification view and the regression range, andissue:updatefor every write. A token carries the scopes you chose when you minted it, and they cannot be added afterwards. The kind catalogue is the one read that always needs a credential — it belongs to no repository, so there is no public repository to answer for it. - A pipeline that emits evidence, if you want test, measurement or coverage criteria to fill themselves in.Those three are decided from per-result data a pipeline node publishes, and until something publishes it they sit at
unverifiedhowever green CI is. The task below sets that up. A criterion bound to a pipeline node is the exception: its verdict is the node's own outcome in the run, so it needs no report at all.
How it works
A criterion is a statement plus an optional binding
Each criterion carries a text — the human statement, up to 500 characters — an optional longer description, a required_for_close flag that defaults to true, and an optional binding. The binding is what turns a checkbox into a predicate: it names a test, a measurement, a coverage floor or a pipeline node, and the platform decides the criterion from the evidence a run produced rather than from anyone's opinion.
No binding means a manual checkbox, and that is deliberately the default. Most criteria on most teams are not machine-checkable — "the error copy does not say 'oops'" is a real acceptance criterion and always will be a human's call — and a tracker that demanded a test id before it would accept a line is a tracker where nobody writes the line. Bind the ones that pay for themselves and leave the rest as boxes.
A criterion's identity is stable across every edit. Reword it, re-describe it, make it optional, move it up the list — it keeps its evidence and its history. Only one edit throws that away: changing what it is bound to, which clears the evidence and puts the criterion back to unverified — or back to a plain checkbox, if you unbound it — because a different predicate has a different history. That reset is a container setting (binding_change_resets_evidence) and it is on by default.
The five states
| State | What it means | Counts as satisfied |
|---|---|---|
manual | No binding. A person ticks it | when its box is ticked |
unverified | Bound, and no evidence has ever decided it | no |
passing | The most recent evidence satisfied the binding | yes |
failing | The most recent evidence contradicted it | no |
stale | The pipeline that used to prove this criterion ran again and no longer reports it — the test was deleted, renamed or skipped | no |
A bound criterion's box is drawn from its state, and a person cannot tick it. Trying returns 409 CONFLICT, with a message beginning CRITERION_IS_BOUND. That refusal is the whole proposition: a box the machine owns is worth something precisely because opinion cannot reach it. Unbind the criterion if you want it back under human control.
What each kind binds to
| Kind | Fields | Passing when | Failing when |
|---|---|---|---|
manual | — | its box is ticked | — |
test | test_id, optional suite | every matching result passed | any matching result failed |
metric | name, op, threshold, optional unit | the reported value compares as required | it does not |
coverage | path_glob, min_pct | every matched file reaches the floor | any matched file is below it |
node | node_id | that pipeline node finished successfully | it failed or timed out |
scorecard_check | scorecard_id, check_key | a service-catalog check passes | it does not |
op is one of lt, le, gt, ge, eq, and the comparison reads value op threshold — lt with a threshold of 200 passes at 187.4 and fails at 200. Coverage takes no operator, because a coverage binding is only ever a floor.
scorecard_check is the one kind you do not author. It is filed onto a work item by a service-catalog campaign, and adding one by hand is refused with 400 BAD_REQUEST naming it. It appears in the kind catalogue so a client can render a criterion that already carries one. It is also the one binding no pipeline decides: its verdict comes from the service catalog's own recomputation, not from a run — which is why it is the one criterion that can move a work item's badge without any pipeline having run.
How a result is matched to a binding
Matching happens once per run, over everything the run's evidence reported.
- A test binding with no suite matches its test id in any suite. A binding that names a suite matches only that suite. Leaving
suiteout says "wherever this test lives"; fill it in when two suites share a test id and you mean one of them. - Several matches are conjunctive. A test id that exists in two suites and fails in one is a failing criterion. Anything else would let a criterion go green while the thing it names is broken.
- A coverage binding is checked against every file its glob matched. One file under the floor fails the criterion.
- A skipped test proves nothing. It is neither a pass nor a fail; it counts as an absence, exactly like a test that no longer exists.
- A node binding is read from the run itself, not from the evidence file — the node's terminal status is the verdict.
Where the evidence comes from
Test, measurement and coverage criteria are decided by a file a pipeline node publishes: per-test results, per-measurement values and per-file coverage percentages, in a form the platform reads. The gitmoap:report-ingest capability turns the report your test runner already produces — JUnit XML, or cargo-nextest / libtest JSON — into that form, and its artifact input takes several sources at once, so every test node in the pipeline can feed one evidence set. A provider you write yourself can publish the same shape directly on a port of type evidence.
A run whose repository has no bound criteria costs nothing: the evidence is not even read. Nothing about criteria slows a pipeline down until you bind one.
One bad report discards the whole run's evidence, not just its own. A report over 8 MiB, one that does not parse, or one written to a schema the platform does not recognise stops the evaluation there: nothing is read from any node of that run, and every criterion keeps the state it had until the next run. Half a report is worse than none, because a criterion that flips to failing on a truncated file is a false alarm and false alarms are what get a feature switched off — and a run that dropped only the unreadable half would leave you unable to tell which criteria the run had actually spoken to.
Shipping branches, and merge-request heads
The same evidence answers two different questions, and the answers are kept apart on purpose.
| A run on a shipping branch | A run on a merge request's branch | |
|---|---|---|
| Proves | the criteria hold in the product | the criteria hold at this change's head commit |
| Answers | is this item done? did it regress? | is this merge request safe to merge? |
| Writes | the criterion's own state and the item's verification, plus the per-commit record | the per-commit record, and nothing else |
| Lifetime | until the next shipping-branch run | until the next push, which changes the head commit |
A shipping branch is the repository's default branch, plus any branch pattern the container adds in its issue settings (closing_branches) — the same set that decides whether a Fixes #412 in a commit message closes anything. A green test on a feature branch is a normal Tuesday: it never touches a criterion's state, and it never verifies or reopens an item.
Evidence is also ordered by commit ancestry, not by clock. A slow run on an old commit that finishes after a fast run on a newer one is dropped rather than allowed to overwrite the newer verdict, and a redelivered result changes nothing.
Verified, and regressed
When every required criterion on an item is satisfied — bound ones passing, manual ones ticked — a shipping-branch run marks the item verified and records the commit and run that proved it. An item with no criteria at all is never verified, and neither is one whose criteria are all optional: "nobody wrote a Definition of Done" and "CI proved the Definition of Done" are opposite claims, and at least one line has to be required before there is a claim to prove.
| From | What happens | To |
|---|---|---|
unverified | every required criterion satisfied, on a shipping branch | verified |
verified | a required criterion goes red on a shipping branch, item closed | regressed, and the item reopens |
verified | a required criterion goes red on a shipping branch, item open | unverified — the criterion simply shows red |
verified | a required criterion goes stale | unverified — the claim is no longer being checked |
regressed | every required criterion green again | verified |
Only evidence moves the badge. Every row above is written when a shipping-branch run moves a criterion — or, for a scorecard_check, when the service catalog recomputes it. Add a criterion, delete one, tick a manual box or change a binding, and you have changed the answer the next such evaluation will compute; the badge follows then. The two gates below do not wait for it — both read the criteria themselves, so a box you tick counts against a close or a merge immediately.
verified is not closed. Two container settings decide what the platform does with the verdict, and they are orthogonal: auto_close_on_verified (off by default) closes the item when it verifies, and require_verified_criteria_to_close (off by default) refuses a manual close while a required criterion is unsatisfied. An item that verifies with nothing linked to it is surfaced rather than acted on — the badge turns green and the activity says which run proved it — because closing an item because a test somebody named happens to pass is exactly the over-eager automation that gets automation turned off.
The reopen is the payoff. A bug that comes back is the same bug, not a new ticket. A closed item whose required criterion goes from passing to failing on a shipping branch reopens, records both ends of the range — the last green commit and the first red one — and writes an activity naming the criterion and both runs. It is gated by auto_reopen_on_regression, which is on by default.
Stale never reopens anything
An unmatched binding is not a failure. The test was deleted, renamed or skipped, and the absence of an assertion is not a failed assertion. So the criterion goes stale, keeping the evidence it last had so you can see what it used to prove.
And stale is narrower than it first looks: a criterion only goes stale when the very pipeline that last proved it ran again and no longer reports it. A lint pipeline that legitimately reports no tests cannot stale your test criteria, and a docs-only push cannot hollow out a repository's Definition of Done. A stale criterion blocks verification, shows loudly on the item, and never triggers a reopen — a closed item springing open because somebody renamed a file would end trust in the feature in one sprint.
How to
Read a work item's Definition of Done
Every criterion comes back in checklist order, each with its state, whether it counts as satisfied, its binding in structured form, and the evidence that last decided it — the pipeline, the run, the commit, when it was observed and, for a measurement or a coverage floor, the value seen.
Open the work item. The Definition of Done panel sits under the description: one row per criterion, with a checkbox, its text, a Required marker, and — where it is bound — a badge saying what it is bound to. Hover the badge for the last evidence: the commit, the run and when it was seen. The panel header carries the running count of satisfied criteria against required ones.
gitmoap issue-criteria list --repo-id <id> --issue-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/criteria \ -H "Authorization: Bearer $GITMOAP_TOKEN"Add a criterion
Send the statement and, optionally, a binding. Leave the binding out and you get a manual checkbox. required_for_close defaults to true — a line in the Definition of Done is required by definition — so pass false explicitly for an aspirational one. New criteria land at the end of the list unless you name an ordinal, and an ordinal you name is taken literally: nothing below it is pushed down, so reorder the whole list rather than hand-picking positions when the order matters.
Open the work item and choose Add criterion in the Definition of Done panel. Type the statement, then pick a kind: Manual is preselected, and choosing Test, Metric, Coverage or Pipeline node swaps in the fields that kind takes. The Required toggle starts on. Add appends the criterion to the bottom of the list.
gitmoap issue-criteria create --repo-id <id> --issue-id <id> --text <text>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/criteria \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "text": "<string>" }'A bound criterion cannot be created already ticked — checked: true alongside a binding is refused with 409 CONFLICT rather than quietly ignored.
Reword a criterion, or stop it gating a close
Send only the fields you are changing. text and description rewrite the statement, required_for_close: false turns a gate into an aspiration that still shows and still rolls up, and leaving binding out of the body altogether keeps the criterion bound to exactly what it was bound to — which is what makes a reword free of consequences. The criterion keeps its id, its state and every verdict a pipeline recorded against it.
Open the criterion's row menu in the Definition of Done panel and choose Edit. Change the statement or its longer description, or turn the Required toggle off. Saving without touching the kind picker leaves the binding and its evidence alone.
gitmoap issue-criteria update \ --repo-id <id> \ --issue-id <id> \ --criterion-id <id>curl -X PATCH https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/criteria/{criterion_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Making a criterion optional is the gentler alternative to deleting one: the record survives, and only its power to block a close goes away.
Tick a manual criterion
Ticking is an edit that sets checked. It applies to manual criteria only.
Click the criterion's checkbox in the Definition of Done panel. A bound criterion's box is filled in from evidence and does not respond to a click; its tooltip says what it is bound to and which run last decided it.
gitmoap issue-criteria update \ --repo-id <id> \ --issue-id <id> \ --criterion-id <id>curl -X PATCH https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/criteria/{criterion_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Bind a criterion to evidence
Send a binding on an existing criterion to bind it, a different binding to re-bind it, or "binding": null to unbind it back to a checkbox. Omit the key entirely and the binding is left alone, which is what lets you reword a criterion without disturbing what proved it.
Open the criterion's row menu in the Definition of Done panel and choose Edit. The kind picker holds the same five options the add form offers; switching kinds swaps the fields. Changing what a criterion is bound to clears its evidence, and the dialog says so before you save. Choosing Manual unbinds it and hands the box back to a person.
gitmoap issue-criteria update \ --repo-id <id> \ --issue-id <id> \ --criterion-id <id>curl -X PATCH https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/criteria/{criterion_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Binding and re-binding are where evidence is lost, so it is worth being deliberate: a reword keeps history, a re-bind starts a new one.
Binding is also the one edit that can be refused for a quota. A manual criterion costs nothing against the repository's bound-criteria limit; the moment you bind it, it does, and a repository already at that ceiling answers 429 TOO_MANY_REQUESTS and leaves the criterion exactly as it was.
Emit the evidence from a pipeline
Bound criteria stay unverified until a pipeline publishes results. This is a pipeline-authoring task rather than a tracker one, and it is done once per repository.
Add a node that runs your tests and writes a report — JUnit XML from most runners, or cargo-nextest / libtest JSON.
Add an Ingest Test Report node (
gitmoap:report-ingest) and wire your test node's report into itsartifactinput. That input accepts several sources, so wire every test node whose results should count; the node waits for all of them and merges their results into one evidence set.Leave its
formatatautounless the sniffing gets it wrong —autoreads XML as JUnit and anything else as cargo-nextest / libtest JSON. Setpathwhen a source's directory holds more than one report file: without it the node takes the single.jsonor.xmlfile there, and the first one alphabetically if there are several.Leave the node's
evidenceoutput unwired. The platform reads it when it verifies a work item, and nothing downstream needs to consume it.Run the pipeline on the default branch, then reload the work item once the run has finished. A criterion whose test appears in the report moves off
unverified.
A provider you write yourself can publish evidence directly: declare an output port of type evidence and write an evidence.json into the node's outputs directory in the shape shown under Examples. Coverage and measurement results have no ingest capability of their own, so they arrive this way.
Reorder the Definition of Done
The order is the order people read the checklist in, so it is worth keeping the setup steps above the acceptance steps. Reordering takes the full list of criterion ids in their new order — every criterion on the item, exactly once — and returns them renumbered.
Drag a criterion by the handle at the left of its row. The list reorders as you drag and saves when you drop. Nothing else about the criterion changes: its state, its evidence and its tick ride along with it.
gitmoap issue-criteria reorder \ --repo-id <id> \ --issue-id <id> \ --order '<json>'curl -X PUT https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/criteria/order \ -H "Authorization: Bearer $GITMOAP_TOKEN"Remove a criterion
Warning
Deleting a criterion destroys its evidence along with it — every verdict a pipeline recorded against it, the commit and run that last proved it, and its tick if it was manual. It cannot be undone, and re-creating it with identical wording and an identical binding gives you a new criterion at unverified with no history. If the criterion has stopped being a gate but the record is worth keeping, make it optional instead: an optional criterion still shows and still rolls up, it just does not block a close.
Deleting changes what the item is judged on. Removing the last unsatisfied required criterion clears the way for the close gate and the merge gate straight away, and lets the next shipping-branch run mark the item verified; removing a bound criterion also frees a slot against the repository's bound-criteria limit.
Open the criterion's row menu in the Definition of Done panel and choose Delete. The confirmation names the criterion and, where it is bound, says what evidence goes with it. Deletion is immediate.
gitmoap issue-criteria delete \ --repo-id <id> \ --issue-id <id> \ --criterion-id <id>curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/criteria/{criterion_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Read the Proof-of-Done view
The verification view is the whole picture in one read: the item's verification state, the commit and run that verified it or the ones it regressed from, every criterion with its evidence, a summary of how many criteria are satisfied against how many are required, and — where the item was filed from a blueprint — its facets, each green when every required criterion under it is satisfied.
The Definition of Done panel's header is this view in miniature: a badge reading Verified, Regressed or nothing, and the satisfied count. Choose Proof of Done in the panel header for the full view, which groups criteria under their facets and links each piece of evidence to the run that produced it.
gitmoap issue-criteria get-issue-verification --repo-id <id> --issue-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/verification \ -H "Authorization: Bearer $GITMOAP_TOKEN"Investigate a regression
When an item is regressed, the platform already knows both ends of the range: the commit it was last verified at, and the commit whose evidence proved it broken. The regression read hands back the commits between them — newest first, each with its author — plus the required criteria that are red right now. One of those commits broke it.
The list is capped at 100 commits. When the range is wider, the response says so and gives you the true count alongside the newest slice, which is the end nearest the failure and the likeliest place to look. A commit whose object can no longer be read — a force-push took it away — comes back as a bare id rather than failing the whole read.
A regressed work item carries a banner above the description: the criterion that broke, the last green commit, the first red one, and Show suspects. The suspect list is the commits in that range, newest first, with their authors and messages, each linking to its diff.
gitmoap issue-criteria get-issue-regression --repo-id <id> --issue-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/regression \ -H "Authorization: Bearer $GITMOAP_TOKEN"Asking for the range on an item that is not regressed is refused with 409 CONFLICT — the message begins NOT_REGRESSED — rather than answered with an empty list, because an empty range and "this item is fine" are different answers.
Look up what a kind takes
The kind catalogue is a global read: every criterion kind with its label, its help text, whether the platform evaluates it, the typed fields it takes, and a JSON Schema you can render a form from. It lists all six kinds, scorecard_check included, so a client can render a criterion a campaign filed as well as the five you author. It is the same contract pipeline capability configuration uses, and it is how a client stays correct when a kind gains a field.
The kind picker in the add and edit dialogs is built from this catalogue, so the fields it shows are always the fields the kind takes. There is no separate screen for it.
gitmoap issue-criteria list-kindscurl -X GET https://api.gitmoap.example/issue-criterion-kinds \ -H "Authorization: Bearer $GITMOAP_TOKEN"Gate closing and merging on criteria
Criteria are worth writing on their own, but the two settings that make them binding live elsewhere and are off until you turn them on.
- Refuse a close while the work is unproved. Turn on
require_verified_criteria_to_closein the container's issue settings, and completing an item with an unsatisfied required criterion answers409 CONFLICTwith a message beginningCRITERIA_NOT_GREENand a count of how many are outstanding. Cancelling stays exempt, so work you have decided against can always be closed, and an item with no required criteria is never blocked. - Refuse a merge while the criteria are red. Turn on
require_issue_criteria_greenin the repository's merge request settings, and every work item the merge request closes must have its required criteria green at the merge request's head commit. Items the merge request merely mentions are not read. A head commit no pipeline has run against fails this check rather than passing it: a gate that opens because it has nothing to say is not a gate. A required manual box counts as its tick, which is the same wherever the head is — so an unticked box blocks the merge even in a repository with no pipeline at all.
Both settings are inherited from the groups above, so turning one on at the organisation's root group covers every repository beneath it.
Reference
8 endpoints
GET/issue-criterion-kindsissue:read
The catalogue of acceptance-criterion kinds and their argument field schemas
Token scope
issue:read
Send
Nothing — the address is the whole request.
Refuses
401403Forbidden — the caller's access level or token scope is insufficient
Receive
kinds[]object5 fields
- config_schemaobjectA JSON Schema over the fields — render a form from this.
- descriptionstring
- evaluablebooleanWhether the platform evaluates this kind from evidence (i.e. it is a real binding, not a manual checkbox).
- kindcoveragemanualmetricnodescorecard_checktestA kind of acceptance criterion.
Manualis a human-ticked checkbox (no binding); the rest bind to a predicate the platform evaluates from pipeline evidence. - labelstring
GET/repositories/{repo_id}/issues/{issue_id}/criteriaissue:read
List a work item's acceptance criteria
Token scope
issue:read
Send
Path
- repo_iduuidRepository ID
- issue_idint64Work-item iid
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Work item not found
Receive
No body.
POST/repositories/{repo_id}/issues/{issue_id}/criteriaissue:update
Add an acceptance criterion to a work item's Definition of Done
create_issue_criterionPOST /repositories/{repo_id}/issues/{issue_id}/criteriaOpen in the API referenceToken scope
issue:update
Counts against
criteria_per_issue
Send
Path
- repo_iduuidRepository ID
- issue_idint64Work-item iid
Body
- bindingobjectoptional
- checkedobjectoptionalFor a manual criterion, whether it starts ticked. Must be
falseor absent for a bound one. - descriptionobjectoptional
- ordinalint32optionalWhere to place it; appended to the end when absent.
- required_for_closeobjectoptionalWhether this criterion must be satisfied before the work item can be closed or verified.
Defaults to
true; setfalsefor an aspirational one. - textstringThe human statement — the criterion's title.
Refuses
400Invalid text or binding401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Work item not found409A bound criterion cannot be created already ticked429Per-issue or per-repository criteria limit reached
Receive
No body.
PATCH/repositories/{repo_id}/issues/{issue_id}/criteria/{criterion_id}issue:update
Edit an acceptance criterion
update_issue_criterionPATCH /repositories/{repo_id}/issues/{issue_id}/criteria/{criterion_id}Open in the API referenceToken scope
issue:update
Counts against
criteria_per_repo
Send
Path
- repo_iduuidRepository ID
- issue_idint64Work-item iid
- criterion_iduuidCriterion ID
Body
- bindingobjectoptional
- checkedobjectoptionalTick or untick a manual criterion. Refused with a
409for a bound one, whose state comes from its evidence. - descriptionobjectoptional
- ordinalint32optional
- required_for_closeobjectoptional
- textobjectoptional
Refuses
400The text is empty or too long, or the binding's arguments do not validate401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Work item or criterion not found409A bound criterion cannot be ticked by hand; unbind it first429Bound-criterion limit reached for this repository
Receive
No body.
PUT/repositories/{repo_id}/issues/{issue_id}/criteria/orderissue:update
Reorder a work item's acceptance criteria
reorder_issue_criteriaPUT /repositories/{repo_id}/issues/{issue_id}/criteria/orderOpen in the API referenceToken scope
issue:update
Send
Path
- repo_iduuidRepository ID
- issue_idint64Work-item iid
Refuses
400The order must list every criterion exactly once401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Work item or criterion not found409The request conflicts with the resource's current state; `reason` says how
Receive
No body.
DELETE/repositories/{repo_id}/issues/{issue_id}/criteria/{criterion_id}issue:update
Delete an acceptance criterion
delete_issue_criterionDELETE /repositories/{repo_id}/issues/{issue_id}/criteria/{criterion_id}Open in the API referenceToken scope
issue:update
Send
Path
- repo_iduuidRepository ID
- issue_idint64Work-item iid
- criterion_iduuidCriterion ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Work item or criterion not found409The request conflicts with the resource's current state; `reason` says how
Receive
No body.
GET/repositories/{repo_id}/issues/{issue_id}/verificationissue:read
The Proof-of-Done view — every criterion, its state, and the evidence that produced it
get_issue_verificationGET /repositories/{repo_id}/issues/{issue_id}/verificationOpen in the API referenceToken scope
issue:read
Send
Path
- repo_iduuidRepository ID
- issue_idint64Work-item iid
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Work item not found
Receive
facets[]object4 fields
- facet_categoryobjectoptional
- facet_keystring
- requiredbooleanWhether any criterion under the facet gates close.
- satisfiedbooleanGreen iff every required criterion under the facet is satisfied — the same set the merge gate keys on, so the rollup is presentational and never a second source of truth.
- regressed_atobjectoptional
- regressed_from_shaobjectoptional
- regressed_run_idobjectoptional
summaryobject4 fields
- requiredint32Of the
required_for_closeones — the set that actually gatesVerifiedand a close. - required_satisfiedint32
- satisfiedint32
- totalint32
- requiredint32Of the
- uiduuid
- verificationregressedunverifiedverifiedWhether CI has proved the work item's acceptance criteria.
unverifieduntil a pipeline run proves them,verifiedonce one has, andregressedwhen a later run shows that evidence no longer holds. - verified_atobjectoptional
- verified_at_shaobjectoptional
- verified_run_idobjectoptional
GET/repositories/{repo_id}/issues/{issue_id}/regressionissue:read
The suspect commits behind a regression — the last-green→first-red range, one of which broke it
get_issue_regressionGET /repositories/{repo_id}/issues/{issue_id}/regressionOpen in the API referenceToken scope
issue:read
Send
Path
- repo_iduuidRepository ID
- issue_idint64Work-item iid
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Work item not found409The work item is not in a Regressed verification state
Receive
- from_shaobjectoptional
suspects[]object2 fields
- commitobjectoptional
- shastring
- to_shaobjectoptional
- totalintegerHow many commits the range holds, including any not listed in
suspects. - truncatedboolean
truewhen the range holds more than 100 commits andsuspectslists only the newest 100. - uiduuid
Limits
Two counts are enforced when a criterion is written, and a refused write creates nothing at all — no half-added criterion and no partially seeded checklist.
| What is counted | Free | Pro | Team | Enterprise |
|---|---|---|---|---|
| Acceptance criteria on one work item | 15 | 15 | 15 | 15 |
| Criteria bound to pipeline evidence, across one repository | 2,000 | 5,000 | 20,000 | 50,000 |
Fifteen per work item is the same on every plan, and a limit increase does not move it. It is a shape limit rather than a capacity one: an item that needs a twentieth acceptance criterion is describing more than one piece of work, and the fix is to split it and let the parts that are really their own job become child items. Reaching it answers 429 TOO_MANY_REQUESTS naming the work item.
The repository count is of bound criteria only — manual checkboxes do not count towards it. It exists because every bound criterion in a repository is re-checked against every pipeline run there, so the number is a bound on work the platform does on your behalf. Reaching it answers 429 TOO_MANY_REQUESTS naming the repository, and the same refusal comes back if you try to bind a manual criterion while the repository is at the ceiling. Delete bound criteria on work that is long closed, leave the criterion manual, or file a limit-increase request naming this limit — unlike the per-item fifteen, this one can be raised.
The rest are fixed, and they are shape rather than plan:
| What is bounded | Limit |
|---|---|
| Characters in a criterion's text | 500 |
| Size of one node's evidence report | 8 MiB |
| Results in one run's evidence, across every node | 50,000 |
| Suspect commits returned for a regression | 100, with the true total alongside |
Going over the text limit is a 400 BAD_REQUEST when you write the criterion. Going over either evidence limit is not an error you see: the run's evidence is discarded whole — every node's, not only the offending one — and every criterion keeps the state it had, as though the run had published nothing.
Troubleshooting
400 BAD_REQUESTon any call, mentioning a path value
- Cause
- The repository id or the work-item number in the URL is not well formed — usually a path like
my-group/my-repopasted where an id belongs, or#412pasted with its hash - What to do
- Use the repository's id and the item's number.
#412is written412in a URL
400 BAD_REQUESTsaying a criterion must say something
- Cause
- The
textis empty, or empty once the spaces are trimmed - What to do
- Give it a statement. A criterion with no wording is a box nobody can act on
400 BAD_REQUESTabout the criterion being too long
- Cause
- The
textis over 500 characters - What to do
- Shorten the statement and move the detail into the criterion's
description, which is where the long form belongs
400 BAD_REQUESTnaming a binding argument
- Cause
- The binding is incomplete or out of range — a test binding with no test id, an empty suite, a metric with no measurement name, a coverage binding with no path, or a coverage floor outside 0 to 100
- What to do
- Fill in the missing field, or drop the binding and add it once you know what the pipeline reports
400 BAD_REQUESTabout a scorecard-check binding
- Cause
- That kind is written by a service-catalog campaign and cannot be authored by hand
- What to do
- Bind a test, a metric, a coverage floor or a pipeline node instead. The check appears on the item by itself when a campaign files it
400 BAD_REQUESTon reorder, about listing every criterion
- Cause
- The order sent does not name each of the item's criteria exactly once — usually because a criterion was added or deleted after the list was read
- What to do
- Re-read the criteria, rebuild the order from what is there now, and send it again
400 BAD_REQUESTabout an unrecognised field
- Cause
- The request body carries a field the API does not define, often a typo or a field from an older script
- What to do
- Send only the documented fields. An unknown field is refused rather than ignored, so a typo fails immediately instead of silently doing nothing
401 UNAUTHORIZED
- Cause
- No credential was sent, or the one sent is invalid or expired
- What to do
- Sign in again, or mint a fresh token. Only a public repository answers a criteria read with no credential at all; the kind catalogue and every write always need one
403 FORBIDDENon a write, where reading works
- Cause
- Authoring criteria needs Developer on the repository, one level above filing and commenting
- What to do
- Ask an Owner or Maintainer to raise your access level, or ask somebody with Developer access to make the change
403 INSUFFICIENT_SCOPE
- Cause
- The token is valid but was minted without the scope this call needs
- What to do
- Mint a new token carrying
issue:readfor the reads orissue:updatefor the writes. Scopes are fixed when a token is created and cannot be added afterwards
404 NOT_FOUNDon a repository or work item you are sure exists
- Cause
- Either the repository is private and you are not a member, or the item number is wrong, or the item has been moved and has a new number
- What to do
- A private repository answers
404rather than403so its existence stays private. Check your membership, then search for the item by title to find its number in the repository it is in now
404 NOT_FOUNDnaming the criterion
- Cause
- The criterion id belongs to another work item, or it has been deleted. A reorder whose list is the right length but names an id the item does not have answers this too, not
400 - What to do
- Read the item's criteria and take the ids from there. Ids are stable across every edit, so a stale id means the criterion is gone rather than renamed
409 CONFLICTon create, saying the criterion is bound
- Cause
- The body asks for a bound criterion that is already ticked
- What to do
- Drop
checked. A bound criterion's box is filled in from evidence; it has no initial tick to set
409 CONFLICTon update, saying the criterion is bound
- Cause
- You are ticking a criterion whose state is a function of the evidence a pipeline produced
- What to do
- Run the pipeline that decides it, or unbind the criterion with
"binding": nullfirst and accept that its evidence is cleared when you do
409 CONFLICTon the regression range, saying the item is not regressed
- Cause
- The work item is not in a regressed state, so there is no last-green-to-first-red range to report
- What to do
- Read the verification view instead — it carries the item's state and, for a verified item, the commit and run that proved it
429 TOO_MANY_REQUESTSnaming the work item
- Cause
- The item is at fifteen criteria, which is the ceiling on every plan
- What to do
- Delete criteria the item does not need, or split the work. Fifteen is not raised by a limit increase
429 TOO_MANY_REQUESTSnaming the repository
- Cause
- The repository is at its ceiling for criteria bound to pipeline evidence — reached either by adding a bound criterion or by binding a manual one
- What to do
- Delete bound criteria on closed work, leave this one manual, or file a limit-increase request naming the per-repository criteria limit. Manual criteria are not counted
A test, metric or coverage criterion stays unverified after a green pipeline run
- Cause
- No node in that run publishes evidence, or the node published none
- What to do
- Add an Ingest Test Report node wired to your test node, and check the run's node list shows it succeeding. A run with no evidence leaves every criterion of those three kinds exactly where it was
One run's evidence changed nothing, and the run before and after it both worked
- Cause
- One report in that run was over 8 MiB, did not parse, or carried a schema the platform does not recognise, so the whole run's evidence was discarded
- What to do
- Shrink or fix the report the node published — split a huge suite across nodes, or check the runner wrote the format you told the ingest node to expect. The next good run reconciles everything by itself
A regressed item's suspect list is empty and total is 0
- Cause
- The regression was recorded without both ends of the range, so there is nothing to walk between
- What to do
- Read the failing criteria in the same response, then compare the shipping branch against the last commit you know was green. The next regression on this item records both ends
A criterion bound to a pipeline node stays unverified
- Cause
- That node was not part of the run, or it ended cancelled — only success, failure and timeout are verdicts
- What to do
- Check the node id in the binding against the node's id in the pipeline, and re-run the pipeline so the node actually executes
A criterion is unverified even though the report contains its test
- Cause
- The test id in the binding does not match the id in the report, or the binding names a suite the result does not carry
- What to do
- Copy the id exactly as your test runner prints it, punctuation and path included. Leave the suite out to match the id in any suite
A criterion went stale after a refactor
- Cause
- The pipeline that used to prove it ran again and no longer reports that test — it was renamed, deleted or skipped
- What to do
- Point the binding at the new test id, or delete the criterion if the assertion is genuinely gone. A stale criterion blocks verification and never reopens anything
A green test on a feature branch does not tick anything
- Cause
- Only a run on a shipping branch writes a criterion's state. A feature-branch run records a per-commit result the merge check reads, and nothing else
- What to do
- Merge the change, or read the merge request's checks, which report the criteria at its head commit
Every criterion is green but the item is not verified
- Cause
- A required manual box is unticked; or nothing on the item is required, or the item has no criteria at all — neither of those is ever verified
- What to do
- Tick the remaining manual boxes, or mark at least one criterion required. The badge is written by the next shipping-branch run that moves a criterion, so it lags an edit. The verification view's summary names the required count against the satisfied one
The item is verified and still open
- Cause
- Closing on verification is off by default, on purpose
- What to do
- Close it yourself, or turn on
auto_close_on_verifiedin the container's issue settings if the team wants that
A closed item reopened itself
- Cause
- A required criterion went from passing to failing on a shipping branch
- What to do
- Read the activity and the regression banner, which name the criterion, both runs and the suspect commits. Turn off
auto_reopen_on_regressionin the container's issue settings if this is not the behaviour you want
An item reopens repeatedly for the same criterion
- Cause
- The criterion is bound to a flaky test, and a red run is taken at face value
- What to do
- Fix the test, or unbind that criterion. The platform does not quietly disbelieve its own CI, so a binding is exactly as trustworthy as the test behind it
A merge is blocked saying required criteria are not green at the head
- Cause
- The repository requires criteria green to merge, and either a criterion is red at that commit or no pipeline has run against it
- What to do
- Push a commit, or re-run the pipeline on the merge request's head, then re-check. A head commit with no evidence fails this gate rather than passing it
Editing a criterion cleared its evidence
- Cause
- The edit changed the binding, which resets the criterion to
unverified - What to do
- Reword and re-describe freely — those keep evidence. If the team would rather carry evidence across a re-binding, turn off
binding_change_resets_evidencein the container's issue settings
A merge check went red after a second pipeline finished on the same commit
- Cause
- The per-commit record the merge check reads is merged, not replaced — a second pipeline folds in the criteria it decided and leaves the rest alone. So the red one is a criterion the second pipeline genuinely spoke to
- What to do
- Read the failing criterion named in the merge check and the run that decided it. A criterion neither pipeline matched keeps whatever verdict it had
Two runs on one commit disagreed about a criterion, and the later one won
- Cause
- For a criterion both runs matched, the later verdict is the current one — the platform does not average two answers about the same code
- What to do
- Decide which pipeline should own that binding, and bind the criterion to a test only that pipeline runs
A skipped test does not count as a pass
- Cause
- A skipped test proves neither that the assertion holds nor that it fails, so it is treated as an absence
- What to do
- Unskip it, or bind the criterion to a test that runs. A skipped test can push a criterion to
stalebut never topassing
Examples
A manual criterion, which is the common case. Everything but text is optional:
{ "text": "The error page copy is reviewed by the design team"}A criterion bound to a named test, in a named suite:
{ "text": "Retrying a payment does not double-charge", "description": "A retry that arrives after the first charge succeeded must be idempotent.", "binding": { "kind": "test", "suite": "cart", "test_id": "tests/cart.rs::retry_no_double_charge" }}A measurement compared against a threshold, and a coverage floor over a path. The comparison reads value op threshold, so this passes while the reported checkout_p99_ms stays under 200:
{ "text": "Checkout p99 stays under 200 ms", "binding": { "kind": "metric", "name": "checkout_p99_ms", "op": "lt", "threshold": 200, "unit": "ms" }}{ "text": "The cart module keeps 90% line coverage", "binding": { "kind": "coverage", "path_glob": "src/cart/**", "min_pct": 90 }}An aspirational criterion that shows and rolls up but does not gate a close:
{ "text": "A follow-up benchmark is added to the nightly suite", "required_for_close": false}Unbinding a criterion and ticking it by hand. The first body hands the box back to a person and clears the evidence; the second ticks it. The binding is applied before the tick is checked, so sending both keys in one body works too:
{ "binding": null }{ "checked": true }The evidence a pipeline node publishes, for a provider you write yourself. Write it as evidence.json in the node's outputs directory, on a port of type evidence. A measurement states what was measured and never whether that was acceptable — the threshold lives on the criterion, so it can change without your pipeline changing:
{ "schema": "gitmoap.evidence/v1", "commit_sha": "9f2c1aa4b3e5d6c7a8b9c0d1e2f3a4b5c6d7e8f9", "results": [ { "kind": "test", "suite": "cart", "id": "tests/cart.rs::empty_cart_rejected", "status": "passed", "duration_ms": 12 }, { "kind": "test", "suite": "cart", "id": "tests/cart.rs::retry_no_double_charge", "status": "failed", "duration_ms": 41, "message": "assertion failed: charges.len() == 1 (was 2)" }, { "kind": "metric", "name": "checkout_p99_ms", "value": 187.4, "unit": "ms" }, { "kind": "coverage", "path": "src/cart/mod.rs", "percent": 93.2 } ]}FAQ
Do I have to bind anything?
No. A criterion with no binding is an ordinary checkbox and always will be a legitimate one. Bind the criteria where a test already exists and the tick is worth automating; leave the rest alone.
Can two work items bind to the same test?
Yes, and to the same measurement with different thresholds. Each criterion is decided independently from the same evidence.
What happens to criteria when a work item moves to another repository?
They move with it, along with the discussion, the activity and the relations. The evidence stays attached, and future runs in the destination repository decide the criteria from then on.
Does deleting a blueprint delete the criteria it seeded?
No. Criteria a blueprint put on an item are ordinary criteria from the moment they are written, and nothing about a Definition of Done is destroyed when the type that seeded it goes away.
Why can I not tick a bound criterion just this once?
Because the value of a machine-decided box is that opinion cannot reach it. Unbind the criterion if a human should own it — the refusal names the reason and the unbind is one call.
Can a criterion be proved by a run in a different repository?
No. A binding is decided by the pipelines of the repository the work item lives in, which is why criteria are addressed through a repository and why an item in a group carries none.
Does anything watch for a criterion that has been red for a long time?
Nothing ages a criterion out. Its state is on the item and in the verification view, and every move a criterion makes — green, red or stale — is written to the item's activity timeline with the run that decided it. Webhooks are coarser: an item becoming verified sends a verified delivery and a regression sends a regressed one, so a subscriber hears about the item's verdict rather than about each criterion under it.
Related
- Work itemsfiling, status, hierarchy, and the close and reopen behaviour these criteria gate
- Blueprintscustom work-item types whose facets seed criteria automatically, bound ones included
- Pipelinesbuilding the pipeline whose run produces the evidence
- Merge requeststhe merge checks, including the gate that reads criteria at a change's head commit
- Merge request settingsturning the criteria gate on for a repository or a whole group
- Linking work to codeclosing an item from a commit message, and the branches that count as shipping
- Webhookssubscribing to the verified and regressed deliveries a criteria change produces
- Billing and plansreading the limits in force, and asking for a bigger per-repository criteria ceiling
- Access tokensminting a token with the issue scopes a script needs