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-linksreports them and changes nothing. It needsadmin:system:read.POST /admin/data-integrity/cross-project-links/repairclears the kinds you name. It needsadmin: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:
| Kind | The link | What a repair does |
|---|---|---|
ticket_milestone | A ticket's milestone is in another project | Clears the ticket's milestone |
ticket_version | A ticket's version is in another project | Clears the ticket's version |
ticket_parent | A ticket's parent ticket is in another project | Clears the parent, so the ticket becomes a top-level ticket |
ticket_label | A ticket carries a label of another project, for example left behind by an import | Deletes the link between ticket and label; the label stays |
risk_milestone, risk_ticket | A risk links a milestone or ticket outside its project; a risk without a project may hold no link | Clears the link |
decision_milestone, decision_ticket | The same for a decision record | Clears the link |
orboto get /admin/data-integrity/cross-project-linksFor 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.
Clearing the links
The repair works on exactly the kinds you name and nothing else:
| Field | Meaning |
|---|---|
kinds | One or more of the eight kinds. Required. |
reason | Three to 500 characters, stored in the audit entry. Required. |
dryRun | true counts what the call would clear and changes nothing. |
limit | The 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
403on the report or the repair. The report needsadmin:system:read, the repairadmin:system:write. A dry run needs the write permission too, because it is aPOST.400on the repair.kindsis empty or names a kind that does not exist,reasonis shorter than three characters, orlimitis outside 1 to 10000.remainingis above0after a call. The call hit itslimit. 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.
Ticket hygiene
Two detectors for tickets that need a human nudge: work that landed but was never handed to review, and open tickets that drifted from their original scope.
Own and answer feedback reports
On a workspace that receives feedback reports from other orboto instances: who owns each report, the unattended alert, the one-step public reply and how a report follows its fix.