Issue Tracking
Issue Tracking is an issue tracker with no user interface, operated by Turn Zero Cloud. People, agents, and programs file reports of what does not work or what is missing, rate their experience, and work each report through to an outcome. Every action is a call made from the caller's own tools. The package states the service's contract and includes a client and a test double for an application's backend and its tests.
Use it when
Use Issue Tracking when an application needs one queue for bug reports, feedback, the checks its test runs record, and ratings that agents file and read as readily as people do. A report records its origin, its evidence, the builds it saw, and the person, agent, or program that filed it. Declare the issue_tracking service in the manifest, and the platform creates one space, the set of records one owner keeps, shared by both of the application's environments. Your backend reaches its space through the platform's egress gateway, which presents the space's credential for it.
Use it for an application's own end users: the backend files each user's report under the user's identity and reads the queue through the client. When your backend files a report for one of your users, send that user only their own report and the likely matches. Do not send the call's whole answer: the issue in it holds other reporters' words. Your tool reads the whole space, files into it, rates in it, and settles its reports through the platform's feedback actions, naming the space.
Do not use it for work that needs boards or sprints, for notifications, or for fields your application defines. The service sends nothing, and an issue moves only through the calls and the passes, along one fixed set of allowed moves between its states. The passes are the AI runs that match new reports to known issues. The hosted service runs them every hour. They read an application's space only where the space's configuration turns them on.
You turn them on with the contract's configure call, sent through the platform's relay under an issues token at the owner grant level. The relay is part of Turn Zero Blueprint. The passes run on the company's AI allowance while Blueprint is free, never on your application's. An application that kept a space for each environment has no such route.
What it provides
- Spaces. A space contains one owner's records and is identified by a universally unique identifier (UUID). Each of its tokens has one of four permission levels, one token per holder, a holder being the person, agent, or program that presents it. From lowest to highest the levels are
file,work,record, andoperator. Every call made with a token reads and writes that space alone. - Attributed writes. Every write names the person, agent, or program that acted. A service that relays its users' actions names each user, never itself, and a relayed read returns that user's own reports and nothing of anyone else's.
- Issues and reports. An issue is the one curated record of a problem. It has a short identifier such as
#12, a kind, a state and an outcome, a priority, a component, who is working it, and the counts of its reports. A report is one filing in the filer's words, never edited, linked to the issue with the reason for the link. - Matching at filing. The service fingerprints the evidence the platform stamped on a report and links a repeat to the known issue without any AI, returns up to three likely matches, and lets the filer confirm one. A report whose origin is not
workspaceorplatformis never linked by its evidence, or confirmed, onto a restricted issue. A restricted issue is one marked as a security concern, which the service hides from other reporters. A follow-up under the filer's own problem key, the filer's own name for the problem, is a new report on the same issue. - Landings, builds, and checks. A landing is the record of the commit that fixed an issue, with its regression test and proof. A build records a version of a release line, the sequence of versions an environment runs, written by the deploy. A check records each run of a monitored job under a stable key, passing and failing alike, and an issue opens from it only once the failures repeat.
- Verification by exposure. A fixed issue closes verified once live builds that contain the fix have exercised it a set number of times, with no report of the same point since. A fixed issue also stays fixed while a
withheldentry in its history names a report filed on a build that contains the fix. The service writes awithheldentry when it keeps a report from a restricted issue. An issue nobody can explain waits, and the next report reopens it. - Passes. Two AI passes propose and check duplicates, splits, one issue divided into two, and themes, several issues grouped under one. A merge is applied where they agree and the evidence on at least one side was stamped by the platform or confirmed against its records. Nobody approves duplicates by hand, and a pass never edits a report. The triage pass also judges each new issue to give it a priority.
- Ratings. The human series contains the standard Net Promoter Score question, scored 0 to 10. The agent series contains an agent's rating of how difficult its task was, scored 1 to 5. The two series are never combined.
- Asks. An ask records a request for a person's rating, and a throttle limits how often one account is asked.
- Reads and erasure. List, search, and export read a space. Erasure removes a person's identity from every record and keeps what they wrote.
The package includes two clients in one module: createIssueTrackingClientV2 for the current wire, the calls and shapes of version 0.3.0, and createIssueTrackingClient for the previous one, version 0.2.0. It also includes a test double, createIssueTrackingDouble, which responds to both wires' calls in memory for your tests. All run in the backend process. The service that handles the calls runs as one of the platform's own applications. Its source is an example customers can read, and its handlers pass the same list of test cases the contract states, which the double passes too.
A manifest that declares { "kind": "issue_tracking" } gets one space for both of the application's environments. The space is created when the manifest is first submitted. Your deployments in each environment the application has file into it and read from it, and so do your local runs. The submission result's issue_tracking list gives the space, and never a token. An application that has a space for each environment keeps both until it is deleted. Its first promote, or its first production deploy where it has one environment, creates its production space.
Both environments share one space, so the platform keeps them apart in three ways. First, the gateway stamps every report your backend files. It sets evidence.environment to the environment the call came from and evidence.version to the version that environment serves. It replaces whatever the report said, so each report records the build it was seen in. A call from your own machine, sent through the platform's public address under the development credential, is stamped with the version local.
Second, the gateway puts development: in front of every person, agent, or program id that a development call names. It does so in what the call files and in the reads it filters by actor. A development tester therefore never appears as one of your production users.
From development, a read must name the actor it reads for, so it never returns a production user's reports. A list, list_signals, or export without actor is refused invalid_request, and so are read and search, which take no actor. A development write that names another issue by its id, such as a report's relations, is refused the same, so a development report never attaches to a production issue. An id that already starts with development: is refused invalid_request from either environment. So is an id that would exceed the 200 characters an id may have once the prefix is added.
Third, a deployment reaches the space at the report grant level. Grant levels are the platform's own sets of calls, separate from the package's four permission levels on a token. At report, a deployment files reports and reads the space. The gateway refuses issue_tracking_call_refused every call that changes an issue: update, comment, relate, settle, signal, export, erase_actor, open_ask, and close_ask.
The contribute grant level adds the working calls, and owner reaches every call. No deployment has owner, so no deployment can settle or export the space. The package's contract still lists those calls as allowed through the gateway. Its table does not hold the grant levels, so the gateway's refusal is the one that applies.
A report can also name the commit it was seen in. The call that starts a deploy may name commit, the commit the code was built from, 7 to 64 lower-case hexadecimal characters. The version keeps it, and a promote or rollback of that version keeps it too. After each deploy, promote, and rollback, the platform writes a build record into the application's own space naming the version, the environment, and that commit. Writing it never delays a deploy. A malformed commit, or one named on the call that only prepares an upload, is refused invalid_request.
An application that keeps a space for each environment gets no build record yet.
A manifest can instead name a space of your account's own for the application to use, one you created with create_issue_space. It names one space for both environments, or { "development": ..., "production": ... } naming one for each. It may add a level of report or contribute, report where it names none.
The platform binds the application to that space and creates no space. It adds one release line to the space for the application, named app- followed by the application's id, and writes the application's build records on that line. It reads the space's lines first and sends them back with its own line added, so your lines and components stay as they are. If a line of that name is already there, the platform uses it as it is and changes nothing of it.
A configure naming lines replaces the whole list, so keep the application's line in it. If your list leaves the line out, the platform adds it again at the application's next build. If your own configure of lines lands at the same moment as the platform's, one can replace the other. The platform restores its own line at the next build, so read the lines again if a change of yours is missing.
A space holds at most 50 release lines. A line stays after its application is deleted or moves to another space, and it still counts toward the 50 until you remove it with configure. When the space already holds 50 release lines, the platform binds the application with no line and writes no build record for it until the space has room. The receipt's entry for the space says so. Remove a departed application's line to make room, and the next submission, deploy, or promote adds the application's line.
The receipt shows the space with the scope account and the outcome bound. The gateway keeps the environments apart in it by the same three rules. The binding works while your account has Turn Zero Blueprint. Once the account no longer has it, the gateway refuses each call with blueprint_required.
A later submission naming another space moves the binding, and the space it leaves keeps its records. A submission naming no space keeps the current binding. A space your account does not own is refused space_not_owned. So is an application's own space, which binds only through its own application's manifest.
A level named without a space is refused invalid_request. A level other than report or contribute, owner among them, is refused level_invalid. An application that has a space for each environment cannot name a space. Neither can an application that already has its own space, which both environments use until the application is deleted.
The backend reaches its space through the egress gateway's platform upstream issue-tracking, under the application's own platform credential, TURNZERO_CLOUD_TOKEN. Compose the previous wire's client with no option, createIssueTrackingClient(transport), over a transport, a function you write that sends each call and returns the response, rooted at the gateway's issue-tracking path, ISSUE_TRACKING_GATEWAY_PATH_PREFIX (/egress/v0/issue-tracking). The gateway forwards the previous wire's calls and none of the current wire's, so a hosted backend keeps createIssueTrackingClient. The path sits on TURNZERO_CLOUD_GATEWAY_URL where the deploy sets it, and on TURNZERO_CLOUD_API where it does not, as the storage, egress, and logging calls are sent.
The gateway finds the space from the calling application and environment. It presents the space's token to the service and never returns it. The deploy injects no setting of the service. A local run reaches no gateway; run the double in its place.
The gateway refuses, before a call reaches the service, a call path it does not allow, issue_tracking_call_refused, and a credential that names no application, issue_tracking_application_required. An environment with no space is refused issue_tracking_not_provisioned, and a space whose token cannot be read issue_tracking_unavailable. The client tells each gateway refusal apart from the service's own response by the gateway's marker header. The external API guide lists every refusal of the platform's upstreams.
The platform keeps the token in custody under a platform-minted name, and no application and no author has it. A manifest revision that drops the kind changes nothing: the space and its records remain. The space's records are the application's own content. The platform reads them only through the feedback actions your tool makes naming the space, the export, the deletion of your account, and the daily copy described below.
Deleting the application deletes its own space at the service with every record in it. It ends a binding to a space of your account's own, and that space keeps its records. Deleting your account deletes the spaces of all its applications and the spaces of the account's own.
Deleting the development environment deletes nothing of a space both environments share, and it ends a development binding to a space of your account's own. For an application that kept a space for each environment, it deletes the development space. The issue service's database backups keep a deleted space's records for 35 days until they expire, and no backup is restored to recover them.
The platform also reads each space your account holds, and each application's own space, once a day, and keeps a copy in storage it holds for your account. A copy is kept 35 days, and the newest is kept until a newer one is written. When a space is deleted, its copies are deleted with it, and a copy being written at that moment is deleted at the next daily run. The spaces of an application that kept one for each environment are not copied, and no action reads or restores a copy yet.
Your tool uses the same space through submit_feedback, read_feedback, settle_feedback, and rate_experience, each naming the space in space. A token scoped to the application is refused them, since the backend reaches the space through the gateway. The Feedback and ratings guide gives the forms of each call.
Availability
Version 0.7.1 is the package's current version, and list_library reports the version the platform currently publishes. Version 0.4.0 changes how an issue's priority is set and read, and when a likely match carries a title. Moving to version 0.4.0 says what a backend does. It keeps the previous wire's calls, so a backend on version 0.2.0 changes no call at that version.
Version 0.6.0 removes the personal option from a report, and a backend that sent the option on either wire stops sending it. Moving to version 0.6.0 says what a backend changes.
Version 0.7.0 limits what a token below the work permission level can claim on a filing and read back. A hosted backend on version 0.2.0 changes no call at that version. Moving to version 0.7.0 says which answer it reads differently and what a token below the operator level changes. The contract will say when the previous wire ends, at least one minor version before the version that ends it.
Version 0.7.1 changes no call, member, or answer. It says a file token belongs in your backend, because its holder names the reporter each call is for. If you gave one to an end user, a device, or a client, revoke it with revoke and mint a new one for your backend with mint. The contract's section "Migration to 0.7.1" says what changed.
The entry includes the testing subpath from version 0.1.0. The entry contains the package's product statement, its detailed contract, its integration guide, and the compiled clients and double as the npm package @turnzero/issue_tracking.
Turn Zero Cloud runs the operated service and creates one space for every application that declares the kind, shared by its environments. An application whose spaces were created one per environment keeps them. The platform records each provisioning step as a usage event of the application. The calls and the records are counted in no plan quantity, and no charge attaches to a space, a call, or a record.
The calls the gateway forwards and the spaces' stored bytes count against none of the plan's quantities. Plan and usage states the measures a plan includes. The passes over an application's space run where its configuration turns them on, on the company's AI allowance while Turn Zero Blueprint is free.
The service also checks each forwarded call against its own per-minute rate limit for the space. A call past it is refused rate_limited by the service.
Moving to version 0.4.0
Version 0.4.0 changes how an issue's priority is set and read. The package's client and test double carry the change once list_library reports that version. The hosted service has the change.
A backend that uses the previous wire, createIssueTrackingClient, changes no call. That wire still stores and returns priorities from 1 to 4. Its results differ from the previous service's, as the rest of this section says. Three concern priority, order, and export:
- A list in
scoreorder follows the new rank, the service's score for ordering issues, which counts an issue's reports where it counted its distinct reporters. - An issue's priority can move when a report's reference check is confirmed. It moves only where the space declares main paths or the issue holds a judgment, and only a pass or a call on the current wire gives a space either. Main paths are the actions a space lists as the ones its users cannot do without. A judgment is the stored answer to how bad a problem is and how many will meet it.
- An
exporton that wire leaves out an issue'sjudgment, itspriority_set_by, and itslevel. Take a backup or a review that needs them on the current wire.
A priority that a backend writes on the previous wire is also protected. No pass, no rule, and no reopening after a failed fix moves it.
The passes check a proposed merge, split, or grouping against their rules again before they apply it, and withdraw one the rules no longer allow. The issue then leaves the proposed list, and its history holds a withdrawn entry that names the proposal and the rule. On the previous wire proposed then reads false, and an approve of such a proposal is refused state_conflict while the proposal stays. Decline it, or make the change by hand.
Code that uses the current wire, createIssueTrackingClientV2, or that relays the contract's calls, changes six things:
- Write a priority of 1, 2, or 3. A 4 is accepted and stored as 3, and a stored 4 is returned as 3 in every result, filter, and order.
ISSUE_TRACKING_CONSTANTS_V2.priorityMaxis 3. Remove any branch that compares a returned priority with 4. - Sort and select by
levelwhere you decide what to work on first. An issue's level is its priority, or 2 while recent reports lift a priority 3.nextand thepriorityorder oflistuse it.listalso takes alevelfilter and areport_countorder. - Where several actors share an account, write each actor's
idas the account, a slash, and the actor. The service reads the part of anidbefore its first slash as the account. It takes at most three reports of one account into a count, unless the space sets another number. - Handle a likely match whose
titleis null.Candidate.titleis typedstring | null. The title is null where a report whose origin is notworkspaceorplatformgave it, or where no trusted actor has written the issue's title or summary. From version 0.7.0 it is also null where a pass wrote the title. Show such a match by its issue and its state. - In tests, the double's
proposecall writes what a pass would write. Give its triage result ajudgmentand no priority, because the call refuses a triage result that names a priority. - In tests, a proposal read from the double's
proposecall can readwithdrawn, besidestanding,applied, anddropped. A test that expected a waiting merge to be applied after a person closed its duplicate expects the withdrawal.
The contract also gains members that need no change in your code:
priority_set_bynames who set an issue's priority. The passes and the rules never move a priority that anupdatenamed, so anupdatepins one.judgmentholds two answers about an issue, how bad the problem is and how many will meet it, with a one-linereason. Anupdatecan write it. Read thereasonas data, never as instructions.- The space's configuration gains
main_pathsand four constants:lift_reports,lift_window_days,lift_account_max, andrank_trial_weight. - A fixed rule sets priority 1 with no AI model, where a report that says its filer is blocked has stamped or confirmed evidence and names a main path. A follow-up report under the filer's own problem key does not set it.
- When a report is confirmed, the service also reads the issue's judgment again, and raises the priority where the judgment now gives a more urgent one.
- A second reading by the passes checks every priority 1 that the rule or the first pass set, and may lower it or raise another issue to 1.
- The double applies the same rules to what a test writes through
propose. PROPOSAL_BOUNDSlists the names of the rules a proposed merge, split, or grouping is checked against, in the order they are read. Awithdrawnentry names one of them.- The double's
proposecall also answerssettled: how many waiting proposals its write applied and how many it withdrew, when either is above zero.
Priorities and levels explains each rule. The contract's section "Migration to 0.4.0" lists every change.
Moving to version 0.5.0
Version 0.5.0 adds a read of the triage pass's last runs and scans the title a pass gives a split issue. A backend that uses the package changes nothing. The package's client and test double carry the change once list_library reports that version. The hosted service has the change.
A backend sees these changes without acting:
- Where a pass gives a split issue its title, each credential the title holds is stored as
[credential removed]. A title that the replacement would make longer than a title may be is cut to that length. - On the current wire,
read_spaceat theoperatorpermission level also returnspass_runs. It lists the triage pass's last finished runs over the space, newest first, at most eight, each with its counts for the space and the word it stopped under. Add the optional member to your own type of the answer if you read it. The double returns the list empty, and the previous wire has no such member. - In
pass_runs, a run's counts are null where the run did not reach the space or finished before the service served this version. Its stop word is null where it did not stop. A run that started before the space was created is not listed.
The contract's section "Migration to 0.5.0" lists every change.
Moving to version 0.6.0
Version 0.6.0 removes the personal option from a report. A report is no longer marked personal, and an erase deletes no report. The hosted service has the change, so it refuses a filing that still names the option. The package's client and test double carry the change once list_library reports that version.
A backend that uses the current wire, createIssueTrackingClientV2, or that relays the contract's calls, changes two things:
- Stop sending
personalonsubmit. Asubmitthat names it,trueorfalse, is refusedinvalid_request. The refusal says to file the report again without it. - Stop reading
personalon a report. No result carries it, and the client's report and request types no longer declare it.
A backend that uses the previous wire, createIssueTrackingClient, changes one thing. Stop sending excerpt: true, which is refused invalid_request. An excerpt that is false or left out is accepted, and an issue's excerpt is always false. The client types a filing's excerpt as false, so code that passes a boolean variable for it drops the member.
Leave out of a report anything you do not want kept. An erase keeps the text of every report.
A backend sees these changes without acting:
- An
erase, and anerase_actoron the previous wire, keeps every report and every issue. It replaces the person's identity on every record as before, anddeletedin its result is 0. - The packet that
nextgives a fixer holds the text of every report of the issue underuntrusted.
In tests, the double's propose call refuses a triage result that names a requirements identifier outside the form a pass may write. That form is a lower-case scope and an ID joined by a colon, each opening with a letter, where the ID holds a digit. Identifiers that your own code writes with submit and update keep the form they had.
The contract's section "Migration to 0.6.0" lists every change.
Moving to version 0.7.0
Version 0.7.0 limits what a token below the work permission level can claim when it files and what it reads back. A report is also linked to a restricted issue by its evidence only where it was filed with the origin workspace or platform. The package's client and test double carry the change once list_library reports that version. The hosted service has the change.
A hosted backend, which uses the previous wire through the gateway with createIssueTrackingClient, changes no call. Typed code that reads a member of the issue in an answer may still need an edit, described under "On either wire". From that version, one of that wire's own calls answers differently under any token. The changes every backend sees, listed further down, reach it too.
- A
submitwithrecovered: trueis refusedrecovery_unmatchedunless that same actor's filing under that key opened the issue and every report on the issue is that actor's. The issue checked is the first one the actor's latest report under that key is linked to. An issue another reporter has since joined is refused too.
Code that calls the service's own address with a token below the operator level changes more. On the previous wire, under such a token:
- Below
work, asubmitthat namespriority,labels,relations, ordocument, or files the kindfinding, is refusedgrant_requiredand files nothing. - Below
record, asubmitwithrecovered: trueis refusedgrant_required. The mark needs arecordtoken. - Below
operator, a repeat onto an issue settled asfixedis filed and linked, and the issue stays settled. The outcome readsrepeated. - With a
filetoken, the issue in the answer holds onlyid,status, anddisposition. It isnullwhere the reporter may not read the issue, a restricted one among them, and a repeat's outcome then readsrepeatedwhatever the issue's state. The outcome readsreopenedonly where the issue in the answer is the one that reopened.
On the current wire, createIssueTrackingClientV2:
- File the origin
workspaceorplatform, or namepriority,requirements, orlabels, with aworktoken or above. Afiletoken that names one is refusedgrant_requiredand files nothing.stampedevidence still needs arecordtoken. A retry with a lower token of a filing a higher token made is refusedgrant_requiredtoo. - With a
filetoken, readissuein the answers ofsubmitandfollowas the reporter's own view of the issue, ornull. A retry answers the same. Type it as an issue, a projected issue, ornull, as the client'sSubmitAnswerV2andFollowAnswerdo. - With a
filetoken, afollowwhoserepeat_ofnames a restricted issue is refusedissue_not_found, exactly as one that names no issue is. A retry of it is refused too. It is refused before the report is looked up. - With any token, a
followis refusedissue_not_foundwhere the report's origin is notworkspaceorplatformandrepeat_ofnames a restricted issue. To join such a report to a restricted issue, merge its issue withsettleand the outcomeduplicate, using theoperatortoken. - Keep a
filetoken in your backend. Its holder names the reporter each call is for, so it can read any reporter's own reports. Give it to no end user, device, or client, and name the reporter from your own record of who is calling.
On either wire:
- If your service passes on other parties' calls under its own token, choose a permission level for each call you pass on. Where you hold a token of that level and add no claim of your own to the call, pass it on under that token, and the service applies the limits. Where you pass it on under a higher token, apply that level's limits yourself. For a limit that depends on the issue's record, read the record first or refuse the call. The service sees only your token.
- In typed code, narrow
issuebefore you read a member of it. On the previous wire the client declaresissueinsubmit's answer as the issue,FiledIssue(itsid,status, anddisposition), ornull. On the current wire it declaresissueinsubmit's andfollow's answers as the issue, a projected issue, ornull. That holds whatever the token. With aworktoken or above the issue is whole and nevernull. - If you gave a
filetoken to a party you trust only to file, read again what that party filed before this version. Do the same if you passed on such a party's filings under a higher token. Earlier reports keep their origin, and an issue one opened keeps its title as trusted text and its priority, requirements, and labels until anupdatenames each.
From that version, every backend sees these changes without acting:
- A report whose origin is not
workspaceorplatformis not linked by its evidence to a restricted issue, when it is filed or when it is confirmed. Where its evidence matches no other issue, it opens its own, and its outcome readsfiled. - Where such a report's evidence matches a restricted issue's and the report is not linked to that issue, the issue's history gains one
withheldentry. A repeat under the reporter's own problem key takes an entry too. The entry names the report and the issue the report stood on. The filer is told nothing of it. A restricted issue'supdated_atmoves when it gains awithheldentry, on both wires, although the previous wire returns no such entry. - A
withheldentry that names a report filed on a build that contains the fix stops a fixed issue from closing as verified. An entry later than an issue's move towaitingstops it from closing as unexplained. Linking the report to the issue ends neither hold. A later fix ends the first kind. The second lasts for that one stay inwaitingand ends when the issue next changes state. - The first daily sweep that reads a held issue gives it the label
recheckand onesurfacedentry, once for each stay of the issue in its state. It adds no entry where the issue was already surfaced in that stay. On a heldwaitingissue the entry'sexposuresisnull. The issue keeps the label until it closes. - The space's operator ends a hold. For a space of your account, call
settle_feedbacknaming the space. Merge the report's issue into the held one where they are one problem, then reopen the held issue or settle it. - A likely match's
titleisnullwhere a pass wrote the issue's title. Anupdateof the summary alone does not make that title appear. Anupdatethat names the title does. - For an issue whose title was set before this version, the service first reads the stored record of the report that gave the title. A likely match carries a title that a
workspaceorplatformreport gave. Where the record names no report, the service reads the issue's history. The match then carries the title only where an entry shows a trusted actor gave it.
The client module gains three constants that need no change in your code: TRUSTED_ORIGINS, FILING_CLAIM_LEVELS, and FILING_CLAIM_LEVELS_V1. The history event names gain withheld. The double applies the same rules, so a test that files with a file token reads the reporter's view and names no claim above that level.
The contract's section "Migration to 0.7.0" lists every change.
Related feature packages
Issue Tracking depends on no other feature package. Storage states the rule this service applies to every write: where a service relays its users' actions, each write names the user who acted. Account verifies the end users an application names as actors.