Skip to content
GitmoapdocsBeta
Guide· How do I…?

Proof of Done

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

Quick start3 steps

Add acceptance criteria

You’ll need Developer on the repository, or the issue's own reporter.
  1. Open the issue and choose Add criterion in its Definition of Done.

  2. 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.

  3. 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 404 rather than a 403, 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:read to read criteria, the kind catalogue, the verification view and the regression range, and issue:update for 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 unverified however 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

StateWhat it meansCounts as satisfied
manualNo binding. A person ticks itwhen its box is ticked
unverifiedBound, and no evidence has ever decided itno
passingThe most recent evidence satisfied the bindingyes
failingThe most recent evidence contradicted itno
staleThe pipeline that used to prove this criterion ran again and no longer reports it — the test was deleted, renamed or skippedno

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

KindFieldsPassing whenFailing when
manual—its box is ticked—
testtest_id, optional suiteevery matching result passedany matching result failed
metricname, op, threshold, optional unitthe reported value compares as requiredit does not
coveragepath_glob, min_pctevery matched file reaches the floorany matched file is below it
nodenode_idthat pipeline node finished successfullyit failed or timed out
scorecard_checkscorecard_id, check_keya service-catalog check passesit 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 suite out 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 branchA run on a merge request's branch
Provesthe criteria hold in the productthe criteria hold at this change's head commit
Answersis this item done? did it regress?is this merge request safe to merge?
Writesthe criterion's own state and the item's verification, plus the per-commit recordthe per-commit record, and nothing else
Lifetimeuntil the next shipping-branch rununtil 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.

FromWhat happensTo
unverifiedevery required criterion satisfied, on a shipping branchverified
verifieda required criterion goes red on a shipping branch, item closedregressed, and the item reopens
verifieda required criterion goes red on a shipping branch, item openunverified — the criterion simply shows red
verifieda required criterion goes staleunverified — the claim is no longer being checked
regressedevery required criterion green againverified

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.

Terminalbash
gitmoap issue-criteria list --repo-id <id> --issue-id <id>
curlbash
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.

Terminalbash
gitmoap issue-criteria create --repo-id <id> --issue-id <id> --text <text>
curlbash
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.

Terminalbash
gitmoap issue-criteria update \
--repo-id <id> \
--issue-id <id> \
--criterion-id <id>
curlbash
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.

Terminalbash
gitmoap issue-criteria update \
--repo-id <id> \
--issue-id <id> \
--criterion-id <id>
curlbash
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.

Terminalbash
gitmoap issue-criteria update \
--repo-id <id> \
--issue-id <id> \
--criterion-id <id>
curlbash
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.

  1. Add a node that runs your tests and writes a report — JUnit XML from most runners, or cargo-nextest / libtest JSON.

  2. Add an Ingest Test Report node (gitmoap:report-ingest) and wire your test node's report into its artifact input. 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.

  3. Leave its format at auto unless the sniffing gets it wrong — auto reads XML as JUnit and anything else as cargo-nextest / libtest JSON. Set path when a source's directory holds more than one report file: without it the node takes the single .json or .xml file there, and the first one alphabetically if there are several.

  4. Leave the node's evidence output unwired. The platform reads it when it verifies a work item, and nothing downstream needs to consume it.

  5. 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.

Terminalbash
gitmoap issue-criteria reorder \
--repo-id <id> \
--issue-id <id> \
--order '<json>'
curlbash
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.

Terminalbash
gitmoap issue-criteria delete \
--repo-id <id> \
--issue-id <id> \
--criterion-id <id>
curlbash
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.

Terminalbash
gitmoap issue-criteria get-issue-verification --repo-id <id> --issue-id <id>
curlbash
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.

Terminalbash
gitmoap issue-criteria get-issue-regression --repo-id <id> --issue-id <id>
curlbash
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.

Terminalbash
gitmoap issue-criteria list-kinds
curlbash
curl -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_close in the container's issue settings, and completing an item with an unsatisfied required criterion answers 409 CONFLICT with a message beginning CRITERIA_NOT_GREEN and 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_green in 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

list_criterion_kindsGET /issue-criterion-kindsOpen in the API reference

Token scope

issue:read

Send

Nothing — the address is the whole request.

Refuses

  • 401
  • 403Forbidden — 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. Manual is 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

list_issue_criteriaGET /repositories/{repo_id}/issues/{issue_id}/criteriaOpen in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Work 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 reference

Token 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 false or 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; set false for an aspirational one.
  • textstringThe human statement — the criterion's title.

Refuses

  • 400Invalid text or binding
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Work item not found
  • 409A bound criterion cannot be created already ticked
  • 429Per-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 reference

Token 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 409 for 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 validate
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Work item or criterion not found
  • 409A bound criterion cannot be ticked by hand; unbind it first
  • 429Bound-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 reference

Token scope

issue:update

Send

Path
  • repo_iduuidRepository ID
  • issue_idint64Work-item iid

Refuses

  • 400The order must list every criterion exactly once
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Work item or criterion not found
  • 409The 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 reference

Token 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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Work item or criterion not found
  • 409The 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 reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Work 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_close ones — the set that actually gates Verified and a close.
    • required_satisfiedint32
    • satisfiedint32
    • totalint32
  • uiduuid
  • verificationregressedunverifiedverifiedWhether CI has proved the work item's acceptance criteria. unverified until a pipeline run proves them, verified once one has, and regressed when 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 reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Work item not found
  • 409The 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.
  • truncatedbooleantrue when the range holds more than 100 commits and suspects lists 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 countedFreeProTeamEnterprise
Acceptance criteria on one work item15151515
Criteria bound to pipeline evidence, across one repository2,0005,00020,00050,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 boundedLimit
Characters in a criterion's text500
Size of one node's evidence report8 MiB
Results in one run's evidence, across every node50,000
Suspect commits returned for a regression100, 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-repo pasted where an id belongs, or #412 pasted with its hash
What to do
Use the repository's id and the item's number. #412 is written 412 in a URL
400 BAD_REQUESTsaying a criterion must say something
Cause
The text is 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 text is 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:read for the reads or issue:update for 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 404 rather than 403 so 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": null first 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_verified in 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_regression in 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_evidence in 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 stale but never to passing

Examples

A manual criterion, which is the common case. Everything but text is optional:

json
{
"text": "The error page copy is reviewed by the design team"
}

A criterion bound to a named test, in a named suite:

json
{
"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:

json
{
"text": "Checkout p99 stays under 200 ms",
"binding": { "kind": "metric", "name": "checkout_p99_ms", "op": "lt", "threshold": 200, "unit": "ms" }
}
json
{
"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:

json
{
"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:

json
{ "binding": null }
json
{ "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:

json
{
"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.

  • 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