@ngis/plugin-sdk 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 (70) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +31 -0
  3. package/dist/index.cjs +76 -0
  4. package/dist/index.d.ts +77 -0
  5. package/dist/index.mjs +102 -0
  6. package/dist/src/api/analysis.d.ts +140 -0
  7. package/dist/src/api/resources.d.ts +530 -0
  8. package/dist/src/api/scenes.d.ts +251 -0
  9. package/dist/src/api/users.d.ts +22 -0
  10. package/dist/src/domain/algorithms/deck-layer-style.d.ts +34 -0
  11. package/dist/src/domain/algorithms/maplibre-style.d.ts +63 -0
  12. package/dist/src/domain/algorithms/raster-color-ramps.d.ts +37 -0
  13. package/dist/src/domain/algorithms/scene-layer-type.d.ts +25 -0
  14. package/dist/src/domain/algorithms/symbol-compile.d.ts +100 -0
  15. package/dist/src/domain/algorithms/symbol-params.d.ts +90 -0
  16. package/dist/src/domain/map/bounds.d.ts +8 -0
  17. package/dist/src/domain/map/constants.d.ts +40 -0
  18. package/dist/src/domain/map/layer-builders.d.ts +32 -0
  19. package/dist/src/domain/map/rendered-layers.d.ts +30 -0
  20. package/dist/src/domain/map/style-ready.d.ts +24 -0
  21. package/dist/src/domain/map/tile-url.d.ts +20 -0
  22. package/dist/src/domain/map/types.d.ts +128 -0
  23. package/dist/src/domain/models/deck-layer-style.d.ts +211 -0
  24. package/dist/src/domain/models/scene-layer-type.d.ts +23 -0
  25. package/dist/src/domain/models/symbol.d.ts +424 -0
  26. package/dist/src/lib/api.d.ts +82 -0
  27. package/dist/src/lib/auth-session.d.ts +22 -0
  28. package/dist/src/lib/raster-color-layer/RasterColorLayer.d.ts +48 -0
  29. package/dist/src/lib/raster-color-layer/TileGrid.d.ts +31 -0
  30. package/dist/src/lib/raster-color-layer/colorRamp.d.ts +18 -0
  31. package/dist/src/lib/raster-color-layer/shaders/raster-color.frag.d.ts +2 -0
  32. package/dist/src/lib/raster-color-layer/shaders/raster-color.vert.d.ts +2 -0
  33. package/dist/src/lib/raster-color-layer/zoomInterpolate.d.ts +2 -0
  34. package/dist/src/sdk/commands.d.ts +113 -0
  35. package/dist/src/sdk/context/keys.d.ts +148 -0
  36. package/dist/src/sdk/context/publishers.d.ts +198 -0
  37. package/dist/src/sdk/context/store.d.ts +78 -0
  38. package/dist/src/sdk/context/when.d.ts +69 -0
  39. package/dist/src/sdk/contributions/dispatch.d.ts +78 -0
  40. package/dist/src/sdk/contributions/panel-id.d.ts +25 -0
  41. package/dist/src/sdk/contributions/registry.d.ts +199 -0
  42. package/dist/src/sdk/contributions/symbol-renderers.d.ts +107 -0
  43. package/dist/src/sdk/contributions/types.d.ts +288 -0
  44. package/dist/src/sdk/contributions/views-bridge.d.ts +92 -0
  45. package/dist/src/sdk/define-plugin.d.ts +17 -0
  46. package/dist/src/sdk/facets/storage.d.ts +152 -0
  47. package/dist/src/sdk/facets/types.d.ts +370 -0
  48. package/dist/src/sdk/host-api.d.ts +256 -0
  49. package/dist/src/sdk/map-api.d.ts +288 -0
  50. package/dist/src/sdk/panels.d.ts +130 -0
  51. package/dist/src/sdk/plugin.d.ts +145 -0
  52. package/dist/src/sdk/rail-tools.d.ts +58 -0
  53. package/dist/src/sdk/scopes.d.ts +158 -0
  54. package/dist/src/systems/renderer/DeckSceneRenderer.d.ts +26 -0
  55. package/dist/src/systems/renderer/FlowFieldLayer.d.ts +68 -0
  56. package/dist/src/systems/renderer/LayerRendererRegistry.d.ts +132 -0
  57. package/dist/src/systems/renderer/authenticated-deck-load.d.ts +4 -0
  58. package/dist/src/systems/renderer/deck/deck-layer-builders.d.ts +29 -0
  59. package/dist/src/systems/renderer/deck/deck-layer-helpers.d.ts +21 -0
  60. package/dist/src/systems/renderer/deck/mvt-point-aggregation-layer.d.ts +107 -0
  61. package/dist/src/systems/renderer/flow-field/flow-field-resources.d.ts +84 -0
  62. package/dist/src/systems/renderer/flow-field/flow-field-shaders.d.ts +4 -0
  63. package/dist/src/systems/renderer/symbol/SpriteRegistry.d.ts +34 -0
  64. package/dist/src/systems/renderer/symbol/entries.d.ts +67 -0
  65. package/dist/src/types/analysis.d.ts +205 -0
  66. package/dist/src/types/common.d.ts +17 -0
  67. package/dist/src/types/extensions.d.ts +560 -0
  68. package/dist/src/types/gis.d.ts +242 -0
  69. package/dist/src/types/market-social.d.ts +378 -0
  70. package/package.json +47 -0
@@ -0,0 +1,560 @@
1
+ /**
2
+ * DTOs for `ngis-extension` — the Stage 11 extension registry / market, its
3
+ * per-user installs, scene-level composition, and the boot / scene-resolve
4
+ * payloads the plugin loader consumes.
5
+ *
6
+ * Field names are the **wire** names; nothing is renamed to taste. Java
7
+ * `Instant` serializes as an ISO-8601 `string`; Java `JsonNode` maps to
8
+ * `unknown` unless the shape is genuinely frozen.
9
+ *
10
+ * Contract: `docs/extension_loader_stage/README.md` § "W1 kickoff — locked
11
+ * contracts" (W1.1 / W1.2) and `frontend-api-guide.md` §16.
12
+ *
13
+ * **Stage 15 additions are additive only.** The market's *social* shapes live in
14
+ * `./market-social` (kickoff-authored, read-only to a wave); the two members
15
+ * imported below are referenced from the existing DTOs rather than re-declared,
16
+ * so there is exactly one source of truth per wire shape.
17
+ */
18
+ import type { RatingAggregateDto, VersionScanResultDto } from "./market-social";
19
+ /**
20
+ * Only `"ui-plugin"` ever has a loadable bundle. `"tool"` and `"ai-skill"`
21
+ * entries carry `bundleUrl`/`integrity` values that are *meaningless rather
22
+ * than absent* — always filter on `kind` before attempting a load, a
23
+ * truthiness check on `bundleUrl` is not enough.
24
+ */
25
+ export type ExtensionKind = "ui-plugin" | "tool" | "ai-skill";
26
+ export type ExtensionStatus = "ACTIVE" | "DEPRECATED" | "REMOVED";
27
+ /** `GROUP` exists on the backend; v1 renders it read-only (no group-grant UI). */
28
+ export type ExtensionVisibility = "PRIVATE" | "GROUP" | "PUBLIC";
29
+ /**
30
+ * Extension *version* lifecycle. Deliberately distinct from
31
+ * {@link SkillVersionStatus} — a skill version reaching `ACTIVE` and its
32
+ * `ai-skill` extension version reaching `PUBLISHED` are independent events.
33
+ */
34
+ export type ExtensionVersionStatus = "DRAFT" | "IN_REVIEW" | "PUBLISHED" | "REJECTED" | "YANKED";
35
+ /** `PINNED` requires a strict `x.y.z` version; `ai-skill` entries must be pinned. */
36
+ export type VersionPolicy = "LATEST" | "PINNED";
37
+ export type PrincipalType = "USER" | "GROUP";
38
+ /** `GET /api/extensions?scope=` */
39
+ export type CatalogScope = "mine" | "shared" | "public";
40
+ /**
41
+ * Why a scene-composed extension could not be resolved for the current viewer.
42
+ * `null` when the entry is available.
43
+ *
44
+ * The tool side used to collapse three distinct failures into the single value
45
+ * `reference_revoked` (`ExtensionKindReferenceValidator.java:106-112`). W2's T1
46
+ * splits them into `tool_missing` / `tool_disabled` / `tool_forbidden`, and
47
+ * routes a geoanalysis outage to the existing `dependency_unavailable` via a
48
+ * new `dependencyAvailable` component on `ToolReferenceStatus`.
49
+ *
50
+ * **`reference_revoked` stays in the union** (W2.1): it stops being emitted but
51
+ * older clients and any cached payload may still carry it, so neither the type
52
+ * nor its `Market.reasons.reference_revoked` key is removed this wave.
53
+ *
54
+ * The naming asymmetry is deliberate — `tool_disabled` names the tool's actual
55
+ * `enabled` field where the skill side's `skill_not_active` names its `status`
56
+ * column. Do not harmonize one into the other.
57
+ *
58
+ * Because every UI looks these up through `Market.reasons.*`, the rename stayed
59
+ * a one-file change.
60
+ */
61
+ export type SceneResolveReason = "extension_forbidden" | "no_published_version" | "extension_version_yanked" | "version_policy_required" | "skill_missing" | "skill_not_active" | "skill_forbidden" | "dependency_unavailable" | "tool_missing" | "tool_disabled" | "tool_forbidden"
62
+ /** Legacy tool-side value — no longer emitted as of T1, kept for one wave. */
63
+ | "reference_revoked";
64
+ export interface ExtensionSummaryDto {
65
+ extId: string;
66
+ kind: ExtensionKind;
67
+ ownerId: string;
68
+ displayName: string;
69
+ summary: string | null;
70
+ iconKey: string | null;
71
+ category: string | null;
72
+ /** Free-form `JsonNode` on the wire — do not assume `string[]`. */
73
+ tags: unknown;
74
+ visibility: ExtensionVisibility;
75
+ status: ExtensionStatus;
76
+ latestPublishedVersion: string | null;
77
+ /**
78
+ * The **latest PUBLISHED version's** declared scopes (Stage 13 XB1) — what
79
+ * the market card renders. Empty when nothing is published.
80
+ *
81
+ * Optional because a payload from a gateway predating XB1 omits it entirely,
82
+ * and "the server did not say" must stay distinguishable from "asks for
83
+ * nothing": the card renders no scope line at all in the first case.
84
+ */
85
+ permissions?: string[];
86
+ /**
87
+ * ZB1's additive social members (F15.6/F15.11), shipped on the wire 2026-08-11.
88
+ *
89
+ * **Optional, and deliberately so.** The server always sends them now, but a
90
+ * summary reconstructed from a pre-Stage-15 cached payload — or from a gateway
91
+ * that has not been redeployed — omits them, and "the server did not say" must
92
+ * stay distinguishable from "zero installs, unrated, unverified". Render the
93
+ * absent case as no line at all, exactly as `permissions` already does.
94
+ *
95
+ * `rating` is `null` until the first rating and its `mean` is `null` whenever
96
+ * `count` is 0 — **never render a 0 mean; there is no such score.**
97
+ */
98
+ installCount?: number;
99
+ rating?: RatingAggregateDto | null;
100
+ publisherVerified?: boolean;
101
+ }
102
+ export interface ExtensionVersionSummaryDto {
103
+ /** Strict `x.y.z`. Contrast with a skill version, which is an integer. */
104
+ version: string;
105
+ status: ExtensionVersionStatus;
106
+ minNgisVersion: string;
107
+ bundleSize: number | null;
108
+ bundleSha256: string | null;
109
+ publishedAt: string | null;
110
+ createdAt: string;
111
+ /**
112
+ * This version's declared scopes (Stage 13 XB1) — **per version**, which is
113
+ * what makes a scope escalation between two versions visible from the
114
+ * detail page without fetching each one. Optional for the same reason as
115
+ * {@link ExtensionSummaryDto.permissions}.
116
+ */
117
+ permissions?: string[];
118
+ }
119
+ /** `GET /api/extensions/{extId}` — note the nesting, this is not a flat summary. */
120
+ export interface ExtensionDetailDto {
121
+ extension: ExtensionSummaryDto;
122
+ versions: ExtensionVersionSummaryDto[];
123
+ }
124
+ /** Populated only for `tool` / `ai-skill` kinds; every field is nullable. */
125
+ export interface ExtensionReferenceDto {
126
+ provider: string | null;
127
+ toolId: string | null;
128
+ toolVersion: number | null;
129
+ skillId: string | null;
130
+ /** Skill versions are integers. */
131
+ skillVersion: number | null;
132
+ }
133
+ export interface ExtensionVersionDetailDto {
134
+ extId: string;
135
+ version: string;
136
+ status: ExtensionVersionStatus;
137
+ /** The package manifest as stored; shape-checked by the loader (F6), not here. */
138
+ manifest: PackageManifestDto | null;
139
+ minNgisVersion: string;
140
+ capabilitySummary: unknown;
141
+ /** Fixed to `"bundle.mjs"` in v1. */
142
+ entry: string | null;
143
+ bundleSize: number | null;
144
+ bundleSha256: string | null;
145
+ changelog: string | null;
146
+ submittedBy: string | null;
147
+ reviewedBy: string | null;
148
+ reviewNote: string | null;
149
+ publishedAt: string | null;
150
+ createdAt: string;
151
+ reference: ExtensionReferenceDto | null;
152
+ /** This version's declared scopes (Stage 13 XB1) — a projection of
153
+ * `manifest.permissions`, not separate storage. */
154
+ permissions?: string[];
155
+ }
156
+ /**
157
+ * The `manifest.json` / boot-payload `manifest` shape.
158
+ *
159
+ * **F1 types it; F1 does not validate it** — validation belongs to F6 at load
160
+ * time, against the bundle actually being executed.
161
+ */
162
+ export interface PackageManifestDto {
163
+ /** Matches `^[a-z0-9-]+(\.[a-z0-9-]+)+$`; `ngis.*` is reserved first-party. */
164
+ id: string;
165
+ kind: ExtensionKind;
166
+ name: string;
167
+ /** Strict `x.y.z`. */
168
+ version: string;
169
+ /** Strict `x.y.z`. */
170
+ minNgisVersion: string;
171
+ /** Fixed to `"bundle.mjs"` in v1. */
172
+ entry: string;
173
+ capabilities?: {
174
+ panels?: unknown;
175
+ railTools?: unknown;
176
+ layerTypes?: unknown;
177
+ /** E7 — `{id, paramsSchema}[]`, consumed by the codeenv plugin bridge. */
178
+ commands?: unknown;
179
+ };
180
+ description?: string;
181
+ icon?: string;
182
+ category?: string;
183
+ tags?: unknown;
184
+ homepage?: string;
185
+ publisher?: string;
186
+ changelog?: string;
187
+ }
188
+ export interface ExtensionInstallDto {
189
+ extId: string;
190
+ enabled: boolean;
191
+ versionPolicy: VersionPolicy;
192
+ pinnedVersion: string | null;
193
+ installedAt: string;
194
+ updatedAt: string | null;
195
+ /**
196
+ * Scopes the installer consented to (Stage 13 XB2, V4's `granted_scopes`).
197
+ * `null` for an install row that predates consent, which is **not** the same
198
+ * as `[]` — the former has never been asked, the latter refused everything.
199
+ *
200
+ * **Never trust this as the effective grant set (`V-85`).** The server
201
+ * intersects with the resolved version's `permissions` when it *writes*, and
202
+ * does not recompute when a later version **narrows** them, so a stale scope
203
+ * outlives the declaration that justified it. X5 intersects at read time; see
204
+ * `src/sdk/scopes.ts`.
205
+ */
206
+ grantedScopes: string[] | null;
207
+ /** The user-leg storage blob (F13.7). Free-form; the schema is the plugin's. */
208
+ settings?: unknown;
209
+ }
210
+ /**
211
+ * `GET|PUT /api/extensions/{extId}/settings` (Stage 13 XB2, W2 kickoff §8).
212
+ *
213
+ * The **user** storage leg of F13.7 — per (user, extension), backed by
214
+ * `ngis_extension_install.settings` (`JSONB`, V4). `settings` is free-form JSON
215
+ * and deliberately typed `unknown`: the settings *schema* lives in the
216
+ * extension's manifest, not in this DTO, and this client never normalizes it.
217
+ *
218
+ * **The envelope is asymmetric, and settled** (XB2, confirmed after the W2
219
+ * kickoff): the `PUT` takes the settings object **bare** as its body and
220
+ * answers `{"settings": <stored>}` — enveloped *out*, bare *in*. Both verbs
221
+ * `404` when the caller has no install row (and for an unknown `extId`), and
222
+ * `PUT null` clears the value and answers `{"settings": null}`.
223
+ *
224
+ * **Never byte-compare the echo against what was written.** The column is
225
+ * Postgres `jsonb`, which normalizes key order, whitespace, duplicate keys and
226
+ * number formatting, so a byte-identical round-trip is not achievable even when
227
+ * nothing is lost. A client that diffed stored-vs-written to decide whether to
228
+ * re-save would loop forever (XB2 row V-83).
229
+ */
230
+ export interface ExtensionSettingsResponse {
231
+ settings: unknown | null;
232
+ }
233
+ export interface ExtensionShareDto {
234
+ principalType: PrincipalType;
235
+ principalId: string;
236
+ grantedBy: string;
237
+ grantedAt: string;
238
+ }
239
+ export interface ExtensionReviewQueueEntryDto {
240
+ extId: string;
241
+ displayName: string;
242
+ kind: ExtensionKind;
243
+ version: string;
244
+ status: ExtensionVersionStatus;
245
+ minNgisVersion: string;
246
+ bundleSize: number | null;
247
+ bundleSha256: string | null;
248
+ submittedBy: string | null;
249
+ createdAt: string;
250
+ /**
251
+ * ZB3's upload-scan result (F15.10), **advisory to the reviewer and never
252
+ * blocking**. `null` for every row uploaded before scanning existed and for
253
+ * every non-`ui-plugin` row — a `tool` or `ai-skill` version has no bundle to
254
+ * scan. `null` means *not scanned*; it does not mean *clean*, and the reviewer
255
+ * UI must say so rather than showing an empty findings list (Z6, W3).
256
+ */
257
+ scan?: VersionScanResultDto | null;
258
+ }
259
+ /** One entry of `GET /api/extensions/me?ngisVersion=`. */
260
+ export interface BootExtensionEntryDto {
261
+ extId: string;
262
+ kind: ExtensionKind;
263
+ version: string;
264
+ enabled: boolean;
265
+ compatible: boolean;
266
+ yanked: boolean;
267
+ manifest: PackageManifestDto;
268
+ bundleUrl: string;
269
+ integrity: string;
270
+ /**
271
+ * The resolved version's declared scopes — a projection of
272
+ * `manifest.permissions` (Stage 13 XB1), where an absent key and `[]` mean
273
+ * the same thing.
274
+ *
275
+ * Optional here because the *field* can be absent from an older payload,
276
+ * which is not the same as "declares nothing": `readDeclaredPermissions`
277
+ * (`src/store/plugin-grants.ts`) falls back to the manifest, which every
278
+ * entry carries.
279
+ */
280
+ permissions?: string[];
281
+ /**
282
+ * The resolved version's `contributes` block, projected out of the manifest by
283
+ * the server (`BootExtensionEntryDto.java:23`, `SceneExtensionEntryDto.java`).
284
+ *
285
+ * **Declared at the Stage 15 W3 kickoff, additively — the wire has carried it
286
+ * since Stage 13 and the TypeScript said nothing** (`V15-244`, the `V15-235`
287
+ * pattern again: a field on the Java DTO with no TypeScript twin).
288
+ *
289
+ * `unknown` rather than a parsed type on purpose: the only supported reader is
290
+ * `parseManifestContributes(manifest)` (`src/sdk/contributions/parse.ts:893`),
291
+ * which takes `unknown`, validates, and reports paths rooted at
292
+ * `manifest.contributes`. **Read the manifest, not this field** — it is a
293
+ * redundant projection, and a second parse path is how the two disagree.
294
+ */
295
+ contributes?: unknown;
296
+ /**
297
+ * The caller's own consented scopes for this install (Stage 13 XB2).
298
+ *
299
+ * **Not a manifest projection** — install-row state, which is why it appears
300
+ * here and *deliberately not* on a scene-resolve entry: a viewer of a shared
301
+ * scene is not the installer and has no install row to read. That asymmetry
302
+ * is the one field-level difference between the two otherwise identical
303
+ * entry shapes, and `SceneExtensionEntryDto` omits it below so the types say
304
+ * so rather than leaving an always-`undefined` property to be misread.
305
+ *
306
+ * The server reports `[]` (never `null`) for an install nobody has consented
307
+ * for. `undefined` therefore means the field was absent from the payload —
308
+ * treated the same as "never asked", i.e. no effective grants until consent.
309
+ *
310
+ * **Never the effective grant set (`V-85`)** — see `resolveEffectiveGrants`.
311
+ */
312
+ grantedScopes?: string[] | null;
313
+ }
314
+ export interface BootPayloadDto {
315
+ ngisVersionEcho: string;
316
+ extensions: BootExtensionEntryDto[];
317
+ }
318
+ /**
319
+ * One entry of `GET /api/extensions/scene/{sceneId}?ngisVersion=`.
320
+ *
321
+ * Structurally a boot entry plus `available`/`reason`, but an **unavailable**
322
+ * entry is far emptier than the kickoff's "boot DTO + two fields" wording
323
+ * suggests. `ExtensionSceneResolveService.java:100` constructs it as
324
+ * `new SceneExtensionEntryDto(extId, null, null, true, false, false, null,
325
+ * null, null, false, reason)` — so `kind` and `version` are `null` too, not
326
+ * just `manifest`/`bundleUrl`/`integrity`, and `enabled` is hard-coded `true`.
327
+ * Only `extId`, the three booleans and `reason` are meaningful when
328
+ * `available === false`.
329
+ *
330
+ * Practical consequence for every consumer: **narrow on `available` before
331
+ * reading `kind`**, because the `kind === "ui-plugin"` load filter cannot be
332
+ * applied to an unavailable entry at all.
333
+ *
334
+ * Disabled entries are omitted from the payload entirely; absence is not an
335
+ * error state and must not render a "missing" placeholder.
336
+ */
337
+ export interface SceneExtensionEntryDto extends Omit<BootExtensionEntryDto, "kind" | "version" | "manifest" | "bundleUrl" | "integrity" | "grantedScopes"> {
338
+ kind: ExtensionKind | null;
339
+ version: string | null;
340
+ manifest: PackageManifestDto | null;
341
+ bundleUrl: string | null;
342
+ integrity: string | null;
343
+ /**
344
+ * The scene-leg storage blob for this entry (F13.7's viewer-read leg).
345
+ *
346
+ * **Frozen by the W3 kickoff ahead of its backend (`V-49`, option 1).** W2's
347
+ * X3 found that "a viewer of a shared scene reads the owner's settings" had
348
+ * no endpoint behind it: the composition `GET` is owner-only (`403`) and the
349
+ * gRPC `ScenePluginSummary` carried no settings, so the value never left
350
+ * `ngis-scene` and a viewer read `null`. **XB3 (W3) delivers the backend leg**
351
+ * — proto field, `ngis-scene` populate, `ngis-extension` map — and X9 wires
352
+ * the read. Until XB3 is deployed the field is simply absent, which is why it
353
+ * is optional rather than `unknown | null`.
354
+ *
355
+ * Owner-only on the **write** path regardless: writes ride the composition
356
+ * `PUT` and its `revision`, and that ACL is unchanged this stage.
357
+ */
358
+ settings?: unknown;
359
+ available: boolean;
360
+ reason: SceneResolveReason | null;
361
+ }
362
+ export interface SceneResolvePayloadDto {
363
+ sceneId: string;
364
+ ngisVersionEcho: string;
365
+ extensions: SceneExtensionEntryDto[];
366
+ }
367
+ export interface SceneExtensionCompositionEntry {
368
+ /** Matches `^[a-z0-9-]+(\.[a-z0-9-]+)+$`, unique per scene. */
369
+ extId: string;
370
+ enabled: boolean;
371
+ versionPolicy: VersionPolicy;
372
+ /** Required and strict `x.y.z` when `versionPolicy === "PINNED"`. */
373
+ pinnedVersion?: string | null;
374
+ settings?: unknown;
375
+ }
376
+ export interface SceneExtensionComposition {
377
+ plugins: SceneExtensionCompositionEntry[];
378
+ /** Reserved/opaque. **Round-trip it verbatim** — never interpret it. */
379
+ layout?: unknown;
380
+ /**
381
+ * Opaque optimistic-concurrency token (Stage 12 V-17). Returned by GET/PUT; **echo it back on
382
+ * PUT** and a stale save is refused with `409` instead of silently erasing the other editor's
383
+ * entry — which is what happened before, verified live.
384
+ *
385
+ * Compare for equality only: it is a content digest, not an ordinal. Omitting it restores the
386
+ * old last-write-wins behaviour, so never drop it to "fix" a conflict.
387
+ */
388
+ revision?: string | null;
389
+ }
390
+ export interface CreateExtensionRequest {
391
+ extId: string;
392
+ kind?: ExtensionKind;
393
+ displayName: string;
394
+ summary?: string;
395
+ description?: string;
396
+ homepage?: string;
397
+ category?: string;
398
+ tags?: unknown;
399
+ visibility?: ExtensionVisibility;
400
+ }
401
+ export interface UpdateExtensionRequest {
402
+ displayName?: string;
403
+ summary?: string;
404
+ description?: string;
405
+ homepage?: string;
406
+ category?: string;
407
+ tags?: unknown;
408
+ visibility?: ExtensionVisibility;
409
+ status?: ExtensionStatus;
410
+ }
411
+ /**
412
+ * `POST /api/extensions/{extId}/versions/reference` — the JSON reference tuple
413
+ * that stands in for a multipart bundle on `tool` / `ai-skill` kinds. Supply
414
+ * either the tool triple or the skill pair, never both.
415
+ */
416
+ export interface CreateReferenceVersionRequest {
417
+ version: string;
418
+ minNgisVersion: string;
419
+ /**
420
+ * Free text carried onto the DRAFT and rendered on the version row.
421
+ *
422
+ * **Added by Stage 14 Y6 (W4).** The backend DTO has always had it
423
+ * (`CreateReferenceVersionRequest.java:22`) and this type did not, so no
424
+ * caller could send one — a `tool`-kind listing published through the
425
+ * self-serve wizard had no way to say what changed between two versions of a
426
+ * recipe, and the omission was invisible because an absent optional field is
427
+ * not an error anywhere.
428
+ */
429
+ changelog?: string;
430
+ provider?: string;
431
+ toolId?: string;
432
+ toolVersion?: number;
433
+ skillId?: string;
434
+ /** Integer — the exact ACTIVE skill version being referenced. */
435
+ skillVersion?: number;
436
+ }
437
+ export interface InstallExtensionRequest {
438
+ versionPolicy?: VersionPolicy;
439
+ pinnedVersion?: string;
440
+ /**
441
+ * The consent decision (Stage 13 XB2). The server **intersects it with the
442
+ * resolved version's declared `permissions`** and stores the intersection —
443
+ * extras are dropped silently rather than failing the install, so read
444
+ * `grantedScopes` back off the response instead of assuming an echo.
445
+ *
446
+ * An ungrantable scope (`layers:write`, `net:<origin>`), an unknown string
447
+ * or a duplicate is a `400`.
448
+ */
449
+ grantedScopes?: string[];
450
+ }
451
+ export interface UpdateInstallRequest {
452
+ enabled?: boolean;
453
+ versionPolicy?: VersionPolicy;
454
+ pinnedVersion?: string;
455
+ /**
456
+ * Re-consent (Stage 13 XB2). **`null`/absent leaves the stored grants
457
+ * unchanged; `[]` revokes everything** — unlike the manifest's
458
+ * `permissions`, where empty ≡ absent. The asymmetry exists so a
459
+ * `PATCH {enabled:false}` cannot silently wipe consent while "revoke all"
460
+ * stays sayable.
461
+ */
462
+ grantedScopes?: string[] | null;
463
+ }
464
+ export type ScopeAuditEventKind = "SCOPE_CONSENT" | "SCOPE_DENIAL";
465
+ /**
466
+ * `POST /api/extensions/{extId}/scope-events` → `202`, empty body.
467
+ *
468
+ * The actor is always the authenticated caller — there is no actor field, and
469
+ * a client cannot attribute an event to somebody else. `scopes` is required,
470
+ * non-empty, duplicate-free and must contain only grantable scopes: the body is
471
+ * client-reported, so an arbitrary string must not be mintable into the audit
472
+ * trail. An extension the caller cannot see answers `404`, not `403`.
473
+ */
474
+ export interface ScopeAuditRequest {
475
+ event: ScopeAuditEventKind;
476
+ scopes: string[];
477
+ /** ≤120 chars, meaningful on a denial — the refused facet member. */
478
+ member?: string;
479
+ /** Strict `x.y.z` when present. */
480
+ version?: string;
481
+ }
482
+ export interface ShareExtensionRequest {
483
+ principalType: PrincipalType;
484
+ principalId: string;
485
+ }
486
+ /** Body of `publish` and `approve` — both optional-note. */
487
+ export interface PublishVersionRequest {
488
+ reviewNote?: string;
489
+ }
490
+ /** `reviewNote` is required and must be non-blank. */
491
+ export interface RejectVersionRequest {
492
+ reviewNote: string;
493
+ }
494
+ export interface YankVersionRequest {
495
+ reason: string;
496
+ }
497
+ /**
498
+ * The preview's fixed host identity. **Never the previewed extension's own id**
499
+ * — a preview running under the real id would read and write that install row's
500
+ * grants and settings and pollute an identity the user may later actually adopt
501
+ * (F15.9). Mirrors `ExtensionPreviewService.PREVIEW_EXT_ID` on the wire; the
502
+ * fixture gate asserts the two agree rather than trusting this constant.
503
+ */
504
+ export declare const PREVIEW_EXT_ID = "ngis.market-preview";
505
+ /**
506
+ * The F13.5 scopes a preview can **never** hold, whatever the version declares
507
+ * (F15.9): nothing a preview does may persist beyond its disposal, and a preview
508
+ * may not raise host chrome outside its own surface.
509
+ *
510
+ * Verified at the W3 kickoff against `NgisPermissionScope`'s frozen union
511
+ * (`src/sdk/contributions/types.ts:218-228`) rather than copied from prose —
512
+ * that check *was* backlog row `V15-061`, and both spellings reproduce exactly.
513
+ * The remaining eight scopes are grantable to a preview if the version declares
514
+ * them; the intersection is computed server-side and arrives as `grantedScopes`.
515
+ */
516
+ export declare const PREVIEW_EXCLUDED_SCOPES: readonly ["storage:user", "ui:notify"];
517
+ /**
518
+ * `sceneId` / `expectedGeneration` are the Stage 19 lab fence, echoed from the
519
+ * caller's own lab read — a stale generation is refused `409`, and Z5 must
520
+ * surface that rather than retrying under a generation the user did not see.
521
+ * `version` absent means the latest PUBLISHED version (v1 previews PUBLISHED
522
+ * only; a reviewer-side `IN_REVIEW` preview is a recorded open tension, not a
523
+ * silent extension of F15.9).
524
+ */
525
+ export interface ExtensionPreviewRequest {
526
+ sceneId: string;
527
+ expectedGeneration: number;
528
+ version?: string;
529
+ ngisVersion: string;
530
+ }
531
+ /**
532
+ * A previewed version composed into the caller's `PLUGIN_UI_LAB` scene.
533
+ *
534
+ * **Nothing here is persisted** — no install row, no stored consent, no scene
535
+ * write. The `POST` is a read that has a consent meaning: `grantedScopes` is
536
+ * what launching consents to, and F15.9 requires Z5 to show that set *before*
537
+ * the launch, not after.
538
+ *
539
+ * `entry.extId` is {@link PREVIEW_EXT_ID}; `sourceExtId` is what is being
540
+ * previewed. Everything else on `entry` — version, manifest, contributes,
541
+ * bundle, integrity — is the previewed version verbatim.
542
+ *
543
+ * The three scope lists are disjoint by construction server-side:
544
+ * `declaredScopes` = `grantedScopes` ∪ `excludedScopes`. Render `excludedScopes`
545
+ * as *withheld*, never as absent — a preview silently missing a scope the
546
+ * version declares is exactly the confusion F15.9's visible refusal exists to
547
+ * prevent.
548
+ */
549
+ export interface ExtensionPreviewDto {
550
+ previewId: string;
551
+ sourceExtId: string;
552
+ sceneId: string;
553
+ generation: number;
554
+ /** ISO-8601. The lab's TTL, not a second clock — the preview dies with the lab. */
555
+ expiresAt: string | null;
556
+ entry: SceneExtensionEntryDto;
557
+ declaredScopes: string[];
558
+ grantedScopes: string[];
559
+ excludedScopes: string[];
560
+ }