@verentis/sdk 0.2.3 → 0.2.5

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/README.md CHANGED
@@ -10,6 +10,41 @@ npm install @verentis/sdk
10
10
 
11
11
  ## Quick Start
12
12
 
13
+ ### Consented Marketplace installation graphs
14
+
15
+ Installation administration uses a **workspace-bound user token**, not an iframe's delegated
16
+ app token or an API key. The token needs `marketplace.install.create` for preview/install and
17
+ `marketplace.install.read` for outcomes, plus the Node/Resource installer scopes documented by
18
+ Marketplace. Explicit recovery also needs `marketplace.install.update` and `node.file.delete`.
19
+
20
+ ```ts
21
+ const preview = await client.installations.preview(invoicingPackageId, {
22
+ packageVersion: '1.0.0',
23
+ branch: 'main',
24
+ })
25
+ // Display ALL preview.packages: publisher, version, treeDigest, permissions,
26
+ // manifestYaml, installMode, branch, installPaths, reuse/recovery IDs.
27
+ // Only after the user explicitly agrees to this exact displayed preview:
28
+ const result = await client.installations.install(preview, true)
29
+ ```
30
+
31
+ `preview()` does not install or grant capability authority. Dependencies use canonical
32
+ `publisher/package-id` identities and exact SemVer pins; optional CRM is never implicit.
33
+ `install()` consumes the exact stored fingerprint once. Stale graphs, changed live package
34
+ content/permissions/signing or withdrawn eligibility are rejected, never silently expanded.
35
+ HTTP failures propagate as the existing `ApiError`, including their response body.
36
+
37
+ After a failure or lost response, inspect
38
+ `await client.installations.graphResult(preview.operationId)` instead of repeating `install()`.
39
+ The durable result includes the exact package baseline and per-package outcomes/ledger IDs.
40
+ Installation is not atomic: installed/shared dependencies and retained data are not rolled back.
41
+ A Failed exact target needs a **new** preview and explicit recovery consent; uncertain
42
+ Installing/Updating targets require ledger/operator investigation.
43
+ Dependency-free `update()` / `uninstall()` remain available; updates cannot bypass declared
44
+ dependency consent. WOPI, hosted services and transactional email retain their separate
45
+ verified capability-consent flows. A graph checkbox or unsigned Canvas document is not
46
+ signature proof or financial/email authority.
47
+
13
48
  ### Hosted header actions (SDK 0.2)
14
49
 
15
50
  Applications register buttons after `await client.whenReady()`. The workspace owns their
@@ -53,10 +88,97 @@ Migrated actions are hosted only: there is no legacy-host or standalone-toolbar
53
88
  Fullscreen apps can hide the host header only when they register no actions. Keep specialized
54
89
  controls (zoom, editing modes, contextual forms) within the app.
55
90
 
91
+ ### Host messages (agent → open app)
92
+
93
+ An agent acting for the user can send structured messages to an app the user has open (the
94
+ `app_send_message` AI tool). Declare each channel in the manifest so agents can discover it and the
95
+ host forwards nothing else:
96
+
97
+ ```yaml
98
+ spec:
99
+ messages:
100
+ - channel: select-record # ^[a-z0-9][a-z0-9._-]{0,63}$
101
+ description: Selects a CRM record by id.
102
+ schema: { type: object, properties: { id: { type: string } }, required: [id] }
103
+ ```
104
+
105
+ ```ts
106
+ const stop = client.bridge!.onHostMessage('select-record', async (payload) => {
107
+ const { id } = payload as { id: string }
108
+ await selectRecord(id)
109
+ return { selected: id } // the reply the agent receives (keep it small JSON)
110
+ })
111
+ ```
112
+
113
+ Treat payloads as untrusted input and validate them against your schema. Throw to report a
114
+ failure. The protocol uses session-bound `verentis:host:message` / `verentis:host:reply` messages
115
+ with a correlation `requestId`; replays are ignored and undeclared or unhandled channels get an error
116
+ reply. Messages are only delivered while the user has "Follow agent" on or accepts the suggestion,
117
+ and the host waits about 15 seconds for a reply.
118
+
56
119
  Release the 0.2 SDK before deploying apps that require `^0.2.0`, together with the matching host.
57
120
  Local sibling-repository development can link the built SDK; published applications must resolve
58
121
  the released SDK from npm.
59
122
 
123
+ ### WOPI editor launch
124
+
125
+ Signed WOPI applications use `Bridge.requestWopiLaunch()` after `waitForInit()`.
126
+ The authenticated workspace parent chooses the installed editor, document,
127
+ branch and action; the child cannot request arbitrary document identities.
128
+ The returned token is scoped to WOPI and is not a normal workspace API token.
129
+ Submit it using a form POST to the validated discovery action, never a URL.
130
+
131
+ `requestWopiSave(generation)`, `getWopiStatus()` and `closeWopiSession()` use the
132
+ same exact-parent, origin/session/request-correlated bridge. A matching durable
133
+ save receipt—not a CODE postMessage alone—is required before clearing dirty state.
134
+ When status reports `continuationAvailable`, `requestWopiContinuation()` asks
135
+ the parent to bind the target of a completed server-owned Save As operation.
136
+ It accepts no document or acquisition IDs. Its `WopiContinuation` result contains
137
+ only document metadata and the persisted target `accessTokenTtl`, not a token,
138
+ action URL or private control credential. The parent obtains the exact existing
139
+ control IDs from authenticated `/derived`; it does not reconstruct a launch,
140
+ including after signing-key rotation. Keep the initial launch/form separate from
141
+ active document metadata, retain CODE's iframe and existing token, and preserve
142
+ unverified edits until a matching target save receipt arrives.
143
+ These methods do not replace ordinary OAuth authentication for apps whose
144
+ backend needs workspace APIs. WOPI-only packages need no per-editor OAuth
145
+ client secret.
146
+
147
+ ### App backends (OAuth 2.0 Token Exchange)
148
+
149
+ An installed app's backend is an ordinary confidential OAuth client. The iframe
150
+ forwards the user's current, file-scoped access token to its own backend; the
151
+ backend exchanges it on behalf of the user (RFC 8693) at Security's token
152
+ endpoint. The installation's approved permissions are the consent: the exchange
153
+ is refused for a backend client the workspace has not approved, and it can
154
+ never widen the user's scopes or resource.
155
+
156
+ ```ts
157
+ // iframe
158
+ const authorization = await client.getAuthorizationHeader()
159
+ await fetch('https://backend.example.com/convert', {
160
+ method: 'POST',
161
+ headers: { Authorization: authorization! },
162
+ body: JSON.stringify({ nodeId: client.context.file?.nodeId }),
163
+ })
164
+ ```
165
+
166
+ ```http
167
+ POST /connect/token
168
+ Authorization: Basic base64(client_id:client_secret)
169
+ Content-Type: application/x-www-form-urlencoded
170
+
171
+ grant_type=urn:ietf:params:oauth:grant-type:token-exchange
172
+ &subject_token=<forwarded access token>
173
+ &subject_token_type=urn:ietf:params:oauth:token-type:access_token
174
+ &scope=node.file.read
175
+ ```
176
+
177
+ The issued token carries an `act` claim naming the backend client. Add
178
+ `offline_access` (or `requested_token_type=urn:ietf:params:oauth:token-type:refresh_token`)
179
+ when the backend must keep working after the user leaves; every refresh is
180
+ re-authorized against the installation's live consent.
181
+
60
182
  ### Standalone mode (local development)
61
183
 
62
184
  ```ts
@@ -126,6 +248,36 @@ credentials and `workspaceId` yourself.
126
248
 
127
249
  ## API Overview
128
250
 
251
+ ### Revision-safe node content
252
+
253
+ Use stable node IDs and an explicit branch for editing existing records. Read bytes and their
254
+ strong revision together; submit that revision and a caller-owned idempotency key when saving:
255
+
256
+ ```ts
257
+ const snapshot = await client.files.readNodeContent(nodeId, { branch: 'main' })
258
+ const next = await client.files.writeNodeContent(nodeId, {
259
+ branch: 'main',
260
+ revision: snapshot.revision,
261
+ idempotencyKey: crypto.randomUUID(),
262
+ content: editedJson,
263
+ })
264
+ ```
265
+
266
+ `getNode(nodeId, { branch })` returns stable metadata, including its unquoted `etag`, which
267
+ `writeNodeContent` also accepts. Node-content writes replace bytes only; they do not create files,
268
+ move paths or change MIME types. Keep the same revision, bytes and operation key for an identical
269
+ retry; a new edit needs a new key. The SDK does not automatically retry or report a stale save as
270
+ successful. `ApiError` is a runtime export and exposes `status`, `detail`, optional `title` and the
271
+ platform's optional machine-readable `code` (for example `revision_mismatch`).
272
+
273
+ Path deletion uses `remove({ path, branch, headStamp?, idempotencyKey?, mode? })` with the routed
274
+ file API. `headStamp` is a **path-binding head stamp**, not the quoted node-content revision.
275
+ The default mode is shallow; request `deep` explicitly for recursive deletion. These options
276
+ do not bypass server authorization or mutation guards and do not introduce financial transactions.
277
+
278
+ Deploy only with a platform release providing the conditional node-content routes. New app
279
+ contracts must not assume a locally present feature-branch API is already deployed.
280
+
129
281
  | Export | Description |
130
282
  | --- | --- |
131
283
  | `createVerentisClient(options?)` | Factory function — creates a configured `VerentisClient` |
@@ -178,15 +330,14 @@ npm run typecheck
178
330
 
179
331
  ## Versioning
180
332
 
181
- This project uses [GitVersion](https://gitversion.net/) for automatic semantic versioning:
333
+ The release workflow publishes the exact stable version in `package.json` after changes
334
+ merge to `main`. Update both `package.json` and `package-lock.json` when preparing a new
335
+ version; npm versions are immutable.
182
336
 
183
- - Commits to `main` produce clean versions (e.g., `0.1.1`, `0.1.2`)
184
- - Feature branches produce pre-release versions (e.g., `0.2.0-my-feature.1`)
185
- - Use commit message tags to control version bumps:
186
- - `+semver: breaking` or `+semver: major` — major bump
187
- - `+semver: feature` or `+semver: minor` — minor bump
188
- - `+semver: fix` or `+semver: patch` — patch bump (default)
189
- - `+semver: none` or `+semver: skip` — no bump
337
+ A version older than the current npm `latest` is published under `version-<version>`
338
+ without moving `latest` backwards. Consumers of such a release must install its exact
339
+ version (for example, `npm install @verentis/sdk@0.2.0 --save-exact`), not a caret range
340
+ that can resolve to a different release.
190
341
 
191
342
  ## License
192
343