orbotodocs
Admin guideOperations

Data integrity

Find and clear links that point at another project: a ticket's milestone, version, parent or label, and the milestone or ticket of a risk or decision record.

Added in v0.230.0 A ticket, a risk or a decision record may only link rows of its own project. From 0.230.0 on, orboto refuses every write that would break that rule (see Tickets, Import, Risk register and Decision records). Links written before that - by an older version, or by an import mapping - stay in the data until you clear them. Two admin calls find and clear them:

  • GET /admin/data-integrity/cross-project-links reports them and changes nothing. It needs admin:system:read.
  • POST /admin/data-integrity/cross-project-links/repair clears the kinds you name. It needs admin:system:write.

There is no page for this in the admin area. Use the CLI (orboto get, orboto post), the REST API, or MCP through orboto_api_search and orboto_api_call.

What the report finds

The report lists eight kinds. Each is a link whose target belongs to a different project than the record that holds it:

KindThe linkWhat a repair does
ticket_milestoneA ticket's milestone is in another projectClears the ticket's milestone
ticket_versionA ticket's version is in another projectClears the ticket's version
ticket_parentA ticket's parent ticket is in another projectClears the parent, so the ticket becomes a top-level ticket
ticket_labelA ticket carries a label of another project, for example left behind by an importDeletes the link between ticket and label; the label stays
risk_milestone, risk_ticketA risk links a milestone or ticket outside its project; a risk without a project may hold no linkClears the link
decision_milestone, decision_ticketThe same for a decision recordClears the link
orboto get /admin/data-integrity/cross-project-links

For every kind the answer carries the number of links (count) and up to 100 example rows (sample), and the overall total. A row names the record that holds the link (recordId, recordRef - the ticket key, or the title of a risk or decision - and recordProjectId) and what it points at (linkedId, linkedProjectId). A workspace without such links answers total: 0.

The repair works on exactly the kinds you name and nothing else:

FieldMeaning
kindsOne or more of the eight kinds. Required.
reasonThree to 500 characters, stored in the audit entry. Required.
dryRuntrue counts what the call would clear and changes nothing.
limitThe most links this call clears, 1 to 10000, default 1000. It is one budget across all the kinds you name, spent in the order you list them.

Look before you clear:

orboto post /admin/data-integrity/cross-project-links/repair \
  '{"kinds":["ticket_milestone"],"reason":"clear links left by an old import","dryRun":true}'

Then run it without dryRun. The answer says what was cleared per kind (repaired, with count and up to 200 recordIds), the total of this call, and remaining - how many links of the kinds you named still point at another project:

orboto post /admin/data-integrity/cross-project-links/repair \
  '{"kinds":["ticket_milestone"],"reason":"clear links left by an old import","limit":5000}'
{
  "dryRun": false,
  "repaired": [{ "kind": "ticket_milestone", "count": 5000, "recordIds": ["..."] }],
  "total": 5000,
  "remaining": 1200
}

Repeat the call until remaining is 0. A repeat on a clean workspace clears nothing. In a dry run, remaining is what would be left after the call.

Through MCP the same call is orboto_api_call with {"method": "POST", "path": "/admin/data-integrity/cross-project-links/repair", "body": {...}}.

What a repair does and records

  • It works in batches of at most 500 links, with one database transaction per batch.
  • A link that another write changed since the report matched it is left alone.
  • Every changed ticket link adds an entry to the ticket's activity (milestone, version, parent or labels changed), and open boards refresh the ticket live. Risk and decision links write no ticket activity.
  • The call is written to the audit log as admin.relation_integrity.repaired, with your reason, the total, what remains, the number of batches and the per-kind counts. A dry run changes nothing and writes no audit entry.

Before you repair ticket_parent

Clearing a parent link breaks a hierarchy: the ticket leaves its epic or parent ticket and becomes a top-level ticket. Review the sample for ticket_parent before you repair that kind.

Troubleshooting

  • 403 on the report or the repair. The report needs admin:system:read, the repair admin:system:write. A dry run needs the write permission too, because it is a POST.
  • 400 on the repair. kinds is empty or names a kind that does not exist, reason is shorter than three characters, or limit is outside 1 to 10000.
  • remaining is above 0 after a call. The call hit its limit. Repeat it.
  • The report still lists links after a repair. A repair touches only the kinds you named. Run the report again to see which kinds are left.

On this page