@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/NOTICE
CHANGED
package/README.md
CHANGED
|
@@ -1,202 +1,210 @@
|
|
|
1
1
|
# @phis/contracts
|
|
2
2
|
|
|
3
|
-
The agreements
|
|
4
|
-
|
|
3
|
+
The agreements phis (`@phis/server`) keeps with what surrounds it: the site UI (`@phis/ui`), Sites, and
|
|
4
|
+
separately shipped Add-ons. Each lives under its own subpath, because they hold between different parties
|
|
5
|
+
and change at different moments.
|
|
5
6
|
|
|
6
7
|
```
|
|
7
|
-
@phis/contracts/addon
|
|
8
|
-
@phis/contracts/access
|
|
9
|
-
@phis/contracts/signals
|
|
10
|
-
@phis/contracts/catalog
|
|
11
|
-
@phis/contracts/cms
|
|
8
|
+
@phis/contracts/addon what phis and a separately shipped Add-on promise each other
|
|
9
|
+
@phis/contracts/access the authorization vocabulary phis and @phis/ui both evaluate
|
|
10
|
+
@phis/contracts/signals the signal vocabulary and address grammar the UI declares in and phis validates
|
|
11
|
+
@phis/contracts/catalog the Module categories an Add-on declares, the UI groups by and a market filters on
|
|
12
|
+
@phis/contracts/cms the CMS node identity both sides derive, and must derive alike
|
|
13
|
+
@phis/contracts/media the Asset vocabularies both sides store, filter on and judge
|
|
14
|
+
@phis/contracts/http the request headers phis and the site UI must spell the same way
|
|
15
|
+
@phis/contracts/locale the locale this software is authored in
|
|
16
|
+
@phis/contracts/logging the structured log vocabulary a Site writes and phis reads back
|
|
17
|
+
@phis/contracts/server-capabilities the capability snapshot phis reports and the UI acts on
|
|
18
|
+
@phis/contracts/site-groups the membership levels of a Site group, as both ends count them
|
|
19
|
+
@phis/contracts/user-state the shapes a Module may keep a person's decisions in, and the key grammar
|
|
12
20
|
```
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
two of our packages happen to share; a subpath makes you say which agreement you mean.
|
|
22
|
+
## Before the v1 release, interface versions stay at 1
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
`PHIS_SERVICE_INTERFACE_VERSIONS` and `PHIS_SERVICE_INTERFACE_DIGESTS` in `@phis/contracts/addon` are not
|
|
25
|
+
raised while phis is pre-v1, however much an interface changes. The mechanism they drive is real and
|
|
26
|
+
deliberate -- Core refuses an Add-on whose recorded manifest names a digest this release does not offer --
|
|
27
|
+
and that is exactly why raising a version now is the wrong move: there is no released Add-on to protect, so
|
|
28
|
+
it buys no compatibility, and it takes the installation down. The recorded manifest in
|
|
29
|
+
`config/phis-addons.json` stops matching, the capability snapshot refuses the desired state, and every
|
|
30
|
+
request that resolves a viewer answers 500 until each installed Add-on has been rebundled and upgraded.
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
An interface that changes before v1 is simply the interface. Both sides are rebuilt from one tree, and the
|
|
33
|
+
version is raised once, when there is something in the field that a wrong assumption would break.
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
against; it must not depend on phi-server itself.
|
|
35
|
+
There is deliberately no root export. A package you can import from the top invites everything two of
|
|
36
|
+
our packages happen to share; a subpath makes you say which agreement you mean.
|
|
25
37
|
|
|
26
38
|
```sh
|
|
27
|
-
npm install
|
|
39
|
+
npm install @phis/contracts
|
|
28
40
|
```
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
time, apart from a handful of constants.
|
|
42
|
+
## `/addon`
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
The types and constants an Add-on declares or implements. Types and constants only: no runtime code, no
|
|
45
|
+
`server-only`, no React, no Next.
|
|
46
|
+
|
|
47
|
+
An Add-on is compiled to a single ESM artifact and installed into a running phis through the `phis` CLI;
|
|
48
|
+
it is never built into the server. This subpath is the only thing an Add-on compiles against, and it must
|
|
49
|
+
not depend on phis itself. For an Add-on author it is a build-time dependency: everything it exports is
|
|
50
|
+
erased at compile time apart from a handful of constants.
|
|
51
|
+
|
|
52
|
+
What it covers:
|
|
53
|
+
|
|
54
|
+
- **Manifest** -- `PhisAddonManifestV1`: identity, version, required Core capabilities, the routes an
|
|
55
|
+
Add-on claims, the services it provides.
|
|
56
|
+
- **Runtime** -- `PhisAddonRuntimeV1`, `PhisAddonHandler`, `PhisAddonRequestContext`. The artifact
|
|
57
|
+
exports it under the name `phisAddon` (`PhisAddonRuntimeModuleV1`).
|
|
58
|
+
- **Schema** -- `PhisAddonSchemaDescriptor` and its column, index and constraint descriptors. Tables are
|
|
59
|
+
declared, not migrated by hand; phis applies them.
|
|
60
|
+
- **Service kinds** -- the Core-owned kinds an Add-on may supply an implementation of: Media Storage
|
|
61
|
+
(`PhisMediaStorageAdapter`, upload plans, object I/O) and Directory. `PHIS_SERVICE_KINDS` names them
|
|
62
|
+
and `PHIS_SERVICE_INTERFACE_DIGESTS` holds the digest of each kind's current interface; Core refuses an
|
|
63
|
+
Add-on built against a different digest at install.
|
|
64
|
+
- **Core capabilities** -- `PHIS_CORE_CAPABILITIES`, the capability ids an Add-on may require. Declare
|
|
65
|
+
against these constants rather than spelling the ids out. The list is the vocabulary and may reserve a
|
|
66
|
+
name before Core delivers it (`resource-links`, `support`); an Add-on that requires a capability this
|
|
67
|
+
release does not deliver is refused rather than handed a missing object.
|
|
68
|
+
|
|
69
|
+
`PHIS_ADDON_ABI_VERSION` is the single number phis checks an Add-on against.
|
|
70
|
+
|
|
71
|
+
How an Add-on is laid out, compiled, installed, activated and upgraded is described in phis's
|
|
72
|
+
[`ADDON_HOWTO.md`](https://github.com/phis-dev/phis-server/blob/main/ADDON_HOWTO.md); the rules behind it
|
|
73
|
+
are in [`SERVER_ADDONS.md`](https://github.com/phis-dev/phis-server/blob/main/SERVER_ADDONS.md).
|
|
34
74
|
|
|
35
|
-
|
|
36
|
-
runtime code, and it is a real dependency of both packages that use it.
|
|
75
|
+
## `/access`
|
|
37
76
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
phi-server is not reachable without a round trip. Neither can defer to the other, so both evaluate,
|
|
41
|
-
and one compiled source is the only way both can agree.
|
|
77
|
+
Claim shapes, policy shapes, and the evaluator that decides them. Unlike `/addon`, this is runtime code
|
|
78
|
+
and a real dependency of both packages that use it.
|
|
42
79
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
80
|
+
Two processes decide the same question. phis decides it in its guards and API routes; a Site decides it
|
|
81
|
+
while rendering -- per navigation entry, per tree node, and in the browser, where phis is not reachable
|
|
82
|
+
without a round trip. Neither can defer to the other, so both evaluate, and one compiled source is the
|
|
83
|
+
only way both reach the same answer: a stored claim normalised on one side and not on the other admits
|
|
84
|
+
on one and refuses on the other, and neither copy looks wrong on its own.
|
|
46
85
|
|
|
47
|
-
Each side keeps its own viewer type and passes a projection onto `PhiAccessSubject`, so neither
|
|
48
|
-
|
|
86
|
+
Each side keeps its own viewer type and passes a projection onto `PhiAccessSubject`, so neither package
|
|
87
|
+
adopts the other's shape.
|
|
49
88
|
|
|
50
89
|
## `/cms`
|
|
51
90
|
|
|
52
91
|
The encoding of a CMS node's identity: how a Preset node's id is derived from the Module, the preset and
|
|
53
92
|
the node key, and how a draft node's id is derived from the revision it was made in.
|
|
54
93
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
94
|
+
It is arithmetic both sides must compute alike. A Preset id is derived rather than allocated, so the site
|
|
95
|
+
UI can address a node before any row exists and phis can check that stored wiring points at nodes that
|
|
96
|
+
really exist. A changed constant on one side produces ids that are still well formed and simply name
|
|
97
|
+
nothing. The Page-key convention and the map helper built on top stay with `@phis/ui`.
|
|
59
98
|
|
|
60
|
-
|
|
61
|
-
`hashPresetIdentity` identical line for line. Nothing would have announced a divergence. A changed
|
|
62
|
-
constant on one side produces ids that are still well formed and simply name nothing: Pages stop
|
|
63
|
-
resolving, drafts point at nodes that are not there, and both copies look correct on their own.
|
|
99
|
+
## `/signals`
|
|
64
100
|
|
|
65
|
-
The
|
|
66
|
-
|
|
101
|
+
The closed vocabularies a Widget's wiring is written in -- scopes, actions, value types -- the shape of a
|
|
102
|
+
value schema name, and the address families.
|
|
67
103
|
|
|
68
|
-
|
|
104
|
+
A Widget declares what it emits and listens for, the Builder stores that, phis validates it on the way
|
|
105
|
+
in, and the Site delivers it at runtime, so all of them parse the same strings. The rule that says which
|
|
106
|
+
receivers a scope admits, and the controller address the Site scope privileges, live here for the same
|
|
107
|
+
reason.
|
|
69
108
|
|
|
70
|
-
|
|
71
|
-
|
|
109
|
+
Which schemas exist stays with the UI. phis checks that a JSON signal names *a* schema, never which one:
|
|
110
|
+
`isPhiSignalValueSchemaShape` checks the shape, not membership. The UI asks the stricter question because
|
|
111
|
+
it holds the Module registry; phis must not, or it would refuse every third party's schema.
|
|
72
112
|
|
|
73
|
-
|
|
74
|
-
the way in. Two lists, one meaning; and when they were two lists they drifted. `@phis/ui` had grown
|
|
75
|
-
`date`, `time` and `length`; phi-server had not. A length control's change signal -- from a widget the
|
|
76
|
-
same release shipped -- was refused on save with "valueType is invalid".
|
|
113
|
+
## `/catalog`
|
|
77
114
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
said so in its own comment: it "mirrors the grammar in @phis/ui", because it could not import it. With
|
|
82
|
-
them the rule that says which receivers a scope admits, which had been written out on both sides in
|
|
83
|
-
different words, and the one controller address the Site scope privileges, which is only worth
|
|
84
|
-
anything if both sides name the same string.
|
|
115
|
+
What a Module is for, as one closed list: `foundation`, `workspace`, `content`, `media`, `commerce`,
|
|
116
|
+
`identity`, `communication`, `events`, `analytics`, `integration`, `operations`, `other`
|
|
117
|
+
(`PHI_RUNTIME_MODULE_CATEGORIES`).
|
|
85
118
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
every third party's schema for never having heard of it.
|
|
119
|
+
An Add-on declares it per Module, because a package may ship a shop and a report and neither answer would
|
|
120
|
+
be true of the other. The site UI groups the Modules page by it, and a marketplace Add-on -- which
|
|
121
|
+
compiles against this package and nothing else -- filters on it.
|
|
90
122
|
|
|
91
|
-
|
|
123
|
+
It is not in `/addon` on purpose: `/addon` is the ABI a third party compiles against, and this list grows
|
|
124
|
+
with the product. phis does not ask the membership question either; a manifest carries categories as
|
|
125
|
+
plain strings, Core checks the shape, and the strict question is asked where a registry is held. The
|
|
126
|
+
labels an operator reads are label-set keys owned by the UI; this subpath holds identifiers only.
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
`
|
|
128
|
+
Beside the vocabulary sits the shape a Module package declares itself in, under `phis` in its
|
|
129
|
+
`package.json`: which Modules it carries, each one's category, and the language their titles and
|
|
130
|
+
descriptions are written in. Reading that from `package.json` means nobody has to execute a stranger's
|
|
131
|
+
package to list it. The author writes it; `phis module` checks that the entries are present and well
|
|
132
|
+
formed, and a package that declares nothing is refused at intake.
|
|
95
133
|
|
|
96
|
-
|
|
97
|
-
neither answer would be true of the other. The site UI groups the Modules page by it, and a Module may
|
|
98
|
-
read another's category and show it.
|
|
134
|
+
## `/media`
|
|
99
135
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
offering's category is still free text -- but it is why this list is a contract.
|
|
136
|
+
The words and numbers a Site and phis must read the same way about an Asset: the Space kind's wire name,
|
|
137
|
+
the media kinds, the Folder flags, the presentation flags, the image variant keys, and the two closed
|
|
138
|
+
vocabularies that decide deliverability -- `lifecycle_status` and `delivery_policy`.
|
|
104
139
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
question is asked where a registry is actually held. The same split as `/signals`.
|
|
140
|
+
These values travel and are judged on the other side. The site UI sends a flag mask as a list filter and
|
|
141
|
+
flag values when metadata is saved, and phis filters with `presentation_flags & mask <> 0` and refuses a
|
|
142
|
+
value it does not know. An Asset payload carries `lifecycleStatus` and `deliveryPolicy` as stored, and
|
|
143
|
+
the UI decides from them whether `next/image` may be pointed at the original.
|
|
110
144
|
|
|
111
|
-
The
|
|
112
|
-
|
|
145
|
+
The resolvers come with their vocabulary: `normalizePhiMediaKind` and `resolvePhiMediaKindFromContentType`
|
|
146
|
+
map onto the kind list, so an upload is filed and listed as the same kind on both sides. How a delivery
|
|
147
|
+
URL is built stays with each side, and phis's numeric Space kind never reaches a Site.
|
|
113
148
|
|
|
114
|
-
|
|
115
|
-
package.json: which Modules it carries, each one's category, and the language their titles and
|
|
116
|
-
descriptions are written in. A Module is compiled UI code, so reading that out of it means executing a
|
|
117
|
-
stranger's package -- inside the `phis` CLI, or inside a marketplace taking a submission. Neither
|
|
118
|
-
should, and a registry serves package.json without anybody fetching a tarball.
|
|
149
|
+
## `/http`
|
|
119
150
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
151
|
+
The request header names phis reads and the site UI writes: `PHIS_TOKEN_HEADER` (`x-phis-token`),
|
|
152
|
+
`PHIS_SITE_KEY_HEADER`, `PHIS_AREA_HEADER`, `PHIS_REQUEST_PATH_HEADER` and `PHIS_REQUEST_SEARCH_HEADER`.
|
|
153
|
+
A disagreement here is not a type error but a 403 or 400 at runtime.
|
|
123
154
|
|
|
124
|
-
|
|
125
|
-
entries are present and well formed; whether `events` is the right answer for a given Module is a
|
|
126
|
-
judgement only the author holds. A package that declares nothing, or declares it wrongly, is refused at
|
|
127
|
-
intake rather than listed on a guess.
|
|
155
|
+
## `/locale`
|
|
128
156
|
|
|
129
|
-
|
|
157
|
+
`PHI_CANONICAL_SOURCE_LOCALE`: the locale every label, email template and global message is authored in,
|
|
158
|
+
and therefore the one a translation starts from. It is not a Site's default locale, which is per-Site
|
|
159
|
+
configuration.
|
|
130
160
|
|
|
131
|
-
|
|
132
|
-
wire name, the media kinds, the Folder flags, the presentation flags, the image variant keys, and the
|
|
133
|
-
two closed vocabularies that decide deliverability -- `lifecycle_status` and `delivery_policy`.
|
|
134
|
-
|
|
135
|
-
It is here because the values themselves travel and are then judged on the other side. The site UI
|
|
136
|
-
sends a flag mask as a list filter and an array of flag values when metadata is saved, and phis answers
|
|
137
|
-
the filter with `presentation_flags & mask <> 0` and refuses a value it does not know. An Asset's
|
|
138
|
-
payload carries `lifecycleStatus` and `deliveryPolicy` as the numbers they are in the row, and the UI
|
|
139
|
-
decides from them whether `next/image` may be pointed at the original.
|
|
140
|
-
|
|
141
|
-
What made it worth moving is what the copies did while nobody looked. Both sides kept their own table,
|
|
142
|
-
and phis still carried `Private`, `Archived` and `Restricted` long after lifecycle and delivery had
|
|
143
|
-
become columns of their own -- so the set of values its metadata route accepted was not the set the UI
|
|
144
|
-
offered. Nothing failed loudly; a drifting copy of a number never does.
|
|
145
|
-
|
|
146
|
-
The resolvers come with their vocabulary. `normalizePhiMediaKind` and
|
|
147
|
-
`resolvePhiMediaKindFromContentType` are total functions onto the kind list, and a content type that
|
|
148
|
-
lands on `document` in the browser and on `other` on the server files an upload as one thing and lists
|
|
149
|
-
it as another.
|
|
150
|
-
|
|
151
|
-
What stays outside: how a delivery URL is built, which is each side's own business, and phis's
|
|
152
|
-
`smallint` Space kind, which never reaches a Site -- the wire carries the name instead.
|
|
153
|
-
|
|
154
|
-
## What `/addon` covers
|
|
155
|
-
|
|
156
|
-
- **Manifest** — `PhisAddonManifestV1`: identity, version, required core capabilities,
|
|
157
|
-
the routes an Add-on claims, the services it provides.
|
|
158
|
-
- **Runtime** — `PhisAddonRuntimeV1`, `PhisAddonHandler`,
|
|
159
|
-
`PhisAddonRequestContext`: the shape the artifact's default export must have.
|
|
160
|
-
- **Schema** — `PhisAddonSchemaDescriptor` and its column, index, and constraint
|
|
161
|
-
descriptors. Tables are declared, not migrated by hand; phi-server applies them.
|
|
162
|
-
- **Service kinds** — the interfaces an Add-on may provide or consume, currently the media
|
|
163
|
-
storage adapter (`PhisMediaStorageAdapter`, upload plans, object I/O).
|
|
164
|
-
- **What Core offers** — `PHIS_CORE_CAPABILITIES`, the capability ids an Add-on may
|
|
165
|
-
require; `PHIS_SERVICE_KINDS`, the service kinds Core owns; and
|
|
166
|
-
`PHIS_SERVICE_INTERFACE_DIGESTS`, the digest of each kind's current interface. Declare
|
|
167
|
-
against these names rather than spelling them out: Core refuses an Add-on whose digest is not
|
|
168
|
-
the one this release offers, and a literal cannot be checked before you ship it.
|
|
169
|
-
|
|
170
|
-
This list is a promise, not a plan. A capability appears here when Core delivers it, never
|
|
171
|
-
before.
|
|
172
|
-
|
|
173
|
-
`PHIS_ADDON_ABI_VERSION` is the single number a server checks an Add-on against.
|
|
161
|
+
## `/logging`
|
|
174
162
|
|
|
175
|
-
|
|
163
|
+
The structured log vocabulary: `PhiLogService` (`phis`, `ui`, `site`, `cli`), `PhiLogLevel`,
|
|
164
|
+
`PhiLoggerContext`, `PhiLogEvent` and the `PhiLogger` interface. A Site and phis both write these records
|
|
165
|
+
to journald, and the log surface in phis parses them back, filtering by exactly these services and
|
|
166
|
+
fields. How a line is serialised is each writer's own concern.
|
|
176
167
|
|
|
177
|
-
|
|
168
|
+
## `/server-capabilities`
|
|
178
169
|
|
|
179
|
-
|
|
180
|
-
|
|
170
|
+
The snapshot `/api/v1/site/capabilities` reports: per provider (Core or an Add-on), the capabilities it
|
|
171
|
+
offers and its state (`PhiCapabilitySnapshot`, `PhiCapabilityProvider`, `PhiCapabilityState`). phis
|
|
172
|
+
decides the verdict; the site UI acts on it, deactivating a Module whose provider is not `available` and
|
|
173
|
+
naming the reason in its diagnostics.
|
|
181
174
|
|
|
182
|
-
|
|
183
|
-
in its own process. Not what both happen to use — a date formatter used on both sides is shared
|
|
184
|
-
convenience, not a contract, and belongs to neither.
|
|
175
|
+
## `/site-groups`
|
|
185
176
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
The membership ladder of a Site group: `PhiGroupMembershipFlags` (`Member`, `Author`, `Editor`,
|
|
178
|
+
`Manager`, cumulative bits), `PHI_GROUP_MEMBERSHIP_LEVELS` and `readPhiGroupMembershipLevel`. The site UI
|
|
179
|
+
sends `membershipFlags` as an integer and offers the levels to choose from, so both ends must count the
|
|
180
|
+
same bits. The Add-on boundary names levels instead (`PHIS_GROUP_LEVELS` in `/addon`).
|
|
181
|
+
|
|
182
|
+
## `/user-state`
|
|
183
|
+
|
|
184
|
+
What a Module may keep per Site about a person who decided something: the closed shape set
|
|
185
|
+
(`PHIS_USER_STATE_SHAPES` -- `flag`, `marker`, `value`, `set`), the `<package>/<key>` grammar, the
|
|
186
|
+
declaration a Module makes (`PhisDeclarableUserStateKey`), and `readPhisUserStateWrite`, which both ends
|
|
187
|
+
call so a refused write is refused the same way in both. phis stores it and is the only party that
|
|
188
|
+
validates; the site UI declares the keys and reads them while rendering. Core's own account facts stay
|
|
189
|
+
bit flags on the membership and account rows and are not this.
|
|
190
|
+
|
|
191
|
+
## Admission rule
|
|
189
192
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
to have written twice: a copy is only a contract when disagreeing about it changes what a request
|
|
193
|
-
returns.
|
|
193
|
+
Per subpath, and narrow on purpose. Name the two parties and the sentence they promise each other; if that
|
|
194
|
+
sentence cannot be written down, it does not go in here.
|
|
194
195
|
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
- `/addon`: only what phis and a separately shipped Add-on must agree on. Declarations, never
|
|
197
|
+
implementations.
|
|
198
|
+
- `/access`: only what phis and `@phis/ui` must both evaluate, each in its own process. A helper both
|
|
199
|
+
happen to use is convenience, not a contract.
|
|
200
|
+
- `/catalog`: only vocabularies an Add-on declares a Module in and another party reads back -- closed
|
|
201
|
+
lists of identifiers, never the words an operator sees.
|
|
202
|
+
- `/media`, `/http`, `/logging`, `/site-groups`, `/server-capabilities`: only what crosses the wire as a
|
|
203
|
+
value and is judged on the other side. A copy is a contract when disagreeing about it changes what a
|
|
204
|
+
request returns.
|
|
205
|
+
- `/user-state`: only the shapes and the key grammar both ends check a write against. What a Module keeps
|
|
206
|
+
under its keys is the Module's business and never named here.
|
|
197
207
|
|
|
198
|
-
##
|
|
208
|
+
## License
|
|
199
209
|
|
|
200
|
-
|
|
201
|
-
upgraded — lives in phi-server's `ADDON_HOWTO.md`, with the design rationale in
|
|
202
|
-
`SERVER_ADDONS.md`.
|
|
210
|
+
Apache-2.0. See `LICENSE` and `NOTICE`.
|
package/dist/access.d.ts
CHANGED
|
@@ -29,6 +29,19 @@ export declare const PhiBaseRole: {
|
|
|
29
29
|
readonly Developer: number;
|
|
30
30
|
readonly Supporter: number;
|
|
31
31
|
readonly Accountant: number;
|
|
32
|
+
/**
|
|
33
|
+
* Works here, and needs nothing else.
|
|
34
|
+
*
|
|
35
|
+
* It grants no capability at all, which is what it is for: being Staff decides who may be written to
|
|
36
|
+
* and who may open a thread with a colleague, and until this bit existed the only way to say it was to
|
|
37
|
+
* hand out a role that also grants something -- a developer bit to somebody who never touches a log.
|
|
38
|
+
*
|
|
39
|
+
* Staff is whoever holds *any* Core role, so every role above implies it and this is the one to give
|
|
40
|
+
* somebody who holds none of them. Roles an Add-on defines never make anybody Staff: they are
|
|
41
|
+
* provider-local and say what a person may do inside that Add-on, which is how somebody outside the
|
|
42
|
+
* company can hold one and remain outside it.
|
|
43
|
+
*/
|
|
44
|
+
readonly Staff: number;
|
|
32
45
|
};
|
|
33
46
|
/**
|
|
34
47
|
* What a stored email address says about itself, as bit flags.
|
package/dist/access.js
CHANGED
|
@@ -29,6 +29,19 @@ export const PhiBaseRole = {
|
|
|
29
29
|
Developer: 1 << 4,
|
|
30
30
|
Supporter: 1 << 5,
|
|
31
31
|
Accountant: 1 << 6,
|
|
32
|
+
/**
|
|
33
|
+
* Works here, and needs nothing else.
|
|
34
|
+
*
|
|
35
|
+
* It grants no capability at all, which is what it is for: being Staff decides who may be written to
|
|
36
|
+
* and who may open a thread with a colleague, and until this bit existed the only way to say it was to
|
|
37
|
+
* hand out a role that also grants something -- a developer bit to somebody who never touches a log.
|
|
38
|
+
*
|
|
39
|
+
* Staff is whoever holds *any* Core role, so every role above implies it and this is the one to give
|
|
40
|
+
* somebody who holds none of them. Roles an Add-on defines never make anybody Staff: they are
|
|
41
|
+
* provider-local and say what a person may do inside that Add-on, which is how somebody outside the
|
|
42
|
+
* company can hold one and remain outside it.
|
|
43
|
+
*/
|
|
44
|
+
Staff: 1 << 7,
|
|
32
45
|
};
|
|
33
46
|
/**
|
|
34
47
|
* What a stored email address says about itself, as bit flags.
|
package/dist/addon-index.d.ts
CHANGED
|
@@ -4,4 +4,8 @@ export * from "./capabilities.js";
|
|
|
4
4
|
export * from "./core.js";
|
|
5
5
|
export * from "./media-storage.js";
|
|
6
6
|
export * from "./queries.js";
|
|
7
|
+
export * from "./resource-links.js";
|
|
7
8
|
export * from "./schema.js";
|
|
9
|
+
export * from "./support.js";
|
|
10
|
+
export * from "./threads.js";
|
|
11
|
+
export * from "./translation.js";
|
package/dist/addon-index.js
CHANGED
|
@@ -4,4 +4,8 @@ export * from "./capabilities.js";
|
|
|
4
4
|
export * from "./core.js";
|
|
5
5
|
export * from "./media-storage.js";
|
|
6
6
|
export * from "./queries.js";
|
|
7
|
+
export * from "./resource-links.js";
|
|
7
8
|
export * from "./schema.js";
|
|
9
|
+
export * from "./support.js";
|
|
10
|
+
export * from "./threads.js";
|
|
11
|
+
export * from "./translation.js";
|
package/dist/addon.d.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* no node builtins -- the Add-on states, and Core checks. If something here ever does more than hand
|
|
12
12
|
* back a constant, it is in the wrong package.
|
|
13
13
|
*/
|
|
14
|
-
import type { PhisAddonCapabilities, PhisAddonJobHandler, PhisSiteAccess } from "./capabilities.js";
|
|
15
|
-
import type { PhisCoreCapabilityId, PhisServiceKind } from "./core.js";
|
|
14
|
+
import type { PhisAddonCapabilities, PhisAddonEventHandler, PhisAddonJobHandler, PhisSiteAccess } from "./capabilities.js";
|
|
15
|
+
import type { PhisAddonEventId, PhisCoreCapabilityId, PhisServiceKind } from "./core.js";
|
|
16
16
|
import type { PhisAddonQueryCatalog } from "./queries.js";
|
|
17
17
|
import type { PhisAddonSchemaDescriptor } from "./schema.js";
|
|
18
18
|
export declare const PHIS_ADDON_MANIFEST_VERSION: 1;
|
|
@@ -30,6 +30,28 @@ export type PhisAddonApiRouteDescriptor = {
|
|
|
30
30
|
rateLimitClass?: string;
|
|
31
31
|
timeoutMs?: number;
|
|
32
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* How Core establishes that a hook request is from who it claims to be.
|
|
35
|
+
*
|
|
36
|
+
* A hook is unauthenticated by construction, and until Core could check a signature itself, "verified"
|
|
37
|
+
* was a thing each handler asserted about itself -- so the rule that a request with no actor may not
|
|
38
|
+
* write had to hold for every hook, including the ones that had in fact proved their caller. Stating the
|
|
39
|
+
* check here moves it in front of the handler: Core verifies over the raw body, and a handler that runs
|
|
40
|
+
* has already been vouched for.
|
|
41
|
+
*
|
|
42
|
+
* `secret` names one of this Add-on's own declared secrets, so the value an operator set is never in the
|
|
43
|
+
* manifest. `deliveryIdHeader` names the header the provider identifies a delivery with; where it is
|
|
44
|
+
* given, Core refuses a delivery it has already accepted, which is what makes a webhook retried after a
|
|
45
|
+
* timeout harmless.
|
|
46
|
+
*/
|
|
47
|
+
export type PhisAddonHookVerificationDescriptor = {
|
|
48
|
+
scheme: "hmac-sha256";
|
|
49
|
+
header: string;
|
|
50
|
+
/** What the header value is prefixed with, where it is -- GitHub sends `sha256=`. */
|
|
51
|
+
prefix?: string;
|
|
52
|
+
secret: string;
|
|
53
|
+
deliveryIdHeader?: string;
|
|
54
|
+
};
|
|
33
55
|
export type PhisAddonHookDescriptor = {
|
|
34
56
|
id: string;
|
|
35
57
|
method: PhisAddonHttpMethod;
|
|
@@ -38,6 +60,14 @@ export type PhisAddonHookDescriptor = {
|
|
|
38
60
|
bodyLimitBytes: number;
|
|
39
61
|
rateLimitClass: string;
|
|
40
62
|
timeoutMs: number;
|
|
63
|
+
/**
|
|
64
|
+
* How this hook's caller is checked, or absent where it cannot be.
|
|
65
|
+
*
|
|
66
|
+
* Absent is not a lesser variant of present: an unverified hook writes nothing at all -- not through
|
|
67
|
+
* `data:v1`, not by opening a thread -- because nothing distinguishes its caller from anybody who
|
|
68
|
+
* found the address. A hook that needs to write declares how it can be believed.
|
|
69
|
+
*/
|
|
70
|
+
verification?: PhisAddonHookVerificationDescriptor;
|
|
41
71
|
/**
|
|
42
72
|
* Whether this hook is told which Site it was called for, through `x-phis-site-key`.
|
|
43
73
|
*
|
|
@@ -155,6 +185,18 @@ export type PhisAddonGroupDescriptor = {
|
|
|
155
185
|
name: string;
|
|
156
186
|
description?: string;
|
|
157
187
|
};
|
|
188
|
+
/**
|
|
189
|
+
* One event this Add-on wants to be told about.
|
|
190
|
+
*
|
|
191
|
+
* Declared like a route: Core reaches the handler by the name the runtime exports it under, so nothing
|
|
192
|
+
* registers itself and what an Add-on listens to is readable before it runs. Several handlers may name
|
|
193
|
+
* the same event; each is delivered separately and retried separately.
|
|
194
|
+
*/
|
|
195
|
+
export type PhisAddonEventHandlerDescriptor = {
|
|
196
|
+
id: string;
|
|
197
|
+
event: PhisAddonEventId;
|
|
198
|
+
handler: string;
|
|
199
|
+
};
|
|
158
200
|
export type PhisAddonManifestV1 = {
|
|
159
201
|
manifestVersion: typeof PHIS_ADDON_MANIFEST_VERSION;
|
|
160
202
|
addonId: string;
|
|
@@ -167,6 +209,14 @@ export type PhisAddonManifestV1 = {
|
|
|
167
209
|
apiRoutes: PhisAddonApiRouteDescriptor[];
|
|
168
210
|
hooks: PhisAddonHookDescriptor[];
|
|
169
211
|
jobs: PhisAddonJobDescriptor[];
|
|
212
|
+
/**
|
|
213
|
+
* The events this Add-on wants delivered. Absent means none.
|
|
214
|
+
*
|
|
215
|
+
* Optional like every field added after this manifest version was in the wild. Declaring one is not
|
|
216
|
+
* receiving it: an event reaches this Add-on only for a thread it holds a synced link to, so an
|
|
217
|
+
* Add-on that declares them and links nothing is told nothing.
|
|
218
|
+
*/
|
|
219
|
+
eventHandlers?: PhisAddonEventHandlerDescriptor[];
|
|
170
220
|
/**
|
|
171
221
|
* What an operator may configure. Absent means nothing is.
|
|
172
222
|
*
|
|
@@ -296,6 +346,7 @@ export type PhisAddonRuntimeV1 = {
|
|
|
296
346
|
hookHandlers?: Readonly<Record<string, PhisAddonHandler>>;
|
|
297
347
|
serviceFactories?: Readonly<Record<string, PhisAddonServiceFactory>>;
|
|
298
348
|
jobHandlers?: Readonly<Record<string, PhisAddonJobHandler>>;
|
|
349
|
+
eventHandlers?: Readonly<Record<string, PhisAddonEventHandler>>;
|
|
299
350
|
};
|
|
300
351
|
export type PhisAddonRuntimeModuleV1 = {
|
|
301
352
|
phisAddon: PhisAddonRuntimeV1;
|