@phis/contracts 0.3.1 → 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.
- 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/core.d.ts +38 -0
- package/dist/core.js +39 -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/resource-links.d.ts +94 -0
- package/dist/resource-links.js +27 -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 +22 -2
package/dist/media-storage.js
CHANGED
|
@@ -26,8 +26,17 @@ export const PHI_LOCAL_MEDIA_STORAGE_PROVIDER_ID = "@phis/server/storage-local";
|
|
|
26
26
|
* therefore carries the algorithm that produced it.
|
|
27
27
|
*/
|
|
28
28
|
export const PHIS_MEDIA_CHECKSUM_ALGORITHMS = [
|
|
29
|
-
"sha256", "sha512", "xxhash128", "crc64nvme", "crc32c", "crc32", "md5",
|
|
29
|
+
"sha256", "sha512", "sha256-composite", "xxhash128", "crc64nvme", "crc32c", "crc32", "md5",
|
|
30
30
|
];
|
|
31
|
+
/*
|
|
32
|
+
* The division and the composite's recorded form live in `./media.ts`, and are re-exported here.
|
|
33
|
+
*
|
|
34
|
+
* They are the one part of this contract a browser has to compute as well: the Client hashes the parts
|
|
35
|
+
* before a plan exists, because a part digest can only be signed into a part request if it is known when
|
|
36
|
+
* that request is signed. So the rule cannot sit behind the Add-on surface, and it must not be written
|
|
37
|
+
* twice -- two copies of a division are two divisions the day one of them is edited.
|
|
38
|
+
*/
|
|
39
|
+
export { PHIS_MEDIA_UPLOAD_MAX_PART_COUNT, PHIS_MEDIA_UPLOAD_PART_SIZE_BYTES, formatPhiMediaCompositeChecksum, parsePhiMediaCompositeChecksum, resolvePhiMediaUploadPartCount, resolvePhiMediaUploadPartSizeBytes, } from "./media.js";
|
|
31
40
|
/**
|
|
32
41
|
* The digests strong enough to answer "is this the same file", rather than only "did it arrive intact".
|
|
33
42
|
*
|
|
@@ -39,7 +48,20 @@ export const PHIS_MEDIA_CHECKSUM_ALGORITHMS = [
|
|
|
39
48
|
* substituted file -- so the cost of trusting a weak digest is that someone who may already write to a
|
|
40
49
|
* Space can stop one file from entering it. Small, but not nothing, and free to avoid.
|
|
41
50
|
*/
|
|
42
|
-
export const PHIS_MEDIA_IDENTITY_CHECKSUM_ALGORITHMS = ["sha256", "sha512"];
|
|
51
|
+
export const PHIS_MEDIA_IDENTITY_CHECKSUM_ALGORITHMS = ["sha256", "sha512", "sha256-composite"];
|
|
52
|
+
/**
|
|
53
|
+
* Why `sha256-composite` is identity-grade although it hashes digests rather than bytes.
|
|
54
|
+
*
|
|
55
|
+
* It is `SHA256` over the concatenated part digests of a body divided by a fixed rule. For the question
|
|
56
|
+
* duplicate detection asks -- are these the same bytes -- it is as hard to forge as SHA-256 itself: a
|
|
57
|
+
* second file matching one requires a SHA-256 collision in some part. What it cannot do is compare across
|
|
58
|
+
* divisions, which is why the division is written into the value and why the rule above is frozen.
|
|
59
|
+
*
|
|
60
|
+
* It exists because a browser cannot produce the other kind for a large file. Web Crypto has no
|
|
61
|
+
* incremental digest, so a whole-object SHA-256 means holding the entire body in memory; a part digest is
|
|
62
|
+
* one part, and the composite over those is a few kilobytes. The choice is not between this and a
|
|
63
|
+
* whole-object digest, it is between this and no digest at all.
|
|
64
|
+
*/
|
|
43
65
|
export function isPhisMediaIdentityChecksum(algorithm) {
|
|
44
66
|
return PHIS_MEDIA_IDENTITY_CHECKSUM_ALGORITHMS.includes(algorithm);
|
|
45
67
|
}
|
|
@@ -53,11 +75,17 @@ export function isPhisMediaIdentityChecksum(algorithm) {
|
|
|
53
75
|
*
|
|
54
76
|
* Only identity-grade digests are eligible. A CRC an endpoint also verifies still proves a transfer
|
|
55
77
|
* intact, and Core records it when that is what arrived; it is simply not what a Profile asks for.
|
|
78
|
+
*
|
|
79
|
+
* `sha256-composite` is not eligible either, for a different reason: it is not something an endpoint
|
|
80
|
+
* verifies, it is the form a SHA-256 takes when a body arrived in parts. A Profile records a base
|
|
81
|
+
* algorithm, and an object assembled from parts under a `sha256` Profile carries the composite of it.
|
|
56
82
|
*/
|
|
57
83
|
export function choosePhisMediaChecksumAlgorithm(probe) {
|
|
58
84
|
if (!probe)
|
|
59
85
|
return null;
|
|
60
86
|
for (const candidate of PHIS_MEDIA_IDENTITY_CHECKSUM_ALGORITHMS) {
|
|
87
|
+
if (candidate === "sha256-composite")
|
|
88
|
+
continue;
|
|
61
89
|
if (probe.verifiedAlgorithms.includes(candidate))
|
|
62
90
|
return candidate;
|
|
63
91
|
}
|
package/dist/media.d.ts
CHANGED
|
@@ -104,9 +104,9 @@ export type PhiImageAssetVariantSpec = {
|
|
|
104
104
|
quality: number;
|
|
105
105
|
format: "webp";
|
|
106
106
|
};
|
|
107
|
-
export declare function normalizePhiMediaKind(kind: string | null | undefined): "
|
|
108
|
-
export declare function resolvePhiMediaKindFromContentType(contentType: string | null | undefined): "
|
|
109
|
-
export declare function normalizePhiImageAssetVariantKey(key: unknown): 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|
|
107
|
+
export declare function normalizePhiMediaKind(kind: string | null | undefined): "image" | "video" | "audio" | "pdf" | "markdown" | "document" | "archive" | "font" | "binary" | "other";
|
|
108
|
+
export declare function resolvePhiMediaKindFromContentType(contentType: string | null | undefined): "image" | "video" | "audio" | "pdf" | "markdown" | "document" | "archive" | "font" | "binary" | "other";
|
|
109
|
+
export declare function normalizePhiImageAssetVariantKey(key: unknown): 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 8 | null;
|
|
110
110
|
export declare function resolvePhiImageAssetVariantKeyName(key: number | null | undefined): string | null;
|
|
111
111
|
export declare function resolvePhiImageAssetVariantSpec(key: number | null | undefined): PhiImageAssetVariantSpec | null;
|
|
112
112
|
/**
|
|
@@ -226,3 +226,47 @@ export declare function isPhiFontSubsetKey(value: unknown): value is PhiFontSubs
|
|
|
226
226
|
* `LatinExt` where `sr` preloads `Cyrillic`.
|
|
227
227
|
*/
|
|
228
228
|
export declare function resolvePhiFontPreloadSubsetKeys(locale: string | null | undefined, coverage: PhiFontCoverage): PhiFontSubsetKeyValue[];
|
|
229
|
+
/**
|
|
230
|
+
* How a body above one part is divided, as a rule rather than as a Provider's private arithmetic.
|
|
231
|
+
*
|
|
232
|
+
* It lives here because two parties have to agree on it and neither can be told by the other in time. The
|
|
233
|
+
* Provider needs the part size to sign one request per part; the Client needs it *before* the plan exists,
|
|
234
|
+
* because a digest can only be signed into a request if it is known when the request is signed -- so the
|
|
235
|
+
* Client has to hash the same divisions the Provider is about to address. A rule both compute from the
|
|
236
|
+
* byte size is the only arrangement where that holds without an extra round trip.
|
|
237
|
+
*
|
|
238
|
+
* **Frozen once objects exist under it.** A `sha256-composite` value is a digest of part digests, so it
|
|
239
|
+
* answers "the same bytes divided the same way". Change the division and the same file yields a different
|
|
240
|
+
* value, which splits duplicate detection into cohorts that can never match across the change. The value
|
|
241
|
+
* carries the part size for exactly this reason: a changed rule produces non-matches, never false matches.
|
|
242
|
+
* Changing it is therefore a decision about the whole corpus, not a tuning knob.
|
|
243
|
+
*
|
|
244
|
+
* `PHIS_MEDIA_UPLOAD_MAX_PART_COUNT` is well under S3's ten thousand on purpose. Every part is signed up
|
|
245
|
+
* front, so the count is also the size of the plan a Client receives and Core holds in memory.
|
|
246
|
+
*/
|
|
247
|
+
export declare const PHIS_MEDIA_UPLOAD_PART_SIZE_BYTES: number;
|
|
248
|
+
export declare const PHIS_MEDIA_UPLOAD_MAX_PART_COUNT = 1000;
|
|
249
|
+
/**
|
|
250
|
+
* The part size for one body, and the same answer wherever it is asked.
|
|
251
|
+
*
|
|
252
|
+
* Sixteen mebibytes until that would need more parts than are allowed, and from there the smallest whole
|
|
253
|
+
* mebibyte that fits the body into the allowance. Whole mebibytes keep the number short where it is
|
|
254
|
+
* recorded and keep the arithmetic exact in a browser, where sizes are doubles.
|
|
255
|
+
*/
|
|
256
|
+
export declare function resolvePhiMediaUploadPartSizeBytes(sizeBytes: number): number;
|
|
257
|
+
/** How many parts one body is divided into under `resolvePhiMediaUploadPartSizeBytes`. */
|
|
258
|
+
export declare function resolvePhiMediaUploadPartCount(sizeBytes: number): number;
|
|
259
|
+
/**
|
|
260
|
+
* The recorded form of a `sha256-composite` digest: the digest, the part count, the part size.
|
|
261
|
+
*
|
|
262
|
+
* Duplicate detection compares these values as strings, so everything that decides whether two of them
|
|
263
|
+
* mean the same thing has to be inside the string. The digest alone would not: the same bytes divided
|
|
264
|
+
* differently hash differently, and a value that did not say how it was divided would look like a plain
|
|
265
|
+
* SHA-256 that simply failed to match. Naming the division makes a mismatch legible instead of puzzling.
|
|
266
|
+
*/
|
|
267
|
+
export declare function formatPhiMediaCompositeChecksum(digestHex: string, partCount: number, partSizeBytes: number): string;
|
|
268
|
+
export declare function parsePhiMediaCompositeChecksum(value: string): {
|
|
269
|
+
digest: string;
|
|
270
|
+
partCount: number;
|
|
271
|
+
partSizeBytes: number;
|
|
272
|
+
} | null;
|
package/dist/media.js
CHANGED
|
@@ -360,3 +360,62 @@ export function resolvePhiFontPreloadSubsetKeys(locale, coverage) {
|
|
|
360
360
|
: PHI_FONT_SUBSET_PRELOAD_BY_LANGUAGE[language] ?? [];
|
|
361
361
|
return [PhiFontSubsetKey.Latin, ...extra].filter((key) => coverage.subsets.includes(key));
|
|
362
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* How a body above one part is divided, as a rule rather than as a Provider's private arithmetic.
|
|
365
|
+
*
|
|
366
|
+
* It lives here because two parties have to agree on it and neither can be told by the other in time. The
|
|
367
|
+
* Provider needs the part size to sign one request per part; the Client needs it *before* the plan exists,
|
|
368
|
+
* because a digest can only be signed into a request if it is known when the request is signed -- so the
|
|
369
|
+
* Client has to hash the same divisions the Provider is about to address. A rule both compute from the
|
|
370
|
+
* byte size is the only arrangement where that holds without an extra round trip.
|
|
371
|
+
*
|
|
372
|
+
* **Frozen once objects exist under it.** A `sha256-composite` value is a digest of part digests, so it
|
|
373
|
+
* answers "the same bytes divided the same way". Change the division and the same file yields a different
|
|
374
|
+
* value, which splits duplicate detection into cohorts that can never match across the change. The value
|
|
375
|
+
* carries the part size for exactly this reason: a changed rule produces non-matches, never false matches.
|
|
376
|
+
* Changing it is therefore a decision about the whole corpus, not a tuning knob.
|
|
377
|
+
*
|
|
378
|
+
* `PHIS_MEDIA_UPLOAD_MAX_PART_COUNT` is well under S3's ten thousand on purpose. Every part is signed up
|
|
379
|
+
* front, so the count is also the size of the plan a Client receives and Core holds in memory.
|
|
380
|
+
*/
|
|
381
|
+
export const PHIS_MEDIA_UPLOAD_PART_SIZE_BYTES = 16 * 1024 * 1024;
|
|
382
|
+
export const PHIS_MEDIA_UPLOAD_MAX_PART_COUNT = 1000;
|
|
383
|
+
/**
|
|
384
|
+
* The part size for one body, and the same answer wherever it is asked.
|
|
385
|
+
*
|
|
386
|
+
* Sixteen mebibytes until that would need more parts than are allowed, and from there the smallest whole
|
|
387
|
+
* mebibyte that fits the body into the allowance. Whole mebibytes keep the number short where it is
|
|
388
|
+
* recorded and keep the arithmetic exact in a browser, where sizes are doubles.
|
|
389
|
+
*/
|
|
390
|
+
export function resolvePhiMediaUploadPartSizeBytes(sizeBytes) {
|
|
391
|
+
const mebibyte = 1024 * 1024;
|
|
392
|
+
if (sizeBytes <= PHIS_MEDIA_UPLOAD_PART_SIZE_BYTES * PHIS_MEDIA_UPLOAD_MAX_PART_COUNT) {
|
|
393
|
+
return PHIS_MEDIA_UPLOAD_PART_SIZE_BYTES;
|
|
394
|
+
}
|
|
395
|
+
return Math.ceil(Math.ceil(sizeBytes / PHIS_MEDIA_UPLOAD_MAX_PART_COUNT) / mebibyte) * mebibyte;
|
|
396
|
+
}
|
|
397
|
+
/** How many parts one body is divided into under `resolvePhiMediaUploadPartSizeBytes`. */
|
|
398
|
+
export function resolvePhiMediaUploadPartCount(sizeBytes) {
|
|
399
|
+
return Math.max(1, Math.ceil(sizeBytes / resolvePhiMediaUploadPartSizeBytes(sizeBytes)));
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* The recorded form of a `sha256-composite` digest: the digest, the part count, the part size.
|
|
403
|
+
*
|
|
404
|
+
* Duplicate detection compares these values as strings, so everything that decides whether two of them
|
|
405
|
+
* mean the same thing has to be inside the string. The digest alone would not: the same bytes divided
|
|
406
|
+
* differently hash differently, and a value that did not say how it was divided would look like a plain
|
|
407
|
+
* SHA-256 that simply failed to match. Naming the division makes a mismatch legible instead of puzzling.
|
|
408
|
+
*/
|
|
409
|
+
export function formatPhiMediaCompositeChecksum(digestHex, partCount, partSizeBytes) {
|
|
410
|
+
return `${digestHex.toLowerCase()}-${partCount}-${partSizeBytes}`;
|
|
411
|
+
}
|
|
412
|
+
export function parsePhiMediaCompositeChecksum(value) {
|
|
413
|
+
const match = /^([0-9a-f]{64})-(\d{1,5})-(\d{1,12})$/u.exec(value.trim().toLowerCase());
|
|
414
|
+
if (!match)
|
|
415
|
+
return null;
|
|
416
|
+
const partCount = Number(match[2]);
|
|
417
|
+
const partSizeBytes = Number(match[3]);
|
|
418
|
+
if (partCount < 1 || partSizeBytes < 1)
|
|
419
|
+
return null;
|
|
420
|
+
return { digest: match[1], partCount, partSizeBytes };
|
|
421
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* References from a Phi record to something in another system: `@phis/server/resource-links:v1`.
|
|
3
|
+
*
|
|
4
|
+
* A ticket that is also a GitHub issue, a task that is also a pull request, a thread that is also a
|
|
5
|
+
* conversation somewhere else. The reference is a row of its own rather than a column on the record,
|
|
6
|
+
* because a record may have several at once, across several systems, and a column per system is a
|
|
7
|
+
* schema change per integration.
|
|
8
|
+
*
|
|
9
|
+
* The provider is the Add-on that wrote the link, and it is the whole of the ownership rule: an Add-on
|
|
10
|
+
* reads and writes its own links and sees no others. Two integrations pointing at one ticket therefore
|
|
11
|
+
* do not have to know about each other, and removing one takes its references with it.
|
|
12
|
+
*
|
|
13
|
+
* The link is also what an Add-on's actorless access is made of. A person who can see a record writes
|
|
14
|
+
* the link; from then on the Add-on may read and append to the conversation that record carries, and
|
|
15
|
+
* nothing beyond it. Unlinking ends that -- which is why `Synced` is stated per link rather than
|
|
16
|
+
* assumed: a reference that is only a pointer should not become a standing permission.
|
|
17
|
+
*/
|
|
18
|
+
/** What a link points away from. */
|
|
19
|
+
export type PhisResourceLinkSubject = {
|
|
20
|
+
kind: "thread";
|
|
21
|
+
id: number;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "ticket";
|
|
24
|
+
id: number;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "ticketTask";
|
|
27
|
+
id: number;
|
|
28
|
+
};
|
|
29
|
+
/** What is true of a link, as bit flags. */
|
|
30
|
+
export declare const PhisResourceLinkFlag: {
|
|
31
|
+
/**
|
|
32
|
+
* The two sides are kept in step, and this Add-on may reach the subject's thread without an actor.
|
|
33
|
+
*
|
|
34
|
+
* A link without it is a reference someone recorded: it is shown, it is followed, and it grants
|
|
35
|
+
* nothing.
|
|
36
|
+
*/
|
|
37
|
+
readonly Synced: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* One reference, as Core stores it.
|
|
41
|
+
*
|
|
42
|
+
* `type` and `state` are the provider's own vocabulary -- an issue, a pull request, open, merged -- and
|
|
43
|
+
* Core never interprets either. It stores them, indexes the external id against them, and hands them
|
|
44
|
+
* back; what `21` means is the Add-on's business, exactly as a role name is.
|
|
45
|
+
*/
|
|
46
|
+
export type PhisResourceLink = {
|
|
47
|
+
id: number;
|
|
48
|
+
subject: PhisResourceLinkSubject;
|
|
49
|
+
type: number;
|
|
50
|
+
externalId: string;
|
|
51
|
+
url: string | null;
|
|
52
|
+
state: number | null;
|
|
53
|
+
flags: number;
|
|
54
|
+
createdAt: string;
|
|
55
|
+
updatedAt: string;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The resource links capability: `@phis/server/resource-links:v1`.
|
|
59
|
+
*
|
|
60
|
+
* Bounded twice: to this Add-on's own links, and to subjects the acting user may reach. Writing needs
|
|
61
|
+
* an actor -- a link is a person saying these two things belong together, and it is what later lets the
|
|
62
|
+
* Add-on act with no person there. A hook or a job may read the links it wrote and may not make new
|
|
63
|
+
* ones.
|
|
64
|
+
*/
|
|
65
|
+
export type PhisResourceLinksCapabilityV1 = {
|
|
66
|
+
/** This Add-on's links on one subject, or none where the subject is out of reach. */
|
|
67
|
+
list(subject: PhisResourceLinkSubject): Promise<PhisResourceLink[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Records a reference, or hands back the one that is already there.
|
|
70
|
+
*
|
|
71
|
+
* Asking twice is how an id is learned, the way a declared group is: `(type, externalId)` is unique
|
|
72
|
+
* within this Add-on and this Site, so a second call answers with the first link instead of failing.
|
|
73
|
+
*/
|
|
74
|
+
link(input: {
|
|
75
|
+
subject: PhisResourceLinkSubject;
|
|
76
|
+
type: number;
|
|
77
|
+
externalId: string;
|
|
78
|
+
url?: string | null;
|
|
79
|
+
state?: number | null;
|
|
80
|
+
flags?: number;
|
|
81
|
+
}): Promise<PhisResourceLink>;
|
|
82
|
+
/** Moves a link's provider-local state. */
|
|
83
|
+
setState(input: {
|
|
84
|
+
linkId: number;
|
|
85
|
+
state: number | null;
|
|
86
|
+
}): Promise<boolean>;
|
|
87
|
+
/** Removes a link, and with it whatever actorless reach it granted. */
|
|
88
|
+
unlink(linkId: number): Promise<boolean>;
|
|
89
|
+
/** One link of this Add-on by the external id it recorded, or null. */
|
|
90
|
+
findByExternalId(input: {
|
|
91
|
+
type: number;
|
|
92
|
+
externalId: string;
|
|
93
|
+
}): Promise<PhisResourceLink | null>;
|
|
94
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* References from a Phi record to something in another system: `@phis/server/resource-links:v1`.
|
|
3
|
+
*
|
|
4
|
+
* A ticket that is also a GitHub issue, a task that is also a pull request, a thread that is also a
|
|
5
|
+
* conversation somewhere else. The reference is a row of its own rather than a column on the record,
|
|
6
|
+
* because a record may have several at once, across several systems, and a column per system is a
|
|
7
|
+
* schema change per integration.
|
|
8
|
+
*
|
|
9
|
+
* The provider is the Add-on that wrote the link, and it is the whole of the ownership rule: an Add-on
|
|
10
|
+
* reads and writes its own links and sees no others. Two integrations pointing at one ticket therefore
|
|
11
|
+
* do not have to know about each other, and removing one takes its references with it.
|
|
12
|
+
*
|
|
13
|
+
* The link is also what an Add-on's actorless access is made of. A person who can see a record writes
|
|
14
|
+
* the link; from then on the Add-on may read and append to the conversation that record carries, and
|
|
15
|
+
* nothing beyond it. Unlinking ends that -- which is why `Synced` is stated per link rather than
|
|
16
|
+
* assumed: a reference that is only a pointer should not become a standing permission.
|
|
17
|
+
*/
|
|
18
|
+
/** What is true of a link, as bit flags. */
|
|
19
|
+
export const PhisResourceLinkFlag = {
|
|
20
|
+
/**
|
|
21
|
+
* The two sides are kept in step, and this Add-on may reach the subject's thread without an actor.
|
|
22
|
+
*
|
|
23
|
+
* A link without it is a reference someone recorded: it is shown, it is followed, and it grants
|
|
24
|
+
* nothing.
|
|
25
|
+
*/
|
|
26
|
+
Synced: 1 << 0,
|
|
27
|
+
};
|
package/dist/site-groups.d.ts
CHANGED
|
@@ -36,3 +36,18 @@ export declare const PHI_GROUP_MEMBERSHIP_LEVELS: readonly [1, number, number, n
|
|
|
36
36
|
* Reading downwards is what makes an unknown higher bit fail closed rather than round up.
|
|
37
37
|
*/
|
|
38
38
|
export declare function readPhiGroupMembershipLevel(value: unknown): PhiGroupMembershipFlagValue | null;
|
|
39
|
+
/**
|
|
40
|
+
* A group a Module needs the Site to have, named rather than created.
|
|
41
|
+
*
|
|
42
|
+
* It travels in a Module's `seed` and is written when the Area carrying that Module is published. The
|
|
43
|
+
* key is the Module's own and never changes: it is what makes a second publish find the group it made
|
|
44
|
+
* the first time instead of making another. `provider_id` is the Module's id and is supplied by the
|
|
45
|
+
* materialization, not by the declaration, so a Module cannot claim a group belonging to another.
|
|
46
|
+
*
|
|
47
|
+
* The name is a starting value. Once the row exists it is the Site's, and administration renames it
|
|
48
|
+
* without the next publish undoing that.
|
|
49
|
+
*/
|
|
50
|
+
export type PhisDeclaredSiteGroup = {
|
|
51
|
+
readonly key: string;
|
|
52
|
+
readonly name: string;
|
|
53
|
+
};
|