initiative-plugin-sdk 2.0.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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +656 -0
  3. package/dist/build.d.ts +52 -0
  4. package/dist/build.js +229 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/cli.d.ts +24 -0
  7. package/dist/cli.js +160 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/client.d.ts +288 -0
  10. package/dist/client.js +539 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/contract.d.ts +1247 -0
  13. package/dist/contract.js +167 -0
  14. package/dist/contract.js.map +1 -0
  15. package/dist/define.d.ts +336 -0
  16. package/dist/define.js +120 -0
  17. package/dist/define.js.map +1 -0
  18. package/dist/dev.d.ts +28 -0
  19. package/dist/dev.js +147 -0
  20. package/dist/dev.js.map +1 -0
  21. package/dist/expression.d.ts +28 -0
  22. package/dist/expression.js +67 -0
  23. package/dist/expression.js.map +1 -0
  24. package/dist/init.d.ts +14 -0
  25. package/dist/init.js +52 -0
  26. package/dist/init.js.map +1 -0
  27. package/dist/keys.d.ts +85 -0
  28. package/dist/keys.js +109 -0
  29. package/dist/keys.js.map +1 -0
  30. package/dist/manifest.d.ts +8 -0
  31. package/dist/manifest.js +8 -0
  32. package/dist/manifest.js.map +1 -0
  33. package/dist/pack.d.ts +35 -0
  34. package/dist/pack.js +84 -0
  35. package/dist/pack.js.map +1 -0
  36. package/dist/plugin-api.generated.d.ts +4263 -0
  37. package/dist/plugin-api.generated.js +1019 -0
  38. package/dist/plugin-api.generated.js.map +1 -0
  39. package/dist/server.d.ts +75 -0
  40. package/dist/server.js +408 -0
  41. package/dist/server.js.map +1 -0
  42. package/dist/testing.d.ts +116 -0
  43. package/dist/testing.js +386 -0
  44. package/dist/testing.js.map +1 -0
  45. package/dist/tokens.d.ts +161 -0
  46. package/dist/tokens.js +215 -0
  47. package/dist/tokens.js.map +1 -0
  48. package/dist/validate.d.ts +86 -0
  49. package/dist/validate.js +997 -0
  50. package/dist/validate.js.map +1 -0
  51. package/dist/widget.d.ts +235 -0
  52. package/dist/widget.js +26 -0
  53. package/dist/widget.js.map +1 -0
  54. package/examples/minimal/README.md +48 -0
  55. package/examples/minimal/assets/avatar.png +0 -0
  56. package/examples/minimal/package.json +22 -0
  57. package/examples/minimal/src/plugin.ts +77 -0
  58. package/examples/minimal/src/widgets/open-issues.ts +11 -0
  59. package/examples/minimal/test/fixtures/issues-statistics.json +9 -0
  60. package/examples/minimal/test/issue-counts.test.ts +48 -0
  61. package/examples/minimal/tsconfig.json +14 -0
  62. package/manifest.contract.json +1886 -0
  63. package/package.json +92 -0
  64. package/schemas/plugin-manifest.json +1747 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Verifying the tokens Initiative signs when it reaches the plug-in.
3
+ *
4
+ * Both kinds are RS256 JWTs signed with the deployment's key and published in
5
+ * its JWKS at `/api/v1/plugin-platform/jwks.json`:
6
+ *
7
+ * - **Context token**, on every call to the plug-in (`Authorization: Bearer …`).
8
+ * Scope `endpoint` is a call to one endpoint, named by `endpoint_id`; scope
9
+ * `lifecycle` is a call to one hook, named by `hook`. When another plug-in made
10
+ * the call through Initiative it also carries `act` (that plug-in), `actor`,
11
+ * `member` and, when the caller was confined to one, `initiative_id`.
12
+ * - **Handoff token**, when a member opens one of the plug-in's surfaces. It names
13
+ * the member (`sub`), the surface, and the initiative it was opened in. It is
14
+ * for one use.
15
+ *
16
+ * Each is checked the same way: the `kid` against the deployment's JWKS, the
17
+ * signature, `iss` = `initiative`, `aud` = `initiative-plugin:<public id>`, and
18
+ * `exp`/`iat` against the clock with a small leeway.
19
+ */
20
+ import { type KeyObject } from "node:crypto";
21
+ import { type ActorKind } from "./contract.js";
22
+ /**
23
+ * What a context token authorizes.
24
+ *
25
+ * `endpoint` covers every call to an endpoint your plug-in declares; the id says
26
+ * which. `lifecycle` is Initiative calling one of your hooks about an
27
+ * installation; `hook` says which.
28
+ */
29
+ export type ContextScope = "endpoint" | "lifecycle";
30
+ /** Where the deployment publishes its verification keys. */
31
+ export declare const JWKS_PATH = "/api/v1/plugin-platform/jwks.json";
32
+ /** How long a fetched key set is reused before a refetch is considered. */
33
+ export declare const JWKS_CACHE_SECONDS = 300;
34
+ /** The `iss` every token from Initiative carries. */
35
+ export declare const INITIATIVE_ISSUER = "initiative";
36
+ /** `typ` on a call to an endpoint or a hook (RFC 8725 §3.11). */
37
+ export declare const CONTEXT_TOKEN_TYPE = "initiative-context+jwt";
38
+ /** `typ` on a page handoff. */
39
+ export declare const HANDOFF_TOKEN_TYPE = "initiative-handoff+jwt";
40
+ /** Claims every token from Initiative carries. */
41
+ export interface InitiativeTokenClaims {
42
+ jti: string;
43
+ iss: string;
44
+ aud: string;
45
+ iat: number;
46
+ exp: number;
47
+ /**
48
+ * The community, by the reference your installation knows it by. Stable for
49
+ * your installation, so it is what your own rows key on.
50
+ */
51
+ community_ref: string;
52
+ /** The installation within that community. */
53
+ plugin_install_id: number;
54
+ }
55
+ export interface ContextClaims extends InitiativeTokenClaims {
56
+ scope: ContextScope;
57
+ /** Which endpoint this call is for. Present when the scope is `endpoint`. */
58
+ endpoint_id?: string;
59
+ /** Which hook this call is for. Present when the scope is `lifecycle`. */
60
+ hook?: string;
61
+ /**
62
+ * Connection id → the opaque handle you ask Initiative for an access token
63
+ * with. Present only where the call depends on a connection. On a call from
64
+ * another plug-in it holds only what the actor may use: the member's own
65
+ * connections for a `member` call, the community's for an `installation`
66
+ * one.
67
+ */
68
+ connection_refs?: Record<string, string>;
69
+ /**
70
+ * Present when another plug-in made this call through Initiative: that plug-in's
71
+ * public id, as `act.sub` (RFC 8693 §4.1). Absent when Initiative itself
72
+ * called, for a widget.
73
+ */
74
+ act?: {
75
+ sub: string;
76
+ };
77
+ /**
78
+ * On a call from another plug-in: whose behalf it is on. `installation` is the
79
+ * community; `member` is the member named in {@link ContextClaims.member}.
80
+ */
81
+ actor?: ActorKind;
82
+ /**
83
+ * On a `member` call: the member, by the reference your installation knows
84
+ * them by. The calling plug-in never sees it.
85
+ */
86
+ member?: string;
87
+ /**
88
+ * On a call from another plug-in whose token is confined to one initiative: that
89
+ * initiative. Your plug-in is placed there too.
90
+ */
91
+ initiative_id?: number;
92
+ }
93
+ export interface HandoffClaims extends InitiativeTokenClaims {
94
+ /** The member, by their reference for your installation. */
95
+ sub: string;
96
+ /** Which of your surfaces was opened. */
97
+ surface_id: string;
98
+ /** The initiative it was opened in. Absent when opened for the whole community. */
99
+ initiative_id?: number;
100
+ /** Whether the member administers the community. */
101
+ community_admin?: boolean;
102
+ }
103
+ export declare class ContextTokenError extends Error {
104
+ }
105
+ /** The audience a token for `publicId` names. */
106
+ export declare function audienceFor(publicId: string): string;
107
+ /**
108
+ * Fetches and caches a deployment's published verification keys, by `kid`.
109
+ *
110
+ * An unknown `kid` refetches the set once, so a key rotation (which publishes
111
+ * both keys for a while) resolves without a restart. A second miss inside the
112
+ * same window does not refetch again.
113
+ */
114
+ export declare class JwksCache {
115
+ private readonly options;
116
+ private readonly cache;
117
+ constructor(options?: {
118
+ /** Injectable for tests and for a runtime with its own fetch. */
119
+ fetchImpl?: typeof fetch;
120
+ /** Milliseconds since the epoch. */
121
+ now?: () => number;
122
+ cacheSeconds?: number;
123
+ /** Which document to read. Defaults to {@link JWKS_PATH}. */
124
+ path?: string;
125
+ });
126
+ /** The key for `kid`, fetching the set if it is unknown or stale. */
127
+ keyFor(baseUrl: string, kid: string): Promise<KeyObject>;
128
+ private load;
129
+ }
130
+ export interface VerifyOptions {
131
+ /** Your plug-in's public id. The audience must name it. */
132
+ publicId: string;
133
+ /** The deployment, for key lookup: its origin or its API base. */
134
+ baseUrl: string;
135
+ jwks: JwksCache;
136
+ /** Defaults to {@link INITIATIVE_ISSUER}. */
137
+ issuer?: string;
138
+ /** Milliseconds since the epoch. */
139
+ now?: () => number;
140
+ /** Tolerance for clock skew, in seconds. Default 30. */
141
+ leewaySeconds?: number;
142
+ }
143
+ /** Verify a context token and return its claims. */
144
+ export declare function verifyContextToken(token: string, options: VerifyOptions): Promise<ContextClaims>;
145
+ /**
146
+ * Verify a handoff token and return its claims.
147
+ *
148
+ * Single use is yours to enforce: record `jti` until `exp` and refuse a token
149
+ * whose `jti` you have already seen.
150
+ */
151
+ export declare function verifyHandoffToken(token: string, options: VerifyOptions): Promise<HandoffClaims>;
152
+ /**
153
+ * Verify the token on a call to one of your hooks, and return its claims.
154
+ *
155
+ * Refuses anything but a `lifecycle` token, and, when `hook` is given, one
156
+ * minted for another hook: a token for `revoke` cannot be spent on
157
+ * `after_connect`.
158
+ */
159
+ export declare function verifyLifecycleToken(token: string, options: VerifyOptions & {
160
+ hook?: string;
161
+ }): Promise<ContextClaims>;
package/dist/tokens.js ADDED
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Verifying the tokens Initiative signs when it reaches the plug-in.
3
+ *
4
+ * Both kinds are RS256 JWTs signed with the deployment's key and published in
5
+ * its JWKS at `/api/v1/plugin-platform/jwks.json`:
6
+ *
7
+ * - **Context token**, on every call to the plug-in (`Authorization: Bearer …`).
8
+ * Scope `endpoint` is a call to one endpoint, named by `endpoint_id`; scope
9
+ * `lifecycle` is a call to one hook, named by `hook`. When another plug-in made
10
+ * the call through Initiative it also carries `act` (that plug-in), `actor`,
11
+ * `member` and, when the caller was confined to one, `initiative_id`.
12
+ * - **Handoff token**, when a member opens one of the plug-in's surfaces. It names
13
+ * the member (`sub`), the surface, and the initiative it was opened in. It is
14
+ * for one use.
15
+ *
16
+ * Each is checked the same way: the `kid` against the deployment's JWKS, the
17
+ * signature, `iss` = `initiative`, `aud` = `initiative-plugin:<public id>`, and
18
+ * `exp`/`iat` against the clock with a small leeway.
19
+ */
20
+ import { createPublicKey, createVerify } from "node:crypto";
21
+ import { ACTOR_KINDS } from "./contract.js";
22
+ /** Where the deployment publishes its verification keys. */
23
+ export const JWKS_PATH = "/api/v1/plugin-platform/jwks.json";
24
+ /** How long a fetched key set is reused before a refetch is considered. */
25
+ export const JWKS_CACHE_SECONDS = 300;
26
+ /** The `iss` every token from Initiative carries. */
27
+ export const INITIATIVE_ISSUER = "initiative";
28
+ /** `typ` on a call to an endpoint or a hook (RFC 8725 §3.11). */
29
+ export const CONTEXT_TOKEN_TYPE = "initiative-context+jwt";
30
+ /** `typ` on a page handoff. */
31
+ export const HANDOFF_TOKEN_TYPE = "initiative-handoff+jwt";
32
+ export class ContextTokenError extends Error {
33
+ }
34
+ /** The audience a token for `publicId` names. */
35
+ export function audienceFor(publicId) {
36
+ return `initiative-plugin:${publicId}`;
37
+ }
38
+ /**
39
+ * Fetches and caches a deployment's published verification keys, by `kid`.
40
+ *
41
+ * An unknown `kid` refetches the set once, so a key rotation (which publishes
42
+ * both keys for a while) resolves without a restart. A second miss inside the
43
+ * same window does not refetch again.
44
+ */
45
+ export class JwksCache {
46
+ options;
47
+ cache = new Map();
48
+ constructor(options = {}) {
49
+ this.options = options;
50
+ }
51
+ /** The key for `kid`, fetching the set if it is unknown or stale. */
52
+ async keyFor(baseUrl, kid) {
53
+ const document = new URL(this.options.path ?? JWKS_PATH, baseUrl).toString();
54
+ const entry = this.cache.get(document);
55
+ const now = this.options.now?.() ?? Date.now();
56
+ const ttl = (this.options.cacheSeconds ?? JWKS_CACHE_SECONDS) * 1000;
57
+ const fresh = entry !== undefined && now - entry.fetchedAt < ttl;
58
+ if (fresh) {
59
+ const cached = entry.keys.get(kid);
60
+ if (cached)
61
+ return cached;
62
+ if (entry.refetched)
63
+ return missing(document, entry.keys, kid);
64
+ }
65
+ const keys = await this.load(document);
66
+ this.cache.set(document, { keys, fetchedAt: now, refetched: fresh });
67
+ const found = keys.get(kid);
68
+ if (!found)
69
+ return missing(document, keys, kid);
70
+ return found;
71
+ }
72
+ async load(document) {
73
+ const doFetch = this.options.fetchImpl ?? fetch;
74
+ const response = await doFetch(document);
75
+ if (response.status === 404)
76
+ return new Map();
77
+ if (!response.ok) {
78
+ throw new ContextTokenError(`jwks fetch failed with ${response.status}`);
79
+ }
80
+ const parsed = (await response.json());
81
+ const keys = new Map();
82
+ for (const jwk of parsed.keys ?? []) {
83
+ if (jwk.kty !== "RSA" || !jwk.kid || !jwk.n || !jwk.e)
84
+ continue;
85
+ keys.set(jwk.kid, createPublicKey({ key: jwk, format: "jwk" }));
86
+ }
87
+ return keys;
88
+ }
89
+ }
90
+ /** The same refusal for "no keys published" and "no such key". */
91
+ function missing(document, keys, kid) {
92
+ throw new ContextTokenError(keys.size === 0
93
+ ? `${document} published no keys`
94
+ : `no verification key published for kid ${kid}`);
95
+ }
96
+ /** Verify a context token and return its claims. */
97
+ export async function verifyContextToken(token, options) {
98
+ const claims = (await verifyInitiativeToken(token, options, CONTEXT_TOKEN_TYPE));
99
+ if (claims.scope !== "endpoint" && claims.scope !== "lifecycle") {
100
+ throw new ContextTokenError(`not a context token (scope ${String(claims.scope)})`);
101
+ }
102
+ checkCaller(claims);
103
+ return claims;
104
+ }
105
+ /**
106
+ * The claims naming another plug-in's call, checked for shape: an `act` names the
107
+ * caller, an `actor` is one of the two kinds, and a `member` call names its
108
+ * member.
109
+ */
110
+ function checkCaller(claims) {
111
+ if (claims.act !== undefined) {
112
+ const act = claims.act;
113
+ if (typeof act !== "object" ||
114
+ act === null ||
115
+ typeof act.sub !== "string" ||
116
+ !act.sub) {
117
+ throw new ContextTokenError("act names no calling plug-in");
118
+ }
119
+ }
120
+ if (claims.actor !== undefined && !ACTOR_KINDS.includes(claims.actor)) {
121
+ throw new ContextTokenError(`unknown actor ${String(claims.actor)}`);
122
+ }
123
+ if (claims.actor === "member" && (typeof claims.member !== "string" || !claims.member)) {
124
+ throw new ContextTokenError("a member call names no member");
125
+ }
126
+ if (claims.initiative_id !== undefined &&
127
+ (!Number.isInteger(claims.initiative_id) || claims.initiative_id <= 0)) {
128
+ throw new ContextTokenError("initiative_id is not an initiative");
129
+ }
130
+ }
131
+ /**
132
+ * Verify a handoff token and return its claims.
133
+ *
134
+ * Single use is yours to enforce: record `jti` until `exp` and refuse a token
135
+ * whose `jti` you have already seen.
136
+ */
137
+ export async function verifyHandoffToken(token, options) {
138
+ const claims = (await verifyInitiativeToken(token, options, HANDOFF_TOKEN_TYPE));
139
+ if (typeof claims.sub !== "string" || !claims.sub) {
140
+ throw new ContextTokenError("handoff token names no member");
141
+ }
142
+ if (typeof claims.surface_id !== "string" || !claims.surface_id) {
143
+ throw new ContextTokenError("handoff token names no surface");
144
+ }
145
+ return claims;
146
+ }
147
+ /**
148
+ * Verify the token on a call to one of your hooks, and return its claims.
149
+ *
150
+ * Refuses anything but a `lifecycle` token, and, when `hook` is given, one
151
+ * minted for another hook: a token for `revoke` cannot be spent on
152
+ * `after_connect`.
153
+ */
154
+ export async function verifyLifecycleToken(token, options) {
155
+ const claims = await verifyContextToken(token, options);
156
+ if (claims.scope !== "lifecycle") {
157
+ throw new ContextTokenError(`not a lifecycle token (scope ${String(claims.scope)})`);
158
+ }
159
+ if (options.hook !== undefined && claims.hook !== options.hook) {
160
+ throw new ContextTokenError(`token is for hook ${String(claims.hook)}, not ${options.hook}`);
161
+ }
162
+ return claims;
163
+ }
164
+ /** Type, signature, issuer, audience and time: everything the kinds share. */
165
+ async function verifyInitiativeToken(token, options, typ) {
166
+ const parts = token.split(".");
167
+ if (parts.length !== 3) {
168
+ throw new ContextTokenError("not a JWT");
169
+ }
170
+ const [rawHeader, rawPayload, rawSignature] = parts;
171
+ const header = decodeJson(rawHeader);
172
+ if (header.typ !== typ) {
173
+ throw new ContextTokenError(`token is typed ${String(header.typ)}, not ${typ}`);
174
+ }
175
+ if (header.alg !== "RS256") {
176
+ throw new ContextTokenError(`unexpected algorithm ${header.alg}`);
177
+ }
178
+ if (!header.kid) {
179
+ throw new ContextTokenError("token names no key");
180
+ }
181
+ const key = await options.jwks.keyFor(options.baseUrl, header.kid);
182
+ const verifier = createVerify("RSA-SHA256");
183
+ verifier.update(`${rawHeader}.${rawPayload}`);
184
+ verifier.end();
185
+ if (!verifier.verify(key, Buffer.from(rawSignature, "base64url"))) {
186
+ throw new ContextTokenError("signature did not verify");
187
+ }
188
+ const claims = decodeJson(rawPayload);
189
+ const expected = audienceFor(options.publicId);
190
+ if (claims.aud !== expected) {
191
+ throw new ContextTokenError(`token is for ${claims.aud}, not ${expected}`);
192
+ }
193
+ const issuer = options.issuer ?? INITIATIVE_ISSUER;
194
+ if (claims.iss !== issuer) {
195
+ throw new ContextTokenError(`token is from ${claims.iss}, not ${issuer}`);
196
+ }
197
+ const seconds = Math.floor((options.now?.() ?? Date.now()) / 1000);
198
+ const leeway = options.leewaySeconds ?? 30;
199
+ if (typeof claims.exp !== "number" || claims.exp + leeway < seconds) {
200
+ throw new ContextTokenError("token has expired");
201
+ }
202
+ if (typeof claims.iat === "number" && claims.iat - leeway > seconds) {
203
+ throw new ContextTokenError("token is not valid yet");
204
+ }
205
+ return claims;
206
+ }
207
+ function decodeJson(segment) {
208
+ try {
209
+ return JSON.parse(Buffer.from(segment, "base64url").toString("utf-8"));
210
+ }
211
+ catch {
212
+ throw new ContextTokenError("not a JWT");
213
+ }
214
+ }
215
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,eAAe,EAAE,YAAY,EAAkB,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,WAAW,EAAkB,MAAM,eAAe,CAAC;AAW5D,4DAA4D;AAC5D,MAAM,CAAC,MAAM,SAAS,GAAG,mCAAmC,CAAC;AAE7D,2EAA2E;AAC3E,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAEtC,qDAAqD;AACrD,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAC;AAE9C,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAC3D,+BAA+B;AAC/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAkE3D,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,OAAO,qBAAqB,QAAQ,EAAE,CAAC;AACzC,CAAC;AAgBD;;;;;;GAMG;AACH,MAAM,OAAO,SAAS;IAID,OAAO;IAHT,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEvD,YACmB,OAAO,GAQpB,EAAE;uBARW,OAAO;IASvB,CAAC;IAEJ,qEAAqE;IACrE,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,GAAW;QACvC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,SAAS,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC7E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,kBAAkB,CAAC,GAAG,IAAI,CAAC;QACrE,MAAM,KAAK,GAAG,KAAK,KAAK,SAAS,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC;QAEjE,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,MAAM,GAAG,KAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAC1B,IAAI,KAAM,CAAC,SAAS;gBAAE,OAAO,OAAO,CAAC,QAAQ,EAAE,KAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK;YAAE,OAAO,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,KAAK,CAAC,IAAI,CAAC,QAAgB;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;QAChD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,GAAG,EAAE,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,iBAAiB,CAAC,0BAA0B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAC3E,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAqB,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAqB,CAAC;QAC1C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;YACpC,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAAE,SAAS;YAChE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,CAAC,EAAE,GAAG,EAAE,GAAY,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,kEAAkE;AAClE,SAAS,OAAO,CAAC,QAAgB,EAAE,IAA0B,EAAE,GAAW;IACxE,MAAM,IAAI,iBAAiB,CACzB,IAAI,CAAC,IAAI,KAAK,CAAC;QACb,CAAC,CAAC,GAAG,QAAQ,oBAAoB;QACjC,CAAC,CAAC,yCAAyC,GAAG,EAAE,CACnD,CAAC;AACJ,CAAC;AAgBD,oDAAoD;AACpD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAa,EACb,OAAsB;IAEtB,MAAM,MAAM,GAAG,CAAC,MAAM,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAkB,CAAC;IAClG,IAAI,MAAM,CAAC,KAAK,KAAK,UAAU,IAAI,MAAM,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;QAChE,MAAM,IAAI,iBAAiB,CAAC,8BAA8B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrF,CAAC;IACD,WAAW,CAAC,MAAM,CAAC,CAAC;IACpB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,MAAqB;IACxC,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAc,CAAC;QAClC,IACE,OAAO,GAAG,KAAK,QAAQ;YACvB,GAAG,KAAK,IAAI;YACZ,OAAQ,GAAyB,CAAC,GAAG,KAAK,QAAQ;YAClD,CAAE,GAAuB,CAAC,GAAG,EAC7B,CAAC;YACD,MAAM,IAAI,iBAAiB,CAAC,8BAA8B,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,iBAAiB,CAAC,iBAAiB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACvF,MAAM,IAAI,iBAAiB,CAAC,+BAA+B,CAAC,CAAC;IAC/D,CAAC;IACD,IACE,MAAM,CAAC,aAAa,KAAK,SAAS;QAClC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,MAAM,CAAC,aAAa,IAAI,CAAC,CAAC,EACtE,CAAC;QACD,MAAM,IAAI,iBAAiB,CAAC,oCAAoC,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAa,EACb,OAAsB;IAEtB,MAAM,MAAM,GAAG,CAAC,MAAM,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAkB,CAAC;IAClG,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAClD,MAAM,IAAI,iBAAiB,CAAC,+BAA+B,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAChE,MAAM,IAAI,iBAAiB,CAAC,gCAAgC,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAa,EACb,OAA0C;IAE1C,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACxD,IAAI,MAAM,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;QACjC,MAAM,IAAI,iBAAiB,CAAC,gCAAgC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/D,MAAM,IAAI,iBAAiB,CACzB,qBAAqB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,IAAI,EAAE,CAChE,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,KAAK,UAAU,qBAAqB,CAClC,KAAa,EACb,OAAsB,EACtB,GAAW;IAEX,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;IACD,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC;IAEpD,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAiD,CAAC;IACrF,IAAI,MAAM,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,iBAAiB,CAAC,kBAAkB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,iBAAiB,CAAC,wBAAwB,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAChB,MAAM,IAAI,iBAAiB,CAAC,oBAAoB,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACnE,MAAM,QAAQ,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAC5C,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,CAAC;IACf,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,iBAAiB,CAAC,0BAA0B,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAA0B,CAAC;IAC/D,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,MAAM,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,iBAAiB,CAAC,gBAAgB,MAAM,CAAC,GAAG,SAAS,QAAQ,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,iBAAiB,CAAC;IACnD,IAAI,MAAM,CAAC,GAAG,KAAK,MAAM,EAAE,CAAC;QAC1B,MAAM,IAAI,iBAAiB,CAAC,iBAAiB,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;IAC3C,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,GAAG,MAAM,GAAG,OAAO,EAAE,CAAC;QACpE,MAAM,IAAI,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,GAAG,MAAM,GAAG,OAAO,EAAE,CAAC;QACpE,MAAM,IAAI,iBAAiB,CAAC,wBAAwB,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,OAAe;IACjC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Checking a manifest before a deployment does.
3
+ *
4
+ * **Schema-valid is necessary, not sufficient.** Some rules are not expressible
5
+ * in JSON Schema and are checked by the platform on publish: cross-references
6
+ * (the endpoint a widget binds, a `requires` term's connection, an endpoint's
7
+ * service prefix), the features/blocks cross-check in both directions, UTF-8
8
+ * byte-size caps, the rules tying a connection's `flow` and `token` to its
9
+ * scope and fields, what a vendor `setup` writes to, and the bounds and unique
10
+ * ids of `schedules`.
11
+ *
12
+ * {@link validateManifest} runs the schema and then every one of those except
13
+ * the byte caps. It also reports every term the contract does not declare: a
14
+ * deployment drops such a term rather than refusing the manifest, so a
15
+ * misspelt or retired field would otherwise do nothing without saying so. And
16
+ * it adds one rule the platform does not check: an endpoint's `identity` must
17
+ * name returns that endpoint actually sends.
18
+ *
19
+ * For a declarative plug-in it also parses every JSONata expression, and checks
20
+ * that a plug-in is one kind or the other, what its requests and steps name, and
21
+ * the hosts it calls.
22
+ */
23
+ import { type Manifest } from "./contract.js";
24
+ /** Where a plug-in serves its manifest document. */
25
+ export declare const MANIFEST_PATH = "/.well-known/initiative-plugin.json";
26
+ /** The wire protocol this SDK speaks. */
27
+ export declare const PLUGIN_PROTOCOL_VERSION = 1;
28
+ /**
29
+ * The document served at {@link MANIFEST_PATH}, of which {@link Manifest} is one
30
+ * field: the plug-in's identity beside what it declares it can do. A registrar
31
+ * refuses anything without a `protocol_version`, a `public_id`, a `kind` and a
32
+ * `definition`.
33
+ */
34
+ export interface PluginDocument {
35
+ protocol_version: number;
36
+ /** `<publisher>.<slug>`, the same id `definition.service.public_id` carries. */
37
+ public_id: string;
38
+ kind: "plugin";
39
+ /** The catalog id: publisher-assigned, immutable, never reused. */
40
+ uid?: string;
41
+ name?: string;
42
+ definition: Manifest;
43
+ }
44
+ /**
45
+ * The document to serve at {@link MANIFEST_PATH}. Serve the same bytes every
46
+ * time: a deployment hashes what it fetches and re-checks it.
47
+ */
48
+ export declare function pluginDocument(manifest: Manifest, options?: {
49
+ uid?: string;
50
+ name?: string;
51
+ }): PluginDocument;
52
+ /**
53
+ * Check a whole served document — the envelope, then the manifest inside it.
54
+ *
55
+ * {@link validateManifest} checks what a plug-in declares; this checks what a
56
+ * registrar will actually fetch. Use it on the bytes you serve.
57
+ */
58
+ export declare function validateDocument(document: unknown): ValidationProblem[];
59
+ /** The generated schema, read from disk once. */
60
+ export declare function manifestSchema(): Record<string, unknown>;
61
+ export interface ValidationProblem {
62
+ /** A JSON Pointer-ish path into the manifest. */
63
+ where: string;
64
+ message: string;
65
+ }
66
+ /**
67
+ * Everything this side can check: the schema, then the two cross-cutting rules
68
+ * it cannot express.
69
+ *
70
+ * The schema runs first and short-circuits. A manifest whose shape is wrong
71
+ * produces cascading nonsense from the reference checks — "binds 'undefined',
72
+ * which is not a declared read endpoint" when the real answer is "endpoints
73
+ * must be an array" — so the structural answer is worth giving alone.
74
+ *
75
+ * An empty array does not promise the platform will accept it — see the module
76
+ * note — but a non-empty one is a definite refusal, so this is worth running in
77
+ * CI and before a publish.
78
+ *
79
+ * A declarative plug-in's manifest has no `service` block to name the plug-in, so its
80
+ * endpoint ids are checked against `publicId` when one is given.
81
+ */
82
+ export declare function validateManifest(manifest: unknown, options?: {
83
+ publicId?: string;
84
+ }): ValidationProblem[];
85
+ /** Every `{name}` in a template, in order. */
86
+ export declare function templateNames(value: string): string[];