@phis/contracts 0.2.0 → 0.4.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.
Files changed (51) hide show
  1. package/NOTICE +1 -1
  2. package/README.md +175 -67
  3. package/dist/access.d.ts +63 -0
  4. package/dist/access.js +78 -0
  5. package/dist/addon-index.d.ts +5 -0
  6. package/dist/addon-index.js +5 -0
  7. package/dist/addon.d.ts +181 -57
  8. package/dist/addon.js +2 -2
  9. package/dist/assets.d.ts +165 -0
  10. package/dist/assets.js +1 -0
  11. package/dist/capabilities.d.ts +175 -33
  12. package/dist/capabilities.js +2 -2
  13. package/dist/catalog.d.ts +104 -0
  14. package/dist/catalog.js +116 -0
  15. package/dist/cms.d.ts +120 -0
  16. package/dist/cms.js +270 -0
  17. package/dist/core.d.ts +52 -6
  18. package/dist/core.js +55 -8
  19. package/dist/http.d.ts +33 -0
  20. package/dist/http.js +33 -0
  21. package/dist/locale.d.ts +14 -0
  22. package/dist/locale.js +14 -0
  23. package/dist/logging.d.ts +49 -0
  24. package/dist/logging.js +17 -0
  25. package/dist/media-storage.d.ts +371 -0
  26. package/dist/media-storage.js +84 -0
  27. package/dist/media.d.ts +272 -0
  28. package/dist/media.js +421 -0
  29. package/dist/queries.d.ts +280 -48
  30. package/dist/queries.js +10 -1
  31. package/dist/resource-links.d.ts +94 -0
  32. package/dist/resource-links.js +27 -0
  33. package/dist/schema.d.ts +160 -26
  34. package/dist/schema.js +11 -2
  35. package/dist/server-capabilities.d.ts +37 -0
  36. package/dist/server-capabilities.js +17 -0
  37. package/dist/signals.d.ts +58 -0
  38. package/dist/signals.js +118 -0
  39. package/dist/site-groups.d.ts +53 -0
  40. package/dist/site-groups.js +54 -0
  41. package/dist/support.d.ts +390 -0
  42. package/dist/support.js +148 -0
  43. package/dist/threads.d.ts +363 -0
  44. package/dist/threads.js +146 -0
  45. package/dist/translation.d.ts +125 -0
  46. package/dist/translation.js +15 -0
  47. package/dist/user-state.d.ts +132 -0
  48. package/dist/user-state.js +92 -0
  49. package/package.json +64 -4
  50. package/dist/index.d.ts +0 -6
  51. package/dist/index.js +0 -6
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The membership ladder of a Site group, as both ends must count it.
3
+ *
4
+ * These numbers cross the wire. A membership row is written by `PATCH .../members/<userId>` carrying
5
+ * `membershipFlags` as an integer, and the administration surface offers the levels to choose from, so
6
+ * the site UI does arithmetic on the same bits phis stores. (The Add-on boundary is the other case:
7
+ * there a level crosses by name, as `PHIS_GROUP_LEVELS`, because an Add-on has no reason to
8
+ * know the bits at all.)
9
+ *
10
+ * They were declared on both sides and had drifted, which cost a level. phis counts four --
11
+ * `GROUPS_AND_STORAGE.md` enforces `Manager => Editor => Author => Member` -- and the UI counted
12
+ * three, calling `1|2` a Contributor and `1|2|4` a Manager. So the UI's Manager was phis's Editor:
13
+ * granting it left out the bit that administers membership and deletes what others made, and the
14
+ * viewer was shown a level they did not have. An actual Manager, at `1|2|4|8`, the UI could not name.
15
+ *
16
+ * The levels are cumulative, and that is the point of the ordering rather than an implementation
17
+ * detail: "may edit only their own" is a narrowing of "may edit", so an Author sits below an Editor
18
+ * on one ladder rather than beside them on another.
19
+ */
20
+ export const PhiGroupMembershipFlags = {
21
+ /** Reads what the group holds. */
22
+ Member: 1,
23
+ /** Creates, and may edit and delete what they created. */
24
+ Author: 1 | 2,
25
+ /** Reaches what others created. */
26
+ Editor: 1 | 2 | 4,
27
+ /** Deletes what anyone created, and administers the group's membership. */
28
+ Manager: 1 | 2 | 4 | 8,
29
+ };
30
+ /** Ascending, so a selector offers them in the order they widen. */
31
+ export const PHI_GROUP_MEMBERSHIP_LEVELS = [
32
+ PhiGroupMembershipFlags.Member,
33
+ PhiGroupMembershipFlags.Author,
34
+ PhiGroupMembershipFlags.Editor,
35
+ PhiGroupMembershipFlags.Manager,
36
+ ];
37
+ /**
38
+ * The level a stored value amounts to, highest bit first, or `null` if it is not one.
39
+ *
40
+ * Reading downwards is what makes an unknown higher bit fail closed rather than round up.
41
+ */
42
+ export function readPhiGroupMembershipLevel(value) {
43
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0)
44
+ return null;
45
+ if (value > PhiGroupMembershipFlags.Manager)
46
+ return null;
47
+ if ((value & 8) !== 0)
48
+ return PhiGroupMembershipFlags.Manager;
49
+ if ((value & 4) !== 0)
50
+ return PhiGroupMembershipFlags.Editor;
51
+ if ((value & 2) !== 0)
52
+ return PhiGroupMembershipFlags.Author;
53
+ return PhiGroupMembershipFlags.Member;
54
+ }
@@ -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
+ };
@@ -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
+ };