oc Outlier Crosstab Narrow tools for Jira
Outlieran product Install

Privacy policy — Outlier

This page is written from the architecture, not from a template. The source is not public, but two things are checkable without it. The method page prints the whole arithmetic. The app's Forge manifest is shown to you by Atlassian before you install, and it either declares an external host or does not.

Last reviewed: 2026-11-01.

This is version 5. The app has not changed since version 4; this site has: it now has a support form, and this edition says what the form sends and the check that runs before it does: The support form. See Changes.

At a glance

Every record the app keeps, and how long it lives, is in one table under How long it is kept. Uninstalling the app deletes its rows, with one exception for a board too large to finish.

Contact: support@chmut.com.

Who this covers

Outlier is a Jira dashboard gadget built on Atlassian Forge. It is distributed through the Atlassian Marketplace. It runs inside Atlassian's infrastructure. It has no servers, no database and no backend of its own.

WhoTheir part
Your organisationController for the ticket data the app reads. It is your Jira site, your data and your agreement with Atlassian
OutlierProcessor for that data, acting on your instruction. Its code runs inside your site, and what it reads does not leave it
The vendor, Oleksandr Chmut, trading as ocController for anything sent to the support address, for the contact details on a paid licence, and for visits to this site
Atlassian, Apple, CloudflareAround the app rather than in it. See Sub-processors

The vendor is Oleksandr Chmut, a sole trader registered in Ukraine, trading as oc, and the vendor on the Marketplace listing. For a support message that covers the message, the address it came from, and whatever the sender chose to put in it.

This site is covered too. outlier.chmut.com is a set of pages — this policy, the method, the security page, pricing and the rest — and Cloudflare serves every one of them. A request that fetched one is a request to a server somewhere, and the vendor is the controller for that too. Cookies and tracking says exactly what is collected.

The basis for processing

DataBasisWhat that interest is, and no wider
The ticket dataYour organisation's, not ours: whatever lawful basis you rely on to run your Jira siteThe app processes it under your instruction. We do not choose the purpose and cannot change it
A support messageLegitimate interestAnswering a question somebody chose to ask, and keeping the exchange long enough to answer follow-up about the same thing
The contact details on a paid licenceLegitimate interest. Atlassian passes them to the vendor; the person did not give them to usA vendor has to be able to reach the organisation that bought the thing, about the thing it bought
A visit to this siteLegitimate interestKnowing that a documentation site is being read and is not broken. Served by counts, and by nothing that identifies a reader
The check on the support formLegitimate interestKeeping a form on a static site from being used by scripts to send mail. It runs only on the support page, and only to decide whether a message is sent

A support message is not used for anything else. There is no marketing, no profiling, no list, and nowhere it is sent. Sub-processors says what is done with the licence contacts.

What the app reads

From Jira, as the app or as the person looking, depending on the request.

WhatWhenWhy
Three fields per ticket: created, updated, statusEach collection pass, for the boards being watched. And each time a card is drawn or a rule asks for it, for up to 300 tickets in flight, as that personWhen the ticket moved
The ticket's changelogWith the three fields, on the same occasionsThe app keeps when a status changed and the name of the field that changed
A board's configuration and its saved filterEach collection pass. The configuration also each time a card is drawn or a rule asks for it, as that personTo work out which tickets are in scope, and whether that person can see the board at all
labels, over up to 500 ticketsOnly while somebody is configuring a gadgetSo the form can offer the labels that board actually uses
issuetype, over the same ticketsOnly while somebody is configuring a gadgetSo the form can offer the kinds of work that board actually carries
The site's status names, one list for the whole JiraEach collection pass with a board dueA column has to be called something on the card, and it has to be classified. A status the app has never seen is a column it cannot judge
The boards you can open, by nameSetting up a gadget, and drawing the cardSo a gadget can be pointed at one, and so the card can say which board it is watching
Which tickets each board still holds, by id and nothing elseOnly after a board is forgotten, by the hourly sweep, as the app, until every ticket row has been checkedTo delete the rows of tickets no board left on the site holds. Per ticket
The board's open sprintsOnly if a gadget was configured to frame the card by them, each time that card is drawn or a rule asks for itThe card prints a sprint's name and how much of the work in flight sits outside it. What is stored of a sprint is its id

Collection runs on a schedule of the app's own, not when somebody looks at a dashboard. So processing happens without a person present, for the boards that are being watched, and only those.

The card reads Jira too. Each time it is drawn, or an Automation rule asks for its morning, it reads the work in flight again, as the person looking or the person whose rule it is. That read writes no row per ticket. What it keeps is the two rows per card, when the board was last looked at and whether it still means what the gadget was set up against in the board's rows, and the day it rendered in the installation's row.

What is used, and what is dropped

Stated precisely, because it is the claim a security review will test.

The honest sentence is "the app reads only how tickets moved", not "content is never sent to us". The second would be easy to disprove with a proxy. The first survives being checked.

What it asks for

The list an administrator approves at installation. That screen is the first thing a security review looks at, so this page is not silent about it.

read:board-scope:jira-software
read:board-scope.admin:jira-software
read:project:jira
read:sprint:jira-software
read:filter:jira
read:jql:jira
read:status:jira
read:project-role:jira
read:application-role:jira
read:avatar:jira
storage:app
read:issue-details:jira
read:issue-meta:jira
read:issue.changelog:jira
read:issue-type-hierarchy:jira
read:field:jira
read:field.default-value:jira
read:field.option:jira
read:field-configuration:jira
read:audit-log:jira
read:user:jira
read:group:jira

Most of the list is one endpoint's price.

ScopesWhy they are there
Nine: read:application-role:jira, read:avatar:jira, read:filter:jira, read:group:jira, read:issue-type-hierarchy:jira, read:jql:jira, read:project-role:jira, read:user:jira, read:project:jiraJira requires them as a set to call the endpoint that returns a saved filter. The app reads one field from that response, jql. It never calls a user, a group or an avatar endpoint. It cannot: there is no code that does
Five: read:audit-log:jira, read:avatar:jira, read:field-configuration:jira, read:issue-details:jira, read:issue-meta:jiraJira requires them as a set to call the search that finds the work in flight. Jira attaches read:audit-log:jira to that call. The app reads no audit log
read:issue.changelog:jira, with read:avatar and read:issue-metaThe changelog endpoint, for a ticket whose history is too long to arrive with the search. It is the one call that needs read:issue.changelog

The first two rows are there because they look wrong on the screen: an avatar and an audit log, in a gadget that reads three fields. Printing the list makes the shape of the request checkable against Jira's own documentation rather than against our word.

What the app stores

In Atlassian Forge storage, on your own site. Nothing anywhere else.

Per ticket

One row per ticket: when it entered and left each status, and whether it is resolved. A resolved ticket's row lives 400 days from the day it closed; an unresolved one has no age limit while a board on the site holds it. When a board is forgotten, the rows of its tickets that no other board holds go with it.

The ticket row and the index of when each is due, in full
  • In it: its id and its key, the ACME-123 you see on the card and click through to. When it entered and left each status. Whether it is resolved.
  • Not in it: no summary, no description, no comments, no assignee, no story points.
  • Written: by collection, and by the one-off walk through a board's history, for the boards being watched.
  • Why: it is what the changelog is reduced to. Everything else in it is discarded.
  • Lives: resolved, 400 days, counted from the day it closed, or until no board on the site holds it, if that is sooner. Unresolved, with no age limit, for as long as a board on the site holds it.
  • Deleted by: the 400 days, for a resolved ticket. A board that stops being read: once it is forgotten, the hourly sweep asks Jira which of the site's tickets the boards left still hold, a hundred at a time, and deletes the rows no board holds. The last board on the site to be forgotten: then every ticket's row goes, up to five hundred an hour. Uninstalling the app.

An unresolved ticket has no age limit on purpose. A ticket that has sat untouched for a year is the most interesting thing this product can find. Expiring it would make a board look calm exactly where it is not. The boards bound it, not the clock.

A ticket's row belongs to the site, not to a board. Two boards that hold the same ticket share one row, so forgetting a board cannot delete its tickets on the spot: another board may hold the same one. Instead the hourly sweep checks every ticket row against the queries of the boards left, asking Jira as the app, and deletes the rows none of them holds. A board nobody has opened for thirty days still counts until it is forgotten in its turn. A request that fails deletes nothing, and the check is asked again an hour later. The check keeps no list of a forgotten board's tickets: what it keeps is where it has got to.

When each resolved ticket is due:

  • In it: for each week, the ids of the resolved tickets whose 400 days end in it, and the moment each one does.
  • Not in it: no ticket key and no status.
  • Why: so deletion reads the week that is due, not every ticket row on the site.
  • Written: by collection and the walk, as they write a resolved ticket.
  • Lives: until its week comes due and the rows it names are deleted.
  • Deleted by: the hourly sweep, as its tickets go. The last board on the site that stops being read. Uninstalling the app.

Per column

Counts and durations per column, derived from the ticket rows, with the status names and no ticket key. Kept while the board is read; a month of finished stays older than 360 days goes sooner.

The column rows and the copy a card reads, in full
  • In it: counts and durations derived from the per-ticket rows. The status names, because a column has to be called something on the card.
  • Not in it: no ticket key and no ticket id. A finished stay is kept as when it ended and how many working days it took, filed by the month it ended in.
  • Written: by collection, for the boards being watched.
  • Lives: while the board is read. A month of finished stays older than the widest window a norm reads, 360 days, is deleted before then.
  • Deleted by: a board that stops being read. Uninstalling the app.

One more row per board copies the norms and how far each column still learning has got, so a card reads one row rather than several. Collection writes it when either changes. Nothing in it is not already in the rows above, and it lives and goes with them.

Per site

The list of status ids and names your Jira defines, the same list any user of your site can see. Kept until the app is uninstalled.

The site's status list, in full
  • In it: the list of status ids and names your Jira defines. It is the same list any user of your site can see. It is read from Jira, not derived from anything.
  • Why: so a card need not ask Jira for it every time somebody opens a dashboard.
  • Written: by collection, and only when Jira's list differs from the one kept.
  • Lives: until the app is uninstalled.

Per board

Three rows for each board a gadget points at: its settings, how it was last read, and how far the walk through its history has got. All three live while the board is read. The labels a gadget is narrowed by are kept, and they are text a person wrote; the query is not. The record of the last read can name a ticket key, when the read failed on one.

The three board rows, the labels and the query, in full

Its settings:

  • In it: the board and filter ids. The timezone, the working week and the working hours. The labels and kinds of work a gadget is narrowed by. The columns a team named as review. The ids of the gadgets that point at it, and the days it was first and last looked at.
  • Not in it: the query. It is composed from the board id, the filter id and the labels on the pass that needs it, and kept nowhere. Nor a sprint's name: a card framed by sprints prints it, and keeps only the sprint's id, in what the card last said.
  • Written: each time a card is drawn or a rule asks for it, and by collection when the board's saved filter changes.
  • Lives: while the board is read.
  • Deleted by: a board that stops being read. Uninstalling the app.

How it was last read:

  • In it: when collection last read the board, how many tickets it saw and wrote, and whether that read was complete. Whether reading the board is failing, since when, and how many times in a row. The message of the last failure: it names the path Jira was asked, with the query removed and the ids left in, so a board id, a filter id, or the key of a ticket whose history was being paged. When the board's own filter last moved.
  • Kept of the query: a short hash of it. It answers the one question it was kept for: whether the board still means what this gadget was set up against. It cannot be read back.
  • Why: so a card can say how fresh it is, and say a board has gone dark rather than look calm.
  • Written: by each collection pass, at the end of the walk through the board's history, and when a card is drawn.
  • Lives: while the board is read. Each write replaces the last.
  • Deleted by: a board that stops being read. Uninstalling the app.

The walk through its history:

  • In it: how far the one-off walk through the board's history has got: the moment it reached, and the ids of the tickets at that edge. Counts of what it has read, and the ids of the columns it found. While it runs, each unfinished stay it saw, as a column, its role and a number of working days. Those are emptied when the walk finishes.
  • Not in it: no ticket key.
  • Why: a walk takes many invocations, and each has to know where the last one stopped.
  • Written: by the walk, a chunk at a time, when a board is first set up or needs reading again.
  • Lives: while the board is read.
  • Deleted by: a board that stops being read. Uninstalling the app.

The labels carry text a person wrote, and they are named here rather than buried. They are whatever your team calls things. They cannot be stripped: a query narrowed by different words is a query over a different population.

The query is not kept because it can name a person. It is composed from the board's own saved filter. A saved filter quite ordinarily reads assignee in (...) or reporter = "someone@example.com", so an address belonging to a person could sit in a stored row. Nobody types a query into this app. Every clause of it comes from Jira.

Per card

Two rows for each card, both written when the card is drawn or a rule asks for it: what the card last said, and its last 60 mornings as counts per age. Both live while the board is read.

The two card rows, in full

Neither is written by collection.

What the card last said:

  • In it: the ticket keys it named and the columns it showed. For each column, how much work was in flight and how much was past the usual wait, and the usual wait a named column was measured against. Whether the card spoke on each of the last ten mornings: the row of squares. The ids of the sprints it was framed by. When it was drawn, and which version of the method its reader was last told about. The same again for the morning before.
  • Why: so the next morning can tell what is new from what was already said, and tick off only what a reader was shown.
  • Written: when a card is drawn or a rule asks for it, on a working morning, and only when it has something to add.
  • Lives: while the board is read. Each write replaces the last.
  • Deleted by: a board that stops being read. Uninstalling the app.

The last sixty mornings, as ages:

  • In it: for each morning, for each column work can wait in, how many tickets had stood there for how many whole working days. A count per age.
  • Not in it: no ticket key and no ticket id.
  • Why: it is the sample for checking whether the setting chosen at install is still the one this board would choose.
  • Written: when a card is drawn or a rule asks for it, on a working morning whose read was current and complete.
  • Lives: the last 60 mornings. An older one is dropped as a new one is added.
  • Deleted by: a board that stops being read. Uninstalling the app.

Per installation

Three rows, each one for the whole site and none about a board: the days a card rendered in its first thirty days here, where deletion last stopped, and how far the check of ticket rows after a forgotten board has got.

The three installation rows, in full

The first thirty days:

  • In it: the day a card first rendered here, and the day offsets on which one rendered again, for thirty days and never after. A date and a list of whole numbers.
  • Not in it: no identifier, and nothing that could become one.
  • Why: it answers whether the app was used rather than merely installed.
  • Written: when a card renders, in those first thirty days.
  • Lives: until the app is uninstalled.
  • Deleted by: uninstalling the app, with everything else.

Where deletion last stopped:

  • In it: a position in the app's own storage, the last week of due tickets cleared, and when it ran.
  • Why: each pass deletes a capped number of rows, so the next one has to know where the last one stopped.
  • Written: by the hourly sweep that deletes expired rows.
  • Lives: until the app is uninstalled.
  • Deleted by: uninstalling the app, with everything else.

How far the check of ticket rows has got:

  • In it: whether a check is owed, a position in the app's own storage, and how many boards have been forgotten since it began.
  • Not in it: no ticket id, no board id. The point of it is that no list of a forgotten board's tickets stays behind.
  • Why: the check after a board is forgotten runs over many hourly passes, so each has to know where the last one stopped.
  • Written: when a board is forgotten, and by each sweep that checks a page of rows.
  • Lives: until every ticket row has been checked against the boards left.
  • Deleted by: the sweep that finishes the check. Uninstalling the app.

No per-person field

No per-person field, anywhere. Not assignee, not reporter, not the account id of whoever moved a ticket. This is enforced by a check that refuses such a field at the boundary, not by intention.

That is a claim about fields. A check that refuses a column called assignee cannot read inside a string somebody else wrote. The query is not kept, so there is no such string for a name to be buried in. What is kept about a board is identifiers and the labels your team chose.

When a board stops being read

What happensWhat follows
Nothing reads a board: no dashboard opened, no rule asking for its morningCollection stops after 30 days, and everything kept for that board is deleted after 90
A subscription lapsesCollection stops with it. What was already derived falls under the same two rules: nothing is collected after that day, and everything kept for the board is deleted after 90
The app is uninstalledThe app deletes its own rows at once, rather than leaving them to Forge's 28-day retention window

The per-ticket rows are not the board's. A ticket two boards hold has one row, so the rows of a forgotten board's tickets go once the hourly sweep finds that no board left on the site holds them. Per ticket says how.

A board too large to delete in the time the platform allows is reported, not silently half-deleted. The run records what it could not finish. Forge's own 28-day window then removes whatever is left. So the outcome is the platform's default, not an indefinite remainder.

It is the one path here where deletion is not immediate. Write to the address above if you need it confirmed for a particular site.

What the app never does

Ask Rovo why

One control is the exception to the second of those.

How long it is kept

Each record's lifetime is set out in its own section above. This table is the index.

RecordLives until
Per ticket, resolved400 days after it closed, or sooner when no board on the site holds it
Per ticket, unresolvedNo age limit while a board on the site holds it
Per ticket, when each resolved one is dueIts week comes due and the rows it names are deleted
Per columnThe board stops being read; a month of finished stays older than 360 days, sooner
Per column, the copy a card readsThe board stops being read; each write replaces the last
Per siteThe app is uninstalled
Per board, its settingsThe board stops being read
Per board, how it was last readThe board stops being read; each write replaces the last
Per board, the walk through its historyThe board stops being read
Per card, what the card last saidThe board stops being read; each write replaces the last
Per card, the last sixty morningsThe newest 60 mornings are kept; all of them go when the board stops being read
Per installation, the first thirty daysThe app is uninstalled
Per installation, where deletion last stoppedThe app is uninstalled
Per installation, how far the check of ticket rows has gotThe check finishes
Anything kept for a board nobody readsCollection stops after 30 days; deleted after 90. Its tickets' rows go once no board left holds them
Everything the app stores, on uninstallDeleted at once; a board too large to finish, by Forge's own 28-day window
A message sent through the support form30 days in the form's database, or sooner on request. Answering it starts support correspondence
Support correspondence24 months, or sooner on request
The contact details on a paid licenceWhile the licence is live, and for a year after it ends

Cookies and tracking

The gadget: none. No cookies, no tracking pixel, no analytics script, and nothing loaded from any host.

This site is not the gadget. It loads one analytics script on every page and, on the support page alone, Cloudflare's check that a person is sending the form. All of it is listed here.

Every page runs one script of our own — 256 lines, comments included.

What the script does, line by line
  • measures the header, so an anchored heading lands below it;
  • makes a wide table or code block reachable from a keyboard;
  • on a page with a contents list, marks which section you are reading;
  • opens folded sections while the page is printing, so nothing is hidden on paper;
  • keeps a screenshot in the theme you are reading in when you change that theme, and opens one over the page when you ask for a closer look;
  • makes a Copy button copy, by reading text already in the page and calling the clipboard, with a fallback for browsers that refuse it.

The support form

The support page has a form. It is another way to write to support@chmut.com, not a different thing.

Where it is held

In your own Atlassian site's Forge storage, in the realm your site is pinned to.

Residency covers where data is stored, not where it runs. Atlassian's documentation notes that Forge "may sometimes execute an app's invocation from a location other than the location where the host Atlassian app is". Where a given invocation runs is Atlassian's to decide. In every case it is Atlassian's own infrastructure. Nothing in this app reaches any other host.

Sub-processors

None for the app. Nothing it reads leaves your Atlassian site, so there is no third party to name: no analytics, no vendor backend, no model, no host of ours anywhere in the path.

Three exist around it. "None" without them would be true only of the half a reader is not asking about.

WhoWhat it does
AppleHosts the support mailbox, so a message sent to the address above, and the answer to one sent through the support form, is stored on iCloud. That is the correspondence the vendor controls, not anything from your board
AtlassianPasses the vendor the contact details attached to a paid licence: a technical contact and a billing contact, which your organisation gave them rather than us. The vendor is the controller for those, uses them only to answer a question about the licence, and keeps them while it is live and for a year after it ends
CloudflareServes this site, sees each request that fetched a page, and receives what the analytics script in Cookies and tracking reports. On the support page it runs the check named there, and keeps a message sent through the form for 30 days, in the European Union

The licence contacts are never joined to anything read from a board. There is nothing to join them to: no record the app stores names a person.

None of the three touches Jira data. All three are listed so the answer stays true when somebody checks it, not only when it was written.

Your rights

DataWhere the request goes
The ticket dataYour Jira administrator first. They control the site, and we do not hold a copy to search
A support messageThe vendor, who is the controller. Write to the address above to ask what is held, to have it corrected, or to have it deleted

The ticket data stays inside your own Atlassian site. Your agreement with Atlassian covers the platform holding it. It does not cover this app, which is a third party to it. Uninstalling the app deletes what it stored, with the one exception: a board too large to finish inside the time the platform allows leaves a remainder, which the platform's own 28-day window then removes.

For a support message:

If you are in the EU or the UK and believe either has been handled wrongly, you can complain to your national supervisory authority. You are not required to raise it with us first, though we would rather you did.

Changes

Version 5 adds the support form: what it sends, the check that runs before it does, and the 30 days Cloudflare keeps it for. The app has not changed since version 4, and neither has what is kept from support correspondence or for how long.

It takes effect at once rather than after thirty days' notice. It covers only a message you choose to send through the form, which did not exist before, so nothing already collected goes anywhere new.

Version 4 is the first edition laid out for reference. It names records the earlier editions left out, and corrects one place where they were wrong, both set out below.

The app changed once since version 3. Every edition said that what was kept for a board nobody reads is deleted after 90 days. Until this edition took effect, the app kept an open ticket's row for as long as any other board on the site was read, because two boards that hold a ticket share its row. It now checks each such row against the boards left and deletes the ones none of them holds: Per ticket. That was a fault in the app, not in the editions, and the fix makes it keep less, not more. So it takes effect without the thirty days' notice below, which is owed for keeping more or for longer.

Nine things earlier editions left out, version by version

Nine times an edition has been narrower than the product rather than wrong about it. Each time the categories of data were unchanged.

  • Version 1 described the Rovo question as the card's text and nothing more.
  • Version 2 listed what is stored per ticket, per column, per site and per board without naming the row kept for the installation itself.
  • Version 3 did not say that the vendor can read the app's logs, which carry counts, durations and field names, name a card or its board in ten places, and log a failed request with the ids in its path. At a glance now does.
  • Version 3 did not say that a card reads the work in flight each time it is drawn or a rule asks for it, as that person. What the app reads now does.
  • Version 3 did not name the two rows kept for each card: what the card last said, and its last sixty mornings as ages. Per card now does.
  • Version 3 did not name the row kept for how each board was last read, whose last failure can name a ticket key. Per board now does.
  • Version 3 did not name the row kept for the walk through a board's history, which holds the ids of the tickets at its edge. Per board now does.
  • Version 3 did not name the index of when each resolved ticket is due, which holds ticket ids. Per ticket now does.
  • Version 3 did not name the row where deletion last stopped, and called the installation's row the only one not about a board. Per installation now does.

An earlier edition also named the board's query among the records carrying text a person wrote. It is no longer stored, and Per board says what is kept instead.

The one place the first three editions were wrong

The first three editions listed the open sprint's name among what is kept per board. A card prints it and stores only the sprint's id.

Every earlier edition is published below with its own digest. All of them change together, so a reader who kept a copy can tell whether they are holding the current one.

ChangeWhat happens
Alters what is collected, what is kept, how long, or who else sees itAnnounced on the listing thirty days before it takes effect. The previous version stays available on request
Anything smaller: a clearer sentence, a corrected figureMoves the review date and not the version

The method has been versioned from the beginning. It would be strange to hold the arithmetic to a rule the policy about your data was not held to.

This edition