@phis/contracts 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +1 -1
- package/README.md +159 -151
- package/dist/access.d.ts +13 -0
- package/dist/access.js +13 -0
- package/dist/addon-index.d.ts +4 -0
- package/dist/addon-index.js +4 -0
- package/dist/addon.d.ts +53 -2
- package/dist/capabilities.d.ts +54 -87
- package/dist/catalog.d.ts +14 -0
- package/dist/catalog.js +14 -2
- package/dist/cms.d.ts +11 -0
- package/dist/cms.js +20 -0
- package/dist/conditions.d.ts +96 -0
- package/dist/conditions.js +163 -0
- package/dist/controls.d.ts +83 -0
- package/dist/controls.js +133 -0
- package/dist/core.d.ts +38 -0
- package/dist/core.js +39 -0
- package/dist/forms.d.ts +496 -0
- package/dist/forms.js +508 -0
- package/dist/http.d.ts +10 -1
- package/dist/http.js +10 -1
- package/dist/layout.d.ts +22 -0
- package/dist/layout.js +30 -0
- package/dist/media-storage.d.ts +256 -14
- package/dist/media-storage.js +30 -2
- package/dist/media.d.ts +47 -3
- package/dist/media.js +59 -0
- package/dist/record.d.ts +8 -0
- package/dist/record.js +24 -0
- package/dist/references.d.ts +130 -0
- package/dist/references.js +208 -0
- package/dist/resource-links.d.ts +94 -0
- package/dist/resource-links.js +27 -0
- package/dist/signals.d.ts +1 -0
- package/dist/signals.js +3 -0
- package/dist/site-groups.d.ts +15 -0
- package/dist/support.d.ts +390 -0
- package/dist/support.js +148 -0
- package/dist/threads.d.ts +363 -0
- package/dist/threads.js +146 -0
- package/dist/translation.d.ts +125 -0
- package/dist/translation.js +15 -0
- package/dist/user-state.d.ts +132 -0
- package/dist/user-state.js +92 -0
- package/package.json +47 -2
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Support vocabulary Core and the Support Module must spell the same way.
|
|
3
|
+
*
|
|
4
|
+
* A ticket type used to be a number out of a fixed list compiled into phi-server, which settled for
|
|
5
|
+
* every Site what kinds of work exist and left no room for a Site to say that one of its kinds is
|
|
6
|
+
* handled in the open and another is not. The list becomes a per-Site catalogue; what stays Core's --
|
|
7
|
+
* and therefore belongs here -- is what the entries in that catalogue may say about themselves.
|
|
8
|
+
*
|
|
9
|
+
* Two ends read these: Core, which decides what a closing issue does to a ticket and what may leave the
|
|
10
|
+
* Site, and the Support Module, which shows a person the notice that goes with it before they write.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* What is true of a ticket type, as bit flags.
|
|
14
|
+
*
|
|
15
|
+
* `External` is a flag rather than a type of its own because it cuts across every type: a bug may be
|
|
16
|
+
* handled internally or in a public repository, and a type list carrying both would have to name each
|
|
17
|
+
* kind twice.
|
|
18
|
+
*/
|
|
19
|
+
export declare const PhisSupportTicketTypeFlag: {
|
|
20
|
+
/**
|
|
21
|
+
* The conversation of a ticket of this type is mirrored to a linked external system.
|
|
22
|
+
*
|
|
23
|
+
* What still never leaves, whatever this says: internal notes, confidential messages, system messages,
|
|
24
|
+
* and anything that arrived from the integration in the first place.
|
|
25
|
+
*/
|
|
26
|
+
readonly External: number;
|
|
27
|
+
/**
|
|
28
|
+
* The work is a code change.
|
|
29
|
+
*
|
|
30
|
+
* It is what makes `CodeAccepted` meaningful: a type without it has no merge to wait for, so its
|
|
31
|
+
* tickets are closed by a person.
|
|
32
|
+
*/
|
|
33
|
+
readonly CodeChange: number;
|
|
34
|
+
/**
|
|
35
|
+
* A requester may choose this type when they open a ticket.
|
|
36
|
+
*
|
|
37
|
+
* `External` and `CodeChange` describe what happens to the work; this one describes who may ask for
|
|
38
|
+
* it, and neither of the others answers that. A listing filtered by them would offer a person a type
|
|
39
|
+
* whose conversation is mirrored into a repository, chosen by someone with no way to know so.
|
|
40
|
+
*
|
|
41
|
+
* Off by default, so a type a Site adds is Staff-only until the Site says otherwise -- the same
|
|
42
|
+
* default-deny that `site_media_config.flags` uses, for the same reason. Where a requestable type also
|
|
43
|
+
* carries `External`, the route that offers it says so before the person writes rather than after.
|
|
44
|
+
*/
|
|
45
|
+
readonly Requestable: number;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* What is true of a Support queue, as bit flags.
|
|
49
|
+
*
|
|
50
|
+
* `Entry` is where work arrives when nobody named a queue. It is a mark a Site sets rather than a
|
|
51
|
+
* default Core picks, because an unrouted ticket is a ticket nobody is looking at: without one, opening
|
|
52
|
+
* a ticket without naming a queue is refused instead of landing wherever the first row happens to be.
|
|
53
|
+
*/
|
|
54
|
+
export declare const PhisSupportQueueFlag: {
|
|
55
|
+
readonly Entry: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Where a ticket stands, as one value rather than as a set of flags.
|
|
59
|
+
*
|
|
60
|
+
* A ladder and not a bag: a ticket is at exactly one of these, and the order is the order work moves
|
|
61
|
+
* in. The two waiting states are apart because they wait on different people -- a Site chasing its own
|
|
62
|
+
* customer and a Site chasing itself are not the same queue to look at -- and `Resolved` stays distinct
|
|
63
|
+
* from `Closed` throughout, for the reason `PhisSupportClosePolicy` gives.
|
|
64
|
+
*
|
|
65
|
+
* These numbers were spelled out in phis-server alone, as `SUPPORT_TICKET_STATUS_*`, while the types in
|
|
66
|
+
* this file pointed at them in prose. That left every surface outside that process unable to name a
|
|
67
|
+
* status it was being sent: a ticket list could show the number or invent its own table of names. The
|
|
68
|
+
* server now derives its constants from these, so there is one spelling of the ladder.
|
|
69
|
+
*/
|
|
70
|
+
export declare const PhisSupportTicketStatus: {
|
|
71
|
+
readonly New: 0;
|
|
72
|
+
readonly Triaged: 1;
|
|
73
|
+
readonly Assigned: 2;
|
|
74
|
+
readonly InProgress: 3;
|
|
75
|
+
readonly WaitingCustomer: 4;
|
|
76
|
+
readonly WaitingInternal: 5;
|
|
77
|
+
readonly Escalated: 6;
|
|
78
|
+
readonly Resolved: 7;
|
|
79
|
+
readonly Closed: 8;
|
|
80
|
+
};
|
|
81
|
+
export type PhisSupportTicketStatusValue = (typeof PhisSupportTicketStatus)[keyof typeof PhisSupportTicketStatus];
|
|
82
|
+
/** How the ticket arrived. `Internal` is a Supporter opening one on somebody's behalf. */
|
|
83
|
+
export declare const PhisSupportTicketChannel: {
|
|
84
|
+
readonly Portal: 0;
|
|
85
|
+
readonly Email: 1;
|
|
86
|
+
readonly GitHub: 2;
|
|
87
|
+
readonly Api: 3;
|
|
88
|
+
readonly Internal: 4;
|
|
89
|
+
};
|
|
90
|
+
export type PhisSupportTicketChannelValue = (typeof PhisSupportTicketChannel)[keyof typeof PhisSupportTicketChannel];
|
|
91
|
+
/** How soon it wants an answer. What the Site promises per level is the Site's, not this contract's. */
|
|
92
|
+
export declare const PhisSupportTicketPriority: {
|
|
93
|
+
readonly Low: 0;
|
|
94
|
+
readonly Normal: 1;
|
|
95
|
+
readonly High: 2;
|
|
96
|
+
readonly Urgent: 3;
|
|
97
|
+
};
|
|
98
|
+
export type PhisSupportTicketPriorityValue = (typeof PhisSupportTicketPriority)[keyof typeof PhisSupportTicketPriority];
|
|
99
|
+
/** How much is broken, which is not the same question as how soon somebody wants it answered. */
|
|
100
|
+
export declare const PhisSupportTicketSeverity: {
|
|
101
|
+
readonly Minor: 0;
|
|
102
|
+
readonly Major: 1;
|
|
103
|
+
readonly Critical: 2;
|
|
104
|
+
};
|
|
105
|
+
export type PhisSupportTicketSeverityValue = (typeof PhisSupportTicketSeverity)[keyof typeof PhisSupportTicketSeverity];
|
|
106
|
+
/**
|
|
107
|
+
* What ends a ticket of this type.
|
|
108
|
+
*
|
|
109
|
+
* `Resolved` and `Closed` stay distinct throughout: resolved is technically done, closed is confirmed.
|
|
110
|
+
* Neither policy ever resolves a ticket because an issue was closed as not planned -- that is a decision
|
|
111
|
+
* somebody took about the external work, not about the Support work, and a repository's own stale
|
|
112
|
+
* automation must not be able to close a customer's ticket.
|
|
113
|
+
*/
|
|
114
|
+
export declare const PhisSupportClosePolicy: {
|
|
115
|
+
/** A Supporter confirms. The default, and the only one available without a code change. */
|
|
116
|
+
readonly Confirm: 0;
|
|
117
|
+
/**
|
|
118
|
+
* The merge is acceptance.
|
|
119
|
+
*
|
|
120
|
+
* A merged pull request completes its task; when every engineering task is done the ticket is
|
|
121
|
+
* resolved, and the requester window or a Supporter closes it. Waiting for a release instead would
|
|
122
|
+
* leave a ticket open for work nobody is doing any more -- what a release adds is what is said, not
|
|
123
|
+
* when the state moves.
|
|
124
|
+
*/
|
|
125
|
+
readonly CodeAccepted: 1;
|
|
126
|
+
};
|
|
127
|
+
export type PhisSupportClosePolicyValue = (typeof PhisSupportClosePolicy)[keyof typeof PhisSupportClosePolicy];
|
|
128
|
+
/**
|
|
129
|
+
* How long a resolved ticket waits for its requester before it closes itself, in days.
|
|
130
|
+
*
|
|
131
|
+
* The Site default, which a ticket type may narrow or widen, and `0` means the type's tickets wait for a
|
|
132
|
+
* Supporter instead. The window starts when the requester could actually have the fix -- at the release
|
|
133
|
+
* message where releases are tracked, at the merge otherwise -- so it is a chance to object rather than
|
|
134
|
+
* a deadline for reading email.
|
|
135
|
+
*/
|
|
136
|
+
export declare const PHIS_SUPPORT_REQUESTER_WINDOW_DEFAULT_DAYS: 14;
|
|
137
|
+
/**
|
|
138
|
+
* A queue a Module needs the Site to have, named rather than created.
|
|
139
|
+
*
|
|
140
|
+
* `groupKey` names a group from the same `seed`, not a group id: what a Module declares cannot contain
|
|
141
|
+
* an id, because the row it refers to does not exist until the same publish makes it. The materialization
|
|
142
|
+
* resolves the two in order, which is why the order is not the Module's to script.
|
|
143
|
+
*
|
|
144
|
+
* A Site that runs Support needs exactly one of these marked `Entry`, and the declaration is where that
|
|
145
|
+
* comes from: a Site with no Entry queue refuses a ticket that names no queue rather than dropping it
|
|
146
|
+
* somewhere, and nobody should have to know that before they can answer anybody.
|
|
147
|
+
*/
|
|
148
|
+
export type PhisDeclaredSupportQueue = {
|
|
149
|
+
readonly key: string;
|
|
150
|
+
readonly name: string;
|
|
151
|
+
readonly groupKey: string;
|
|
152
|
+
/** `PhisSupportQueueFlag`. Absent is none. */
|
|
153
|
+
readonly flags?: number;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* A ticket type a Module needs the Site to have, named rather than created.
|
|
157
|
+
*
|
|
158
|
+
* What a Site opens with before anybody has administered anything. It is declared without
|
|
159
|
+
* `Requestable`, because a type that arrives with the Module has not been looked at by the Site yet, and
|
|
160
|
+
* offering it to the public is a decision the Site takes rather than one it inherits.
|
|
161
|
+
*/
|
|
162
|
+
export type PhisDeclaredSupportTicketType = {
|
|
163
|
+
readonly key: string;
|
|
164
|
+
readonly name: string;
|
|
165
|
+
/** `PhisSupportTicketTypeFlag`. Absent is none. */
|
|
166
|
+
readonly flags?: number;
|
|
167
|
+
/** Absent is `Confirm`, the only policy available without a code change. */
|
|
168
|
+
readonly closePolicy?: PhisSupportClosePolicyValue;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* The stretch of time a requester's allowance is counted over.
|
|
172
|
+
*
|
|
173
|
+
* Calendar periods rather than a rolling window of days, which is the other clock Support keeps
|
|
174
|
+
* (`requester_window_days`) and is deliberately not this one. That clock measures one ticket's silence,
|
|
175
|
+
* where a rolling window is right because it starts when the ticket does. This is a budget, and a budget
|
|
176
|
+
* a person can plan around is one whose start they can name: "five this year" is a sentence, "five in
|
|
177
|
+
* any three hundred and sixty-five days" is an audit.
|
|
178
|
+
*/
|
|
179
|
+
export declare const PhisSupportAllowancePeriod: {
|
|
180
|
+
/** No period, which is what a type without a limit has. */
|
|
181
|
+
readonly None: 0;
|
|
182
|
+
/** From the first of the month. */
|
|
183
|
+
readonly Month: 1;
|
|
184
|
+
/** From the first of January. */
|
|
185
|
+
readonly Year: 2;
|
|
186
|
+
};
|
|
187
|
+
export type PhisSupportAllowancePeriodValue = (typeof PhisSupportAllowancePeriod)[keyof typeof PhisSupportAllowancePeriod];
|
|
188
|
+
/**
|
|
189
|
+
* What one person may still ask for, of one ticket type.
|
|
190
|
+
*
|
|
191
|
+
* Two numbers that are not the same kind of thing, and keeping them apart is the point. `limit` is the
|
|
192
|
+
* Site's periodic allowance -- it renews, and what goes unused at the end of the period is gone.
|
|
193
|
+
* `granted` is a stock somebody handed over, which does not renew and is not lost at the boundary: it
|
|
194
|
+
* sits there until it is used or it expires. A ticket spends the periodic allowance first and only
|
|
195
|
+
* reaches the stock when that is empty, so a person who bought ten does not find them quietly eaten in a
|
|
196
|
+
* month where the free five would have covered it.
|
|
197
|
+
*
|
|
198
|
+
* `limit` of `null` is no limit at all, and then `remaining` is `null` too -- absent rather than a large
|
|
199
|
+
* number, because "unlimited" and "very many left" are different answers and a surface that cannot tell
|
|
200
|
+
* them apart will eventually print one as the other.
|
|
201
|
+
*/
|
|
202
|
+
export type PhisSupportAllowance = {
|
|
203
|
+
readonly typeKey: string;
|
|
204
|
+
/** The periodic allowance, or `null` where this type has none. */
|
|
205
|
+
readonly limit: number | null;
|
|
206
|
+
readonly period: PhisSupportAllowancePeriodValue;
|
|
207
|
+
/** Where the current period began, or `null` where there is no limit. */
|
|
208
|
+
readonly periodStart: string | null;
|
|
209
|
+
/** Tickets this person opened for themselves of this type since then. */
|
|
210
|
+
readonly used: number;
|
|
211
|
+
/** Still-live stock from grants: handed over, not yet spent, not expired, not revoked. */
|
|
212
|
+
readonly granted: number;
|
|
213
|
+
/** What they may still open, counting both, or `null` where there is no limit. */
|
|
214
|
+
readonly remaining: number | null;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* A stock of requests handed to one person by whoever is entitled to hand it over.
|
|
218
|
+
*
|
|
219
|
+
* `reference` is the granting Add-on's own name for the occasion -- an order number, a contract line --
|
|
220
|
+
* and it is required rather than optional because it is what makes the grant idempotent. A payment
|
|
221
|
+
* provider retries its webhook; a shop that had no reference to give would grant the ten twice and have
|
|
222
|
+
* no way to notice. Granting the same reference again is therefore not an error and not a second grant:
|
|
223
|
+
* it answers with the one that already exists.
|
|
224
|
+
*/
|
|
225
|
+
export type PhisSupportAllowanceGrant = {
|
|
226
|
+
readonly id: number;
|
|
227
|
+
readonly typeKey: string;
|
|
228
|
+
readonly userId: number;
|
|
229
|
+
/** The Add-on that handed it over. */
|
|
230
|
+
readonly providerId: string;
|
|
231
|
+
readonly reference: string;
|
|
232
|
+
readonly amount: number;
|
|
233
|
+
/** How much of it has been spent on tickets. */
|
|
234
|
+
readonly consumed: number;
|
|
235
|
+
/** When it stops counting, or `null` for never. */
|
|
236
|
+
readonly expiresAt: string | null;
|
|
237
|
+
readonly revokedAt: string | null;
|
|
238
|
+
readonly createdAt: string;
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* One ticket, as an Add-on that is linked to it may see it.
|
|
242
|
+
*
|
|
243
|
+
* Without its conversation, which is the thread and is read through `threads:v1` with the reader's own
|
|
244
|
+
* visibility applied -- internal notes withheld, confidential bodies withheld. Reproducing that rule
|
|
245
|
+
* here would be a second answer to a question that already has one.
|
|
246
|
+
*
|
|
247
|
+
* The type is carried whole rather than as an id, because what an integration has to decide it decides
|
|
248
|
+
* from the type: whether anything may leave the Site at all, and what ends the ticket. An Add-on asking
|
|
249
|
+
* for the id and then for the type would be two calls to learn one thing.
|
|
250
|
+
*/
|
|
251
|
+
export type PhisSupportTicket = {
|
|
252
|
+
id: number;
|
|
253
|
+
/** Its conversation, to be read and appended to through `threads:v1`. */
|
|
254
|
+
threadId: number;
|
|
255
|
+
subject: string;
|
|
256
|
+
/** `PhisSupportTicketStatus`: the ladder from `New` to `Closed`. */
|
|
257
|
+
status: number;
|
|
258
|
+
/** The queue it was taken into, or none while it is still in triage. */
|
|
259
|
+
queueId: number | null;
|
|
260
|
+
type: {
|
|
261
|
+
id: number;
|
|
262
|
+
key: string;
|
|
263
|
+
/** `PhisSupportTicketTypeFlag`. */
|
|
264
|
+
flags: number;
|
|
265
|
+
closePolicy: PhisSupportClosePolicyValue;
|
|
266
|
+
};
|
|
267
|
+
createdAt: string;
|
|
268
|
+
updatedAt: string;
|
|
269
|
+
};
|
|
270
|
+
/**
|
|
271
|
+
* One piece of work on a ticket.
|
|
272
|
+
*
|
|
273
|
+
* An issue or a pull request hangs on a task rather than on the ticket, because one ticket may need
|
|
274
|
+
* several and because resolving an issue does not resolve the Support work. A task is completed by
|
|
275
|
+
* whoever did it; the ticket decides for itself when it is done.
|
|
276
|
+
*/
|
|
277
|
+
export type PhisSupportTicketTask = {
|
|
278
|
+
id: number;
|
|
279
|
+
ticketId: number;
|
|
280
|
+
/** `SUPPORT_TICKET_TASK_TYPE_*`; `2` is engineering. */
|
|
281
|
+
type: number;
|
|
282
|
+
/** `SUPPORT_TICKET_TASK_STATUS_*`: open, in progress, blocked, done, canceled. */
|
|
283
|
+
status: number;
|
|
284
|
+
title: string;
|
|
285
|
+
description: string | null;
|
|
286
|
+
completedAt: string | null;
|
|
287
|
+
createdAt: string;
|
|
288
|
+
updatedAt: string;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* The Support capability: `@phis/server/support:v1`.
|
|
292
|
+
*
|
|
293
|
+
* The workflow record around a conversation -- the type, the queue, the status, the work -- for an
|
|
294
|
+
* integration that mirrors that conversation somewhere else. It exists so that a package in its own
|
|
295
|
+
* repository can reach a Site's tickets without depending on `@phis/support`, which is only the
|
|
296
|
+
* Module: the tables and the rules are Core's.
|
|
297
|
+
*
|
|
298
|
+
* **Reach is the thread's answer, never a second one.** A ticket is readable when its thread is, which
|
|
299
|
+
* for an Add-on with nobody behind it means one of the two ways in `threads:v1` has: it opened the
|
|
300
|
+
* thread itself, or a person who could see the thread linked it to something of this Add-on's and
|
|
301
|
+
* marked that link synced. There is no listing, and no search. An Add-on finds a ticket it was given
|
|
302
|
+
* and never walks a Site's.
|
|
303
|
+
*
|
|
304
|
+
* **What it may not do.** It does not move a ticket's status. Closing is the ticket's own rule -- an
|
|
305
|
+
* issue completes its task, and whether that resolves the Support work depends on the type's close
|
|
306
|
+
* policy and on the other tasks -- so an integration that could set the status directly would be
|
|
307
|
+
* deciding something it cannot see all of.
|
|
308
|
+
*/
|
|
309
|
+
export type PhisSupportCapabilityV1 = {
|
|
310
|
+
/** One ticket, or null where it is out of reach or absent. */
|
|
311
|
+
get(ticketId: number): Promise<PhisSupportTicket | null>;
|
|
312
|
+
/** The ticket a thread belongs to, for an event that arrived naming the conversation. */
|
|
313
|
+
findByThread(threadId: number): Promise<PhisSupportTicket | null>;
|
|
314
|
+
/**
|
|
315
|
+
* Opens a ticket, its thread owned by this Add-on.
|
|
316
|
+
*
|
|
317
|
+
* For work that arrived from the other side -- an issue opened in a watched repository. The type and
|
|
318
|
+
* the queue are the Site's own rows and are named by key, because an id means nothing in an Add-on's
|
|
319
|
+
* configuration and would not survive the Site being rebuilt.
|
|
320
|
+
*/
|
|
321
|
+
create(input: {
|
|
322
|
+
typeKey: string;
|
|
323
|
+
queueKey?: string;
|
|
324
|
+
subject: string;
|
|
325
|
+
message: string;
|
|
326
|
+
externalRef?: string;
|
|
327
|
+
}): Promise<PhisSupportTicket>;
|
|
328
|
+
/** The work on one ticket, in the order the Site keeps it. */
|
|
329
|
+
listTasks(ticketId: number): Promise<PhisSupportTicketTask[]>;
|
|
330
|
+
/** Records a piece of work, which is what a linked issue hangs on. */
|
|
331
|
+
addTask(input: {
|
|
332
|
+
ticketId: number;
|
|
333
|
+
type: number;
|
|
334
|
+
title: string;
|
|
335
|
+
description?: string;
|
|
336
|
+
}): Promise<PhisSupportTicketTask>;
|
|
337
|
+
/** Moves one task, and only the task. */
|
|
338
|
+
setTaskStatus(input: {
|
|
339
|
+
taskId: number;
|
|
340
|
+
status: number;
|
|
341
|
+
}): Promise<PhisSupportTicketTask>;
|
|
342
|
+
/**
|
|
343
|
+
* What one person may still ask for, of one type.
|
|
344
|
+
*
|
|
345
|
+
* The read behind a "you have two left" before somebody writes, and the read a shop does before it
|
|
346
|
+
* offers to sell more. It names a person, which nothing else on this capability does -- see `grant`
|
|
347
|
+
* for why that is allowed here and what bounds it.
|
|
348
|
+
*/
|
|
349
|
+
allowance(input: {
|
|
350
|
+
userId: number;
|
|
351
|
+
typeKey: string;
|
|
352
|
+
}): Promise<PhisSupportAllowance>;
|
|
353
|
+
/**
|
|
354
|
+
* Hands somebody a stock of requests.
|
|
355
|
+
*
|
|
356
|
+
* This is the call the rest of the allowance exists for. A Site's periodic limit is the Site's to set,
|
|
357
|
+
* but "this customer bought ten more" is knowledge no part of Support has: it lives in whatever sold
|
|
358
|
+
* them, and that is by design a package Support does not know about. So Support states the arithmetic
|
|
359
|
+
* and lets somebody else move the number.
|
|
360
|
+
*
|
|
361
|
+
* **Naming a person is bounded by the Site, not by reach.** Every other call here reaches a ticket it
|
|
362
|
+
* was given and walks nothing; this one takes a user id, as `groups:v1` does for the same unavoidable
|
|
363
|
+
* reason -- there is no ticket yet, and the grant is about the person. The bound is the same one
|
|
364
|
+
* `groups:v1` uses: the user must be a member of this Site, and an unverified hook reaches none of it.
|
|
365
|
+
* What that leaves an Add-on able to do is give a member of its own Site something, which is what it
|
|
366
|
+
* was installed to do.
|
|
367
|
+
*
|
|
368
|
+
* Granting is idempotent on `reference`; see `PhisSupportAllowanceGrant`.
|
|
369
|
+
*/
|
|
370
|
+
grant(input: {
|
|
371
|
+
userId: number;
|
|
372
|
+
typeKey: string;
|
|
373
|
+
amount: number;
|
|
374
|
+
reference: string;
|
|
375
|
+
/** ISO 8601. Absent is never. */
|
|
376
|
+
expiresAt?: string | null;
|
|
377
|
+
}): Promise<PhisSupportAllowanceGrant>;
|
|
378
|
+
/**
|
|
379
|
+
* Takes back a stock that has not been spent, by the reference it was granted under.
|
|
380
|
+
*
|
|
381
|
+
* A refund, a chargeback, a contract that ended. What it does not do is undo the tickets already
|
|
382
|
+
* opened against it: those are conversations somebody is having, and a payment reversed later does not
|
|
383
|
+
* make them not have happened. So the unspent remainder stops counting and the spent part stands.
|
|
384
|
+
*
|
|
385
|
+
* An Add-on revokes only what it granted. Null where there is no such grant of its own.
|
|
386
|
+
*/
|
|
387
|
+
revokeGrant(input: {
|
|
388
|
+
reference: string;
|
|
389
|
+
}): Promise<PhisSupportAllowanceGrant | null>;
|
|
390
|
+
};
|
package/dist/support.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Support vocabulary Core and the Support Module must spell the same way.
|
|
3
|
+
*
|
|
4
|
+
* A ticket type used to be a number out of a fixed list compiled into phi-server, which settled for
|
|
5
|
+
* every Site what kinds of work exist and left no room for a Site to say that one of its kinds is
|
|
6
|
+
* handled in the open and another is not. The list becomes a per-Site catalogue; what stays Core's --
|
|
7
|
+
* and therefore belongs here -- is what the entries in that catalogue may say about themselves.
|
|
8
|
+
*
|
|
9
|
+
* Two ends read these: Core, which decides what a closing issue does to a ticket and what may leave the
|
|
10
|
+
* Site, and the Support Module, which shows a person the notice that goes with it before they write.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* What is true of a ticket type, as bit flags.
|
|
14
|
+
*
|
|
15
|
+
* `External` is a flag rather than a type of its own because it cuts across every type: a bug may be
|
|
16
|
+
* handled internally or in a public repository, and a type list carrying both would have to name each
|
|
17
|
+
* kind twice.
|
|
18
|
+
*/
|
|
19
|
+
export const PhisSupportTicketTypeFlag = {
|
|
20
|
+
/**
|
|
21
|
+
* The conversation of a ticket of this type is mirrored to a linked external system.
|
|
22
|
+
*
|
|
23
|
+
* What still never leaves, whatever this says: internal notes, confidential messages, system messages,
|
|
24
|
+
* and anything that arrived from the integration in the first place.
|
|
25
|
+
*/
|
|
26
|
+
External: 1 << 0,
|
|
27
|
+
/**
|
|
28
|
+
* The work is a code change.
|
|
29
|
+
*
|
|
30
|
+
* It is what makes `CodeAccepted` meaningful: a type without it has no merge to wait for, so its
|
|
31
|
+
* tickets are closed by a person.
|
|
32
|
+
*/
|
|
33
|
+
CodeChange: 1 << 1,
|
|
34
|
+
/**
|
|
35
|
+
* A requester may choose this type when they open a ticket.
|
|
36
|
+
*
|
|
37
|
+
* `External` and `CodeChange` describe what happens to the work; this one describes who may ask for
|
|
38
|
+
* it, and neither of the others answers that. A listing filtered by them would offer a person a type
|
|
39
|
+
* whose conversation is mirrored into a repository, chosen by someone with no way to know so.
|
|
40
|
+
*
|
|
41
|
+
* Off by default, so a type a Site adds is Staff-only until the Site says otherwise -- the same
|
|
42
|
+
* default-deny that `site_media_config.flags` uses, for the same reason. Where a requestable type also
|
|
43
|
+
* carries `External`, the route that offers it says so before the person writes rather than after.
|
|
44
|
+
*/
|
|
45
|
+
Requestable: 1 << 2,
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* What is true of a Support queue, as bit flags.
|
|
49
|
+
*
|
|
50
|
+
* `Entry` is where work arrives when nobody named a queue. It is a mark a Site sets rather than a
|
|
51
|
+
* default Core picks, because an unrouted ticket is a ticket nobody is looking at: without one, opening
|
|
52
|
+
* a ticket without naming a queue is refused instead of landing wherever the first row happens to be.
|
|
53
|
+
*/
|
|
54
|
+
export const PhisSupportQueueFlag = {
|
|
55
|
+
Entry: 1 << 0,
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Where a ticket stands, as one value rather than as a set of flags.
|
|
59
|
+
*
|
|
60
|
+
* A ladder and not a bag: a ticket is at exactly one of these, and the order is the order work moves
|
|
61
|
+
* in. The two waiting states are apart because they wait on different people -- a Site chasing its own
|
|
62
|
+
* customer and a Site chasing itself are not the same queue to look at -- and `Resolved` stays distinct
|
|
63
|
+
* from `Closed` throughout, for the reason `PhisSupportClosePolicy` gives.
|
|
64
|
+
*
|
|
65
|
+
* These numbers were spelled out in phis-server alone, as `SUPPORT_TICKET_STATUS_*`, while the types in
|
|
66
|
+
* this file pointed at them in prose. That left every surface outside that process unable to name a
|
|
67
|
+
* status it was being sent: a ticket list could show the number or invent its own table of names. The
|
|
68
|
+
* server now derives its constants from these, so there is one spelling of the ladder.
|
|
69
|
+
*/
|
|
70
|
+
export const PhisSupportTicketStatus = {
|
|
71
|
+
New: 0,
|
|
72
|
+
Triaged: 1,
|
|
73
|
+
Assigned: 2,
|
|
74
|
+
InProgress: 3,
|
|
75
|
+
WaitingCustomer: 4,
|
|
76
|
+
WaitingInternal: 5,
|
|
77
|
+
Escalated: 6,
|
|
78
|
+
Resolved: 7,
|
|
79
|
+
Closed: 8,
|
|
80
|
+
};
|
|
81
|
+
/** How the ticket arrived. `Internal` is a Supporter opening one on somebody's behalf. */
|
|
82
|
+
export const PhisSupportTicketChannel = {
|
|
83
|
+
Portal: 0,
|
|
84
|
+
Email: 1,
|
|
85
|
+
GitHub: 2,
|
|
86
|
+
Api: 3,
|
|
87
|
+
Internal: 4,
|
|
88
|
+
};
|
|
89
|
+
/** How soon it wants an answer. What the Site promises per level is the Site's, not this contract's. */
|
|
90
|
+
export const PhisSupportTicketPriority = {
|
|
91
|
+
Low: 0,
|
|
92
|
+
Normal: 1,
|
|
93
|
+
High: 2,
|
|
94
|
+
Urgent: 3,
|
|
95
|
+
};
|
|
96
|
+
/** How much is broken, which is not the same question as how soon somebody wants it answered. */
|
|
97
|
+
export const PhisSupportTicketSeverity = {
|
|
98
|
+
Minor: 0,
|
|
99
|
+
Major: 1,
|
|
100
|
+
Critical: 2,
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* What ends a ticket of this type.
|
|
104
|
+
*
|
|
105
|
+
* `Resolved` and `Closed` stay distinct throughout: resolved is technically done, closed is confirmed.
|
|
106
|
+
* Neither policy ever resolves a ticket because an issue was closed as not planned -- that is a decision
|
|
107
|
+
* somebody took about the external work, not about the Support work, and a repository's own stale
|
|
108
|
+
* automation must not be able to close a customer's ticket.
|
|
109
|
+
*/
|
|
110
|
+
export const PhisSupportClosePolicy = {
|
|
111
|
+
/** A Supporter confirms. The default, and the only one available without a code change. */
|
|
112
|
+
Confirm: 0,
|
|
113
|
+
/**
|
|
114
|
+
* The merge is acceptance.
|
|
115
|
+
*
|
|
116
|
+
* A merged pull request completes its task; when every engineering task is done the ticket is
|
|
117
|
+
* resolved, and the requester window or a Supporter closes it. Waiting for a release instead would
|
|
118
|
+
* leave a ticket open for work nobody is doing any more -- what a release adds is what is said, not
|
|
119
|
+
* when the state moves.
|
|
120
|
+
*/
|
|
121
|
+
CodeAccepted: 1,
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* How long a resolved ticket waits for its requester before it closes itself, in days.
|
|
125
|
+
*
|
|
126
|
+
* The Site default, which a ticket type may narrow or widen, and `0` means the type's tickets wait for a
|
|
127
|
+
* Supporter instead. The window starts when the requester could actually have the fix -- at the release
|
|
128
|
+
* message where releases are tracked, at the merge otherwise -- so it is a chance to object rather than
|
|
129
|
+
* a deadline for reading email.
|
|
130
|
+
*/
|
|
131
|
+
export const PHIS_SUPPORT_REQUESTER_WINDOW_DEFAULT_DAYS = 14;
|
|
132
|
+
/**
|
|
133
|
+
* The stretch of time a requester's allowance is counted over.
|
|
134
|
+
*
|
|
135
|
+
* Calendar periods rather than a rolling window of days, which is the other clock Support keeps
|
|
136
|
+
* (`requester_window_days`) and is deliberately not this one. That clock measures one ticket's silence,
|
|
137
|
+
* where a rolling window is right because it starts when the ticket does. This is a budget, and a budget
|
|
138
|
+
* a person can plan around is one whose start they can name: "five this year" is a sentence, "five in
|
|
139
|
+
* any three hundred and sixty-five days" is an audit.
|
|
140
|
+
*/
|
|
141
|
+
export const PhisSupportAllowancePeriod = {
|
|
142
|
+
/** No period, which is what a type without a limit has. */
|
|
143
|
+
None: 0,
|
|
144
|
+
/** From the first of the month. */
|
|
145
|
+
Month: 1,
|
|
146
|
+
/** From the first of January. */
|
|
147
|
+
Year: 2,
|
|
148
|
+
};
|