@fusebase/fusebase-gate-sdk 2.5.0 → 2.5.1-sdk.1
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/package.json
CHANGED
package/release-notes/latest.md
CHANGED
|
@@ -1,1054 +0,0 @@
|
|
|
1
|
-
# Release Notes 2.3.34-sdk.10
|
|
2
|
-
|
|
3
|
-
- Current ref: `HEAD`
|
|
4
|
-
- Previous tag: `v2.2.15`
|
|
5
|
-
- Generated at: 2026-07-27T04:36:05.229Z
|
|
6
|
-
|
|
7
|
-
## Included Drafts
|
|
8
|
-
|
|
9
|
-
- `docs/release-notes/2026-05-20-app-magic-link-redirectpath-fix.md` - 2026-05-20-app-magic-link-redirectpath-fix
|
|
10
|
-
- `docs/release-notes/2026-05-27-visitor-api-edge-skills.md` - 2026-05-27 — Visitor access vs open `/api/*` (skill guidance)
|
|
11
|
-
- `docs/release-notes/2026-06-05-isolated-store-secret-guidance.md` - Isolated store secret guidance
|
|
12
|
-
- `docs/release-notes/2026-06-08-get-org-url.md` - Get organization URL
|
|
13
|
-
- `docs/release-notes/2026-06-15-create-portal-folder.md` - createPortalFolder
|
|
14
|
-
- `docs/release-notes/2026-06-15-duplicate-portal-item.md` - duplicatePortalItem
|
|
15
|
-
- `docs/release-notes/2026-06-15-update-portal-access.md` - updatePortalAccess
|
|
16
|
-
- `docs/release-notes/2026-06-16-bulk-invite-to-portal.md` - Bulk invite users to a portal
|
|
17
|
-
- `docs/release-notes/2026-06-16-getme-timezone-time-format.md` - getMe returns user timezone and time-format preferences
|
|
18
|
-
- `docs/release-notes/2026-06-16-token-rls-bypass-permission-validation.md` - Token creation accepts isolated_store RLS break-glass permissions
|
|
19
|
-
- `docs/release-notes/2026-06-19-token-provisioning-pagination.md` - Summary
|
|
20
|
-
- `docs/release-notes/2026-06-20-app-portal-embeds.md` - List an app's portal embeds
|
|
21
|
-
- `docs/release-notes/2026-06-26-publish-portal-draft.md` - publishPortalDraft
|
|
22
|
-
- `docs/release-notes/2026-06-29-app-portal-embeds-iframe-kind.md` - App portal embeds — detect iframe embeds, add `kind`
|
|
23
|
-
- `docs/release-notes/2026-06-30-append-workspace-note-content.md` - Append workspace note content
|
|
24
|
-
- `docs/release-notes/2026-06-30-client-app-invite.md` - Client → client app invite op (NIM-40541 / NIM-41954)
|
|
25
|
-
- `docs/release-notes/2026-06-30-client-portal-invite.md` - Client → client portal invite authz (NIM-40541 / NIM-41950)
|
|
26
|
-
- `docs/release-notes/2026-07-02-markdown-notes-crud.md` - Markdown (v3) notes CRUD
|
|
27
|
-
- `docs/release-notes/2026-07-02-member-removal-client-deletion.md` - Member removal and client deletion scheduling
|
|
28
|
-
- `docs/release-notes/2026-07-03-auto-confirm-email-registration.md` - 2026-07-03-auto-confirm-email-registration
|
|
29
|
-
- `docs/release-notes/2026-07-04-bulk-app-magic-links.md` - Bulk app magic-link invites
|
|
30
|
-
- `docs/release-notes/2026-07-09-workspace-portal-member-read.md` - Workspace and portal member read
|
|
31
|
-
- `docs/release-notes/2026-07-13-getme-no-health-read.md` - getMe no longer requires health.read
|
|
32
|
-
- `docs/release-notes/2026-07-23-add-portal-blank-note-block.md` - Add Portal Blank Note Block
|
|
33
|
-
- `docs/release-notes/2026-07-23-conditional-remove-org-member.md` - Conditional removeOrgMember (optional preconditions)
|
|
34
|
-
- `docs/release-notes/2026-07-24-add-portal-chat-block.md` - Add portal Chat block + channel discovery
|
|
35
|
-
- `docs/release-notes/2026-07-24-add-portal-embed-block.md` - Add Portal Embed Block
|
|
36
|
-
- `docs/release-notes/2026-07-24-create-portal-page-with-note-portal-service.md` - createPortalPageWithNote moves into portal-service
|
|
37
|
-
- `docs/release-notes/2026-07-25-list-portal-chat-users.md` - List portal Chat DM target users
|
|
38
|
-
|
|
39
|
-
## Summary
|
|
40
|
-
|
|
41
|
-
### 2026-05-20-app-magic-link-redirectpath-fix
|
|
42
|
-
|
|
43
|
-
Bug fix for the AI App **Magic Link** ops. The Gate controller coerced an
|
|
44
|
-
omitted `redirectPath` to an explicit `null` before forwarding the request to
|
|
45
|
-
`nimbus-ai`. nimbus-ai accepts an _absent_ `redirectPath` but rejects an
|
|
46
|
-
explicit `null` (`"must be string"`), so every `createAppMagicLink` /
|
|
47
|
-
`requestAppMagicLink` call that did not include a deep-link path failed with
|
|
48
|
-
HTTP 400. That broke the two most common magic-link scenarios — inviting a
|
|
49
|
-
client so they land on the app root, and visitor self-service where the
|
|
50
|
-
visitor only types an email.
|
|
51
|
-
|
|
52
|
-
### 2026-05-27 — Visitor access vs open `/api/*` (skill guidance)
|
|
53
|
-
|
|
54
|
-
Clarify in MCP prompts that `--access=visitor` enables guest access to the App host via platform `/_auth/` and `fbsfeaturetoken`, not unauthenticated `/api/*`. Reduces false "broken API" reports from curl and agent smoke tests.
|
|
55
|
-
|
|
56
|
-
### Isolated store secret guidance
|
|
57
|
-
|
|
58
|
-
Clarified Gate isolated-store guidance so app agents treat store identity as a Gate-resolved platform resource, not app secret or runtime environment configuration.
|
|
59
|
-
|
|
60
|
-
### Get organization URL
|
|
61
|
-
|
|
62
|
-
Gate exposes `GET /:orgId/url` (`getOrgUrl`) to resolve the canonical HTTPS base URL for an organization. The hostname follows org-service rules: custom CNAME domain when configured, otherwise `{sub}.{FUSEBASE_HOST}`.
|
|
63
|
-
|
|
64
|
-
### createPortalFolder
|
|
65
|
-
|
|
66
|
-
Adds the `createPortalFolder` Gate operation (NIM-41312): create a new empty
|
|
67
|
-
root folder in a portal from CLI/SDK/MCP agents. The folder is staged in the
|
|
68
|
-
portal's customizer draft (via the NIM-41542 changeEvents/draft mechanism), not
|
|
69
|
-
published immediately — the portal owner reviews and publishes it in the
|
|
70
|
-
customizer.
|
|
71
|
-
|
|
72
|
-
### duplicatePortalItem
|
|
73
|
-
|
|
74
|
-
Adds the `duplicatePortalItem` Gate operation (NIM-41316): copy a folder or page
|
|
75
|
-
(and, for a folder, its whole subtree of copyable children) inside the same
|
|
76
|
-
portal from CLI/SDK/MCP agents. The copy is staged in the portal's customizer
|
|
77
|
-
draft (via the NIM-41542 changeEvents/draft mechanism), not published
|
|
78
|
-
immediately — the portal owner reviews and publishes it in the customizer.
|
|
79
|
-
|
|
80
|
-
### updatePortalAccess
|
|
81
|
-
|
|
82
|
-
Adds the `updatePortalAccess` Gate operation (NIM-41308): set a portal's access
|
|
83
|
-
mode from CLI/SDK/MCP agents. The change is staged in the portal's customizer
|
|
84
|
-
draft (via the NIM-41542 changeEvents/draft mechanism), not published
|
|
85
|
-
immediately — the portal owner reviews and publishes it in the customizer.
|
|
86
|
-
|
|
87
|
-
### Bulk invite users to a portal
|
|
88
|
-
|
|
89
|
-
Gate exposes `POST /:orgId/portals/:portalId/invite/bulk` (`bulkInviteToPortal`) to invite many users to a single portal in one request, replacing N sequential `inviteToPortal` calls. Invites run with bounded concurrency (default 5, max 5); the rest are queued. Portal discovery (`listPortals`/`getPortal`) runs once for the whole batch. An optional `background` mode returns immediately without blocking the caller.
|
|
90
|
-
|
|
91
|
-
### getMe returns user timezone and time-format preferences
|
|
92
|
-
|
|
93
|
-
Extend the `GET /me` (`getMe`) response with a `preferences` object so Fusebase Apps can read the user's timezone and clock preference (12h/24h) without a separate call.
|
|
94
|
-
|
|
95
|
-
### Token creation accepts isolated_store RLS break-glass permissions
|
|
96
|
-
|
|
97
|
-
Fix token creation rejecting `isolated_store.rls.bypass` and `isolated_store.rls.delegate` with `Invalid permission` even though they are part of the Gate permission catalog.
|
|
98
|
-
|
|
99
|
-
### List an app's portal embeds
|
|
100
|
-
|
|
101
|
-
Adds a Gate operation that returns the portal pages where a given app is embedded
|
|
102
|
-
(as an `app-feature` brick), scoped to the org. Backs the app card "Resources"
|
|
103
|
-
popup, the in-app runtime, and the public-api/CLI proxy.
|
|
104
|
-
|
|
105
|
-
### publishPortalDraft
|
|
106
|
-
|
|
107
|
-
Adds the `publishPortalDraft` Gate operation (NIM-41873): publish a portal's
|
|
108
|
-
staged customizer draft to the live portal from CLI/SDK/MCP agents — the
|
|
109
|
-
programmatic equivalent of the customizer's Publish button. This lets an agent
|
|
110
|
-
make its previously staged content/settings ops (folders, pages, blocks, access
|
|
111
|
-
mode, custom code) visible to clients without opening the customizer.
|
|
112
|
-
|
|
113
|
-
### App portal embeds — detect iframe embeds, add `kind`
|
|
114
|
-
|
|
115
|
-
Extends `listAppPortalEmbeds` to also report portal pages where the app is embedded
|
|
116
|
-
via a regular `embed` (iframe) block whose `contentUrl` points at the app host, in
|
|
117
|
-
addition to the existing `app-feature` brick embeds. Every entry now carries a
|
|
118
|
-
`kind` discriminator. Detection works for all consumers, including the in-app
|
|
119
|
-
runtime app-token path.
|
|
120
|
-
|
|
121
|
-
### Append workspace note content
|
|
122
|
-
|
|
123
|
-
Add an append-only workspace note content operation for apps that need to add content to an existing note without replacing the note body.
|
|
124
|
-
|
|
125
|
-
### Client → client app invite op (NIM-40541 / NIM-41954)
|
|
126
|
-
|
|
127
|
-
New op `createAppClientInviteMagicLink`
|
|
128
|
-
(`POST /:orgId/apps/:appId/client-invites`): an authenticated **client** that
|
|
129
|
-
already has access to an AI App can invite another client. The caller's
|
|
130
|
-
`orgRole` must be `client` (owners/admins use the separate `createAppMagicLink`
|
|
131
|
-
flow); the op is gated by the `FEATURE_FLAGS=client_invite` env kill-switch
|
|
132
|
-
(fail-closed). The invitee is always provisioned as a `client` and inherits the
|
|
133
|
-
inviter's app scope (a user principal on every feature) — no role/access
|
|
134
|
-
selectors. The owner/admin `createAppMagicLink` (`app_magic_link.write`) flow is
|
|
135
|
-
unchanged.
|
|
136
|
-
|
|
137
|
-
### Client → client portal invite authz (NIM-40541 / NIM-41950)
|
|
138
|
-
|
|
139
|
-
`inviteToPortal` / `bulkInviteToPortal` now allow a caller whose `orgRole` is
|
|
140
|
-
`client` to invite other clients, gated by the portal's `allowClientsToInvite`
|
|
141
|
-
setting and the `FEATURE_FLAGS=client_invite` env kill-switch (fail-closed:
|
|
142
|
-
off → client callers are denied `portal_client_invite_disabled`).
|
|
143
|
-
Members/managers/owners are unaffected.
|
|
144
|
-
|
|
145
|
-
### Markdown (v3) notes CRUD
|
|
146
|
-
|
|
147
|
-
Add Gate contracts, controllers, and MCP-visible operations for v3 markdown notes: create, read, replace, and append markdown stored as the source of truth in note-service (no editor-service involved). Part of Notes v3 Iteration 1 (NIM-42034, NIM-42042).
|
|
148
|
-
|
|
149
|
-
### Member removal and client deletion scheduling
|
|
150
|
-
|
|
151
|
-
Gate now exposes member-removal operations for organization, workspace, and portal membership using app-visible user/workspace ids, plus delayed Client account deletion scheduling.
|
|
152
|
-
|
|
153
|
-
### Bulk app magic-link invites
|
|
154
|
-
|
|
155
|
-
Gate exposes `POST /:orgId/apps/:appId/magic-links/bulk` (`bulkCreateAppMagicLinks`) to invite many users to a single app in one request, replacing N sequential `createAppMagicLink` calls. Invites run with bounded concurrency (default 5, max 5); the rest are queued in-process. An optional `background` mode returns immediately without blocking the caller. Mirrors the portal `bulkInviteToPortal` pattern (NIM-41651).
|
|
156
|
-
|
|
157
|
-
### Workspace and portal member read
|
|
158
|
-
|
|
159
|
-
Gate now exposes read operations that list the members of a single workspace or portal scoped to an organization, closing the gap where only member writes and the org-wide roster were available.
|
|
160
|
-
|
|
161
|
-
### getMe no longer requires health.read
|
|
162
|
-
|
|
163
|
-
`GET /me` (`getMe`) no longer declares a required Gate permission. Any authenticated **user** or **token** subject may call it with a valid session, without `health.read` in the grant.
|
|
164
|
-
|
|
165
|
-
### Add Portal Blank Note Block
|
|
166
|
-
|
|
167
|
-
New operation `addPortalBlankNoteBlock`: creates an empty Fusebase note, shares it
|
|
168
|
-
into the portal and stages one `addBlock` event on an existing portal page — the
|
|
169
|
-
programmatic equivalent of the customizer's "Blank Block" action. No page and no
|
|
170
|
-
sidebar menu item is created.
|
|
171
|
-
|
|
172
|
-
### Conditional removeOrgMember (optional preconditions)
|
|
173
|
-
|
|
174
|
-
`DELETE /{orgId}/users/{userId}` (`removeOrgMember`) accepts two optional query preconditions. Without them the call stays exactly as unconditional as before.
|
|
175
|
-
|
|
176
|
-
- `expectedRole` — remove only while the member still holds that org role.
|
|
177
|
-
- `expectedJoinedAfter` — unix seconds; remove only when the membership was created at or after that moment.
|
|
178
|
-
|
|
179
|
-
Either mismatch returns **409** and removes nothing, with `data.errorCode` = `org_member_role_mismatch` or `org_member_joined_before_expected`.
|
|
180
|
-
|
|
181
|
-
### Add portal Chat block + channel discovery
|
|
182
|
-
|
|
183
|
-
Gate exposes two new portal operations that stage a Chat widget on a portal page,
|
|
184
|
-
mirroring the customizer's "Chat" action:
|
|
185
|
-
|
|
186
|
-
- `GET /:orgId/portals/:portalId/chat/channels` (`listPortalChatChannels`) — the
|
|
187
|
-
portal's real, listable chat channels (DMs, group DMs and hidden channels
|
|
188
|
-
excluded; default channel first) for the Chat block picker.
|
|
189
|
-
- `POST /:orgId/portals/:portalId/pages/:pageId/blocks/chat` (`addPortalChatBlock`)
|
|
190
|
-
— stages one Chat block whose `target` is a discriminated union: a channel
|
|
191
|
-
(`target: { type: 'channel', channelId }`) or a DM (`target: { type: 'dm', userId }`).
|
|
192
|
-
One active Chat block per page.
|
|
193
|
-
|
|
194
|
-
Gate stays thin: all channel/DM revalidation, the chat widget configuration and the
|
|
195
|
-
draft logic live in portal-service.
|
|
196
|
-
|
|
197
|
-
### Add Portal Embed Block
|
|
198
|
-
|
|
199
|
-
New operation `addPortalEmbedBlock`: stages one Customizer-compatible Embed block
|
|
200
|
-
("Embeds & Integrations") on an existing portal page — the programmatic equivalent
|
|
201
|
-
of the customizer's Embed action. No page and no sidebar menu item is created, and
|
|
202
|
-
nothing is published automatically.
|
|
203
|
-
|
|
204
|
-
As part of the same change, the generic `addPortalBlock` **no longer accepts
|
|
205
|
-
`type: "embed"`** — Embed blocks now go exclusively through `addPortalEmbedBlock`.
|
|
206
|
-
|
|
207
|
-
### createPortalPageWithNote moves into portal-service
|
|
208
|
-
|
|
209
|
-
`createPortalPageWithNote` now delegates the whole use case to a new portal-service
|
|
210
|
-
endpoint (`POST /portals/:portalId/pages/note`), the same way `addPortalBlankNoteBlock`
|
|
211
|
-
does. Gate keeps the public contract, auth/authz and error mapping; note creation,
|
|
212
|
-
sharing, optional initial content, slug/URL/index calculation, the canonical
|
|
213
|
-
addPage/addMenuItem/addBlock events and the draft append + conflict retry all live in
|
|
214
|
-
portal-service. The public operation, HTTP path, request/response shape, permission
|
|
215
|
-
(`portals.write`) and MCP semantics are unchanged.
|
|
216
|
-
|
|
217
|
-
### List portal Chat DM target users
|
|
218
|
-
|
|
219
|
-
Gate exposes a new portal operation that searches a portal org's active members
|
|
220
|
-
and owner as DM targets for the Chat block picker, completing the Chat block flow
|
|
221
|
-
(`listPortalChatChannels` + `addPortalChatBlock` shipped earlier):
|
|
222
|
-
|
|
223
|
-
- `GET /:orgId/portals/:portalId/chat/users?query=&limit=&cursor=`
|
|
224
|
-
(`listPortalChatUsers`) — a single bounded page of
|
|
225
|
-
`{ id, displayName, email, username? }` plus the matching `total` and an opaque
|
|
226
|
-
`nextCursor`. `query` is optional: omit it to browse the first page when the
|
|
227
|
-
person is not known. Call it before `addPortalChatBlock` with a
|
|
228
|
-
`{ type: 'dm', userId }` target; pass the exact `users[].id`.
|
|
229
|
-
|
|
230
|
-
Gate stays thin: the name match, member scoping and opaque cursor all live in
|
|
231
|
-
org-service (`/orgs/:org/members/search`); portal-service forwards the caller's
|
|
232
|
-
user context and the query/limit/cursor.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
## API / SDK Changes
|
|
236
|
-
|
|
237
|
-
### 2026-05-20-app-magic-link-redirectpath-fix
|
|
238
|
-
|
|
239
|
-
- No contract, schema, OpenAPI, SDK, or permission change. The Gate HTTP
|
|
240
|
-
surface is identical; `redirectPath` remains optional in both request
|
|
241
|
-
contracts.
|
|
242
|
-
- `AppMagicLinksController` (`src/controllers/app-magic-links/app-magic-links.ts`)
|
|
243
|
-
now forwards `redirectPath` to nimbus-ai **only when the caller provided
|
|
244
|
-
one** (`...(body.redirectPath != null ? { redirectPath } : {})`), for both
|
|
245
|
-
`createAppMagicLink` and `requestAppMagicLink`. An empty-string
|
|
246
|
-
`redirectPath` is still forwarded verbatim.
|
|
247
|
-
|
|
248
|
-
### Isolated store secret guidance
|
|
249
|
-
|
|
250
|
-
No API or SDK contract changes.
|
|
251
|
-
|
|
252
|
-
MCP prompts and generated skill references now explicitly say not to register `storeId`, database IDs, physical database names, or provider connection details through `fusebase secret create`.
|
|
253
|
-
|
|
254
|
-
### Get organization URL
|
|
255
|
-
|
|
256
|
-
- Added `getOrgUrl` operation and `OrgsApi.getOrgUrl` SDK client method.
|
|
257
|
-
- Response fields: `url`, `host`, `kind` (`cname` | `subdomain`), `sub`, `customDomain`, `domainShorter`.
|
|
258
|
-
- Permission: `org.read` with org-scoped authz.
|
|
259
|
-
|
|
260
|
-
### createPortalFolder
|
|
261
|
-
|
|
262
|
-
- New op `createPortalFolder` — `POST /:orgId/portals/:portalId/folders`.
|
|
263
|
-
- Body: `name` (required), `icon` (optional, default `folder`).
|
|
264
|
-
- Response: `{ folderId, pageId, url, branchId, seqs, staged: true }`.
|
|
265
|
-
- Permission: `portals.write`.
|
|
266
|
-
- New contract types `CreatePortalFolderRequest` / `CreatePortalFolderResponse`
|
|
267
|
-
(regenerated `runtime-schema-defs`).
|
|
268
|
-
- MCP `portals` prompt bumped to 1.8.0 with folder-creation guidance.
|
|
269
|
-
|
|
270
|
-
### duplicatePortalItem
|
|
271
|
-
|
|
272
|
-
- New op `duplicatePortalItem` —
|
|
273
|
-
`POST /:orgId/portals/:portalId/items/:itemId/duplicate`.
|
|
274
|
-
- Body: `name` (optional, default `<source name> copy`), `parentId` (optional,
|
|
275
|
-
default: next to the source).
|
|
276
|
-
- Response: `{ itemId, pageId?, url, itemCount, branchId, seqs, staged: true }`.
|
|
277
|
-
- Permission: `portals.write`.
|
|
278
|
-
- New contract types `DuplicatePortalItemRequest` / `DuplicatePortalItemResponse`
|
|
279
|
-
(regenerated `runtime-schema-defs`).
|
|
280
|
-
- MCP `portals` prompt bumped to 1.10.0 with duplication guidance.
|
|
281
|
-
|
|
282
|
-
### updatePortalAccess
|
|
283
|
-
|
|
284
|
-
- New op `updatePortalAccess` — `POST /:orgId/portals/:portalId/access`.
|
|
285
|
-
- Body: `accessMode` ∈ `open` | `invite-only` | `email` | `email-verified`.
|
|
286
|
-
- Response: `{ accessMode, branchId, seq, staged: true }`.
|
|
287
|
-
- Permission: `portals.write`.
|
|
288
|
-
- New contract types `UpdatePortalAccessRequest` / `UpdatePortalAccessResponse`
|
|
289
|
-
(regenerated `runtime-schema-defs`).
|
|
290
|
-
- MCP `portals` prompt bumped to 1.7.0 with access-mode guidance.
|
|
291
|
-
|
|
292
|
-
### Bulk invite users to a portal
|
|
293
|
-
|
|
294
|
-
- Added `bulkInviteToPortal` operation and `PortalsApi.bulkInviteToPortal` SDK client method.
|
|
295
|
-
- Request: `invitations[]` (each item matches `inviteToPortal`: `email` required; optional `fullName`, `orgRole`, `workspaceRole`, `isFullAccess`), optional `concurrency` (1..5, default 5), optional `background` (default false).
|
|
296
|
-
- Response: `total`, `status` (`completed` | `processing`). For `completed`: `succeeded`, `failed`, `results[]` (`{ email, status, magicLink?, url?, userId?, error? }`). For `processing`: `accepted`.
|
|
297
|
-
- Per-invitation `orgRole`/`isFullAccess` semantics are identical to `inviteToPortal` (shared `invitePortalUser` helper).
|
|
298
|
-
- Permission: `portals.manage` with org-scoped authz (same as `inviteToPortal`).
|
|
299
|
-
- MCP: `bulkInviteToPortal` is MCP-visible under the `portals` prompt group; the prompt now documents batching, concurrency, background mode, and the client access-clarification rule.
|
|
300
|
-
|
|
301
|
-
### getMe returns user timezone and time-format preferences
|
|
302
|
-
|
|
303
|
-
- `MeResponse` gains a nullable `preferences` object (`MePreferences` schema):
|
|
304
|
-
- `timezone` — IANA timezone name, e.g. `Europe/Moscow` (nullable)
|
|
305
|
-
- `timezoneOffset` — current UTC offset string, e.g. `+03:00`, recomputed from the timezone at request time so it stays correct across DST (nullable)
|
|
306
|
-
- `timeFormat` — `12h` or `24h`, derived from the user's `dateTimeLocale`
|
|
307
|
-
- `dateTimeLocale` — raw display locale, e.g. `en-US` (12h) / `en-GB` (24h) (nullable)
|
|
308
|
-
- `preferences` is `null` when the auth context is not user-bound (e.g. an app token without a user).
|
|
309
|
-
|
|
310
|
-
### Token creation accepts isolated_store RLS break-glass permissions
|
|
311
|
-
|
|
312
|
-
- No API contract changes.
|
|
313
|
-
- Token `POST` now accepts all permissions from the Gate catalog, including `isolated_store.rls.bypass` and `isolated_store.rls.delegate`.
|
|
314
|
-
|
|
315
|
-
### List an app's portal embeds
|
|
316
|
-
|
|
317
|
-
- New operation `listAppPortalEmbeds`: `GET /:orgId/apps/:appId/portal-embeds`.
|
|
318
|
-
- Auth: `userOrToken('portals.read', PATH)` — internal user or app token.
|
|
319
|
-
- Response `ListAppPortalEmbedsResponse`: `{ portalEmbeds: [{ portal { globalId, name }, page { globalId, title }, url }] }`, one entry per page (deduped).
|
|
320
|
-
- Lives in the new `app-embed-targets` controller/tag (a bucket for app embed-target lookups; `portal-embeds` is the first kind).
|
|
321
|
-
- MCP: not exposed (`mcp.enabled: false`).
|
|
322
|
-
- Downstream: calls portal-service `GET /blocks/search` with the app-embed filter (`org`, `blockType=flexible`, `brickType=app-feature`, `brickMatch=featureId:<appId>`).
|
|
323
|
-
|
|
324
|
-
### publishPortalDraft
|
|
325
|
-
|
|
326
|
-
- New op `publishPortalDraft` — `POST /:orgId/portals/:portalId/publish`.
|
|
327
|
-
- No body.
|
|
328
|
-
- Response: `{ branchId, newBranchId, appliedEventCount, lastPublishedAt, published: true }`.
|
|
329
|
-
- Permission: `portals.write`.
|
|
330
|
-
- New contract type `PublishPortalDraftResponse` (regenerated `runtime-schema-defs`).
|
|
331
|
-
- MCP `portals` prompt bumped to 1.15.0 with publish guidance.
|
|
332
|
-
|
|
333
|
-
### App portal embeds — detect iframe embeds, add `kind`
|
|
334
|
-
|
|
335
|
-
- `ListAppPortalEmbedsResponse` entries gain `kind: 'app-feature' | 'iframe'`
|
|
336
|
-
(additive, non-breaking).
|
|
337
|
-
- `listAppPortalEmbeds` now merges two portal-service searches:
|
|
338
|
-
- existing brick search (`blockType=flexible`, `brickType=app-feature`,
|
|
339
|
-
`brickMatch=featureId:<appId>`) → `kind: 'app-feature'`.
|
|
340
|
-
- new iframe search (`blockType=embed`, `attrContains=contentUrl:<host>` per
|
|
341
|
-
resolved host) → `kind: 'iframe'`.
|
|
342
|
-
- Host(s) resolved via nimbus-ai `apiGetAppByGlobalId` using a secret-only
|
|
343
|
-
("visitor") header (no caller user id), so the app-token runtime path works too.
|
|
344
|
-
Takes the host of `url` and, when set, of `customDomainFqdn` (deduped).
|
|
345
|
-
- Merge is per page with brick precedence: a page matched by both stays
|
|
346
|
-
`app-feature`.
|
|
347
|
-
- Graceful degrade: if host resolution yields nothing (app not found / unpublished
|
|
348
|
-
/ nimbus-ai unreachable), the iframe search is skipped and brick embeds are
|
|
349
|
-
returned normally — the op never fails on this.
|
|
350
|
-
|
|
351
|
-
### Append workspace note content
|
|
352
|
-
|
|
353
|
-
- Added `appendWorkspaceNoteContent`.
|
|
354
|
-
- The operation accepts a workspace note id plus non-empty `content`, with optional `format: "text" | "html"`.
|
|
355
|
-
- The operation appends to the end of the note and returns the refreshed note metadata plus `note.md`.
|
|
356
|
-
- Regenerated SDK/OpenAPI artifacts include the new Notes API method and request contract.
|
|
357
|
-
|
|
358
|
-
### Client → client app invite op (NIM-40541 / NIM-41954)
|
|
359
|
-
|
|
360
|
-
- New op + request schema `CreateAppClientInviteRequest` (`email`,
|
|
361
|
-
`redirectPath?`); response reuses `CreateAppMagicLinkResponse`. SDK + OpenAPI
|
|
362
|
-
regenerated.
|
|
363
|
-
- New permission `app_magic_link.client_invite`, granted to clients (and, via
|
|
364
|
-
the full set, to members/managers/owners). Distinct from
|
|
365
|
-
`app_magic_link.write` so the owner/admin flow stays owner-only.
|
|
366
|
-
- `403` when the caller is not a client, the feature flag is off, the caller has
|
|
367
|
-
no app-invite permission, or the inviter has no access to the app
|
|
368
|
-
(`errorCode: forbidden` for the downstream no-access case).
|
|
369
|
-
|
|
370
|
-
### Client → client portal invite authz (NIM-40541 / NIM-41950)
|
|
371
|
-
|
|
372
|
-
Request/response schemas and op-level authz are unchanged; enforcement is
|
|
373
|
-
internal to the controller. New error codes are returned in the existing error
|
|
374
|
-
response body under `apiData.errorCode` and are now documented as `403`
|
|
375
|
-
responses on both ops in the OpenAPI spec:
|
|
376
|
-
|
|
377
|
-
- `portal_client_invite_disabled` (403) — portal setting off or feature flag off.
|
|
378
|
-
- `forbidden` (403) — caller has no access to the portal.
|
|
379
|
-
- `client_limit_reached` (403) — org client seat limit would be exceeded.
|
|
380
|
-
|
|
381
|
-
### Markdown (v3) notes CRUD
|
|
382
|
-
|
|
383
|
-
- Added `createWorkspaceMarkdownNote` (`POST /:orgId/workspaces/:workspaceId/markdown-notes`).
|
|
384
|
-
- Added `getWorkspaceMarkdownNote` (`GET .../markdown-notes/:noteId`) returning the markdown source of truth plus `revision`.
|
|
385
|
-
- Added `updateWorkspaceMarkdownNoteContent` (`PUT .../markdown-notes/:noteId/content`) replacing the full markdown document with a required optimistic `revision` lock.
|
|
386
|
-
- Added `appendWorkspaceMarkdownNoteContent` (`POST .../markdown-notes/:noteId/content/append`) with an optional `revision` lock.
|
|
387
|
-
- Stale-revision writes return HTTP 409 with `data.errorCode = markdown_note_revision_conflict` and `data.currentRevision`.
|
|
388
|
-
- Regenerated SDK/OpenAPI artifacts include the new Notes API methods and contracts.
|
|
389
|
-
|
|
390
|
-
### Member removal and client deletion scheduling
|
|
391
|
-
|
|
392
|
-
- Added `removeOrgMember`.
|
|
393
|
-
- Added `removeWorkspaceMember`.
|
|
394
|
-
- Added `removePortalMember`.
|
|
395
|
-
- Added `scheduleClientAccountDeletion`, which schedules delayed deletion only for Client-role org members.
|
|
396
|
-
|
|
397
|
-
### Bulk app magic-link invites
|
|
398
|
-
|
|
399
|
-
- Added `bulkCreateAppMagicLinks` operation and `AppMagicLinksApi.bulkCreateAppMagicLinks` SDK client method.
|
|
400
|
-
- Request: `invitations[]` (each item matches `createAppMagicLink`: `email` required; optional `redirectPath`, `addToAccessPrincipals`), optional `concurrency` (1..5, default 5), optional `background` (default false).
|
|
401
|
-
- Response: `total`, `status` (`completed` | `processing`). For `completed`: `succeeded`, `failed`, `results[]` (`{ email, status, id?, magicLinkUrl?, expiresAt?, error? }`, request order). For `processing`: `accepted`.
|
|
402
|
-
- Per-invitation semantics identical to `createAppMagicLink` (shared `createOneAppMagicLink` helper): `redirectPath` is omitted from the nimbus-ai call when absent, `addToAccessPrincipals` defaults to true upstream.
|
|
403
|
-
- Permission: `app_magic_link.write` with org-scoped authz (same as `createAppMagicLink`). No nimbus-ai changes — the gate fans out the existing per-user calls.
|
|
404
|
-
- MCP: `bulkCreateAppMagicLinks` is MCP-visible under the `appMagicLinks` prompt group; the prompt (v1.4.0) now tells agents to use it instead of looping `createAppMagicLink`.
|
|
405
|
-
|
|
406
|
-
### Workspace and portal member read
|
|
407
|
-
|
|
408
|
-
- Added `listWorkspaceMembers({ orgId, workspaceId })`.
|
|
409
|
-
- Added `listPortalMembers({ orgId, workspaceId })` (portal membership is represented by its underlying workspace, same semantics as `removePortalMember`).
|
|
410
|
-
- Both return `{ members: OrgWorkspaceMember[] }`, where each member includes the workspaceMember `id` (globalId) required by `removeWorkspaceMember` / `removePortalMember`.
|
|
411
|
-
|
|
412
|
-
### Add Portal Blank Note Block
|
|
413
|
-
|
|
414
|
-
- `POST /:orgId/portals/:portalId/pages/:pageId/blocks/blank-note` (`addPortalBlankNoteBlock`,
|
|
415
|
-
permission `portals.write`, MCP prompt groups `authz`, `sdk`, `portals`).
|
|
416
|
-
- Optional body `{ title }` (default `"New Document"`); returns
|
|
417
|
-
`{ noteId, blockId, pageId, branchId, seqs, staged: true }`.
|
|
418
|
-
- The whole use case lives in portal-service (`POST /portals/:portalId/pages/:pageId/blocks/blank-note`,
|
|
419
|
-
portal-service client `2.34.0`): note creation/sharing, canonical block shape, block
|
|
420
|
-
index from the effective draft state and the draft append. Gate makes exactly one
|
|
421
|
-
downstream call and adds no customizer business logic.
|
|
422
|
-
- Unlike `addPortalNoteBlock`, `pageId` may also be a page created earlier in the
|
|
423
|
-
active draft (portal-service resolves published + staged pages).
|
|
424
|
-
|
|
425
|
-
### Conditional removeOrgMember (optional preconditions)
|
|
426
|
-
|
|
427
|
-
- `OrgUsersApi.removeOrgMember` gained an optional `query` argument (`expectedRole`, `expectedJoinedAfter`).
|
|
428
|
-
- New exported types `OrgMemberExpectedRoleInQueryOptional`, `OrgMemberExpectedJoinedAfterInQueryOptional`.
|
|
429
|
-
- OpenAPI: two optional query parameters on `removeOrgMember`.
|
|
430
|
-
|
|
431
|
-
### Add portal Chat block + channel discovery
|
|
432
|
-
|
|
433
|
-
- Added `listPortalChatChannels` and `addPortalChatBlock` operations + SDK methods.
|
|
434
|
-
- Permission: `portals.write`, org-scoped authz.
|
|
435
|
-
- **BREAKING:** generic `addPortalBlock` no longer accepts `type='chat'`. Consumers
|
|
436
|
-
must migrate to `listPortalChatChannels` (for a channel target) followed by
|
|
437
|
-
`addPortalChatBlock`.
|
|
438
|
-
|
|
439
|
-
### Add Portal Embed Block
|
|
440
|
-
|
|
441
|
-
- `POST /:orgId/portals/:portalId/pages/:pageId/blocks/embed` (`addPortalEmbedBlock`,
|
|
442
|
-
permission `portals.write`, MCP prompt groups `authz`, `sdk`, `portals`).
|
|
443
|
-
- Body `{ title, embedType, url?, content?, showEmbedLink?, colspan?, rowspan?, height? }`:
|
|
444
|
-
- `embedType` — one of the 11 chooser variants (`custom`, `youtube`, `google-drive`,
|
|
445
|
-
`ms-doc`, `calendly`, `hubspot`, `airtable`, `loom`, `data-studio`, `figma`, `miro`).
|
|
446
|
-
The legacy `unknown` is rejected.
|
|
447
|
-
- For `custom`, pass `content` (URL or embed code) and not `url`; for every named
|
|
448
|
-
variant, pass `url` and not `content`. The service builds the canonical iframe.
|
|
449
|
-
- Defaults: `showEmbedLink` true, `colspan` 3, `rowspan` 1, `height` null.
|
|
450
|
-
- Returns `{ blockId, pageId, branchId, seqs, staged: true }`.
|
|
451
|
-
- The whole use case lives in portal-service (`POST /portals/:portalId/pages/:pageId/blocks/embed`):
|
|
452
|
-
semantic-input validation, URL/Custom-Embed normalization, canonical iframe/block
|
|
453
|
-
shape, index and the draft append. Gate makes exactly one downstream call and adds
|
|
454
|
-
no customizer business logic. No server-side fetch, DNS resolution or Iframely
|
|
455
|
-
request is performed.
|
|
456
|
-
- Like `addPortalBlankNoteBlock`, `pageId` may also be a page created earlier in the
|
|
457
|
-
active draft (portal-service resolves published + staged pages).
|
|
458
|
-
|
|
459
|
-
### createPortalPageWithNote moves into portal-service
|
|
460
|
-
|
|
461
|
-
- No public contract change: `POST /:orgId/portals/:portalId/pages/note`
|
|
462
|
-
(`createPortalPageWithNote`) keeps its operationId, path, request/response fields
|
|
463
|
-
and MCP prompt (`authz`, `sdk`, `portals`). Response is unchanged
|
|
464
|
-
(`{ noteId, menuItemId, pageId, url, branchId, seqs, staged: true }`).
|
|
465
|
-
- The contract now declares `403` (portal console / org access denied) and `409`
|
|
466
|
-
(draft conflict after the internal retry) as documented responses, and enforces
|
|
467
|
-
`title` `maxLength 100`.
|
|
468
|
-
- Gate makes exactly one downstream call and no longer imports customizer draft/event
|
|
469
|
-
types; note-service, editor-server, menu/slug/index and draft sequencing are gone
|
|
470
|
-
from the controller.
|
|
471
|
-
- portal-service response fields (`noteId`, `menuItemId`, `pageId`, `url`, `branchId`,
|
|
472
|
-
`seqs`; `lastSeq` on 409) are marked required in its OpenAPI, so the generated client
|
|
473
|
-
exposes them as non-optional.
|
|
474
|
-
|
|
475
|
-
### List portal Chat DM target users
|
|
476
|
-
|
|
477
|
-
- Added `listPortalChatUsers` operation + SDK method.
|
|
478
|
-
- Permission: `portals.write`, org-scoped authz. The org member search is
|
|
479
|
-
user-bound (org-service enforces `canViewMembers` on the acting user).
|
|
480
|
-
- Query params: `query` (optional, 2..100 chars, matched case-insensitively
|
|
481
|
-
against display name, username and email), `limit` (optional, default 20,
|
|
482
|
-
max 50), `cursor` (optional, opaque, bound to org+user+query).
|
|
483
|
-
- MCP `portals` prompt v1.23.0: added `listPortalChatUsers` guidance, the
|
|
484
|
-
DM-target flow for `addPortalChatBlock`, and the one-page-at-a-time browse /
|
|
485
|
-
"show the next 20?" pagination rule.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
## Consumer Impact
|
|
489
|
-
|
|
490
|
-
### 2026-05-20-app-magic-link-redirectpath-fix
|
|
491
|
-
|
|
492
|
-
- `createAppMagicLink` and `requestAppMagicLink` calls that omit
|
|
493
|
-
`redirectPath` now succeed (HTTP 200) instead of failing with HTTP 400
|
|
494
|
-
`"must be string"`. Callers that already send a `redirectPath` are
|
|
495
|
-
unaffected.
|
|
496
|
-
- No action required by SDK or apps-cli consumers — the wire contract is
|
|
497
|
-
unchanged; this purely fixes the no-deep-link request path.
|
|
498
|
-
|
|
499
|
-
### Isolated store secret guidance
|
|
500
|
-
|
|
501
|
-
App agents should resolve isolated stores at runtime through Gate using app token/source scope and stable aliases, or use platform-provided bindings when available.
|
|
502
|
-
|
|
503
|
-
Operator/CI migration commands may still use `storeId` in handoff logs, `fusebase.json`, or `--store-id`; that does not make `storeId` app runtime configuration.
|
|
504
|
-
|
|
505
|
-
### Get organization URL
|
|
506
|
-
|
|
507
|
-
- Apps and agents can stop hardcoding Fusebase hostnames when building org-scoped links.
|
|
508
|
-
- Portal domains and app magic-link hosts remain separate surfaces; use `listPortals` or app APIs for those.
|
|
509
|
-
|
|
510
|
-
### createPortalFolder
|
|
511
|
-
|
|
512
|
-
- Agents can create a portal folder without touching the customizer UI. The op
|
|
513
|
-
stages two change events exactly as the customizer's section-create flow does:
|
|
514
|
-
an `addPage` node followed by an `addMenuItem` (`portalSection`) pointing at
|
|
515
|
-
it, sharing a `batchId` so the folder is a single undo unit. The slug is
|
|
516
|
-
derived from the name (collision-suffixed against the existing menu) and the
|
|
517
|
-
item is appended to the end of the root sidebar.
|
|
518
|
-
- The change is staged, not live — consumers must tell the user to publish it.
|
|
519
|
-
|
|
520
|
-
### duplicatePortalItem
|
|
521
|
-
|
|
522
|
-
- Agents can duplicate portal content without the customizer UI. The op reads the
|
|
523
|
-
published menu subtree (and each page's content blocks) and re-emits fresh
|
|
524
|
-
`addPage` / `addMenuItem` / `addBlock` change events with new ids, remapping
|
|
525
|
-
parent ids so the copied subtree is self-contained. Copyable types are
|
|
526
|
-
`portalSection`, `note`, `portalPage`, `notesFolder`, `link`; portal
|
|
527
|
-
scaffolding (home, system dashboards, tasks/chat/all-pages, processes) is
|
|
528
|
-
skipped.
|
|
529
|
-
- Fusebase notes are **reused** (the copies reference the same note via
|
|
530
|
-
`targetId`, the note is not cloned), so later edits to the note affect both the
|
|
531
|
-
original and the copy.
|
|
532
|
-
- It reads **published** content only; pending draft changes should be published
|
|
533
|
-
before duplicating items that depend on them.
|
|
534
|
-
- The change is staged, not live — consumers must tell the user to publish it.
|
|
535
|
-
|
|
536
|
-
### updatePortalAccess
|
|
537
|
-
|
|
538
|
-
- Agents can set portal access without touching the customizer UI. The op maps
|
|
539
|
-
the high-level mode to portal-service `access.portal` flags exactly as the
|
|
540
|
-
customizer's `PortalAccessSelector` does; `access.console` (editor access) is
|
|
541
|
-
preserved.
|
|
542
|
-
- The change is staged, not live — consumers must tell the user to publish it.
|
|
543
|
-
|
|
544
|
-
### Bulk invite users to a portal
|
|
545
|
-
|
|
546
|
-
- App agents inviting several clients can issue one call instead of N blocking sequential requests.
|
|
547
|
-
- Single `inviteToPortal` is unchanged; behavior and response shape are preserved.
|
|
548
|
-
- Failures are isolated per invitee in `completed` mode — a single bad invite does not fail the batch.
|
|
549
|
-
|
|
550
|
-
### getMe returns user timezone and time-format preferences
|
|
551
|
-
|
|
552
|
-
- Apps can render dates/times in the user's timezone and clock format directly from `getMe`.
|
|
553
|
-
- Backward compatible: existing fields are unchanged; `preferences` is additive.
|
|
554
|
-
- Data sources: timezone from user-service `GET /users/{id}/timezone`; `timeFormat` derived from the
|
|
555
|
-
`dateTimeLocale` user var (defaults to `en-US` → `12h` when unset), matching the webnotes client.
|
|
556
|
-
- Preference resolution is non-fatal: if user-service is unreachable, `getMe` still returns identity with `preferences: null`.
|
|
557
|
-
|
|
558
|
-
### Token creation accepts isolated_store RLS break-glass permissions
|
|
559
|
-
|
|
560
|
-
- Studio and other token-creation UIs can include RLS break-glass permissions when issuing owner/manager tokens.
|
|
561
|
-
- No migration or config changes required.
|
|
562
|
-
|
|
563
|
-
### List an app's portal embeds
|
|
564
|
-
|
|
565
|
-
- public-api can proxy this op for the CLI; the frontend Resources popup and in-app runtime can fetch an app's portal embeds via the regenerated SDK.
|
|
566
|
-
- Org-scoped by design (per spec): returns all embeds in the org; workspace-level access enforcement remains portal-service's responsibility (same as `listPortals`).
|
|
567
|
-
- App-token callers must have `portals.read` in their scoped permissions, otherwise the token path returns 403.
|
|
568
|
-
- Requires `npm run build:sdk` to publish the new op in the SDK/OpenAPI.
|
|
569
|
-
|
|
570
|
-
### publishPortalDraft
|
|
571
|
-
|
|
572
|
-
- Agents can publish staged portal changes without the customizer UI. The op
|
|
573
|
-
delegates to portal-service `POST /portals/:id/draft/publish`, which applies the
|
|
574
|
-
whole active draft branch through the same content pipeline the customizer's
|
|
575
|
-
Publish button uses (every reducer/cache/share side effect preserved).
|
|
576
|
-
- It publishes the ENTIRE staged draft (including any changes the owner staged
|
|
577
|
-
manually in the customizer), so it should only be called when the user
|
|
578
|
-
explicitly wants the staged changes to go live.
|
|
579
|
-
- Returns 400 if the draft is empty (nothing staged) and 409 if the portal was
|
|
580
|
-
published elsewhere since the draft was based (the draft is stale and must be
|
|
581
|
-
re-staged before publishing again).
|
|
582
|
-
|
|
583
|
-
### App portal embeds — detect iframe embeds, add `kind`
|
|
584
|
-
|
|
585
|
-
- public-api / CLI / frontend receive the new `kind` field; downstream consumers
|
|
586
|
-
pass it through without behavioral or visual differentiation for now.
|
|
587
|
-
- Requires `npm run build:sdk` to publish the additive `kind` field and the updated
|
|
588
|
-
op description.
|
|
589
|
-
|
|
590
|
-
### Append workspace note content
|
|
591
|
-
|
|
592
|
-
Apps can now update existing notes by appending text or HTML instead of creating a new note. For Markdown sources, apps should render Markdown to HTML before calling the operation when formatting fidelity matters; otherwise send the content as text.
|
|
593
|
-
|
|
594
|
-
### Client → client app invite op (NIM-40541 / NIM-41954)
|
|
595
|
-
|
|
596
|
-
- The gate forwards the inviter's id to nimbus-ai, which performs the actual
|
|
597
|
-
"inviter has app access" check (strict principal match — never the permissive
|
|
598
|
-
managed-app User fallback) and creates the invite.
|
|
599
|
-
|
|
600
|
-
### Client → client portal invite authz (NIM-40541 / NIM-41950)
|
|
601
|
-
|
|
602
|
-
- Portal-client UI (NIM-41952) maps the above error codes to user messages.
|
|
603
|
-
- A client invitee is always forced to `orgRole='client'` and inherits the
|
|
604
|
-
inviter's binary access level (full-private vs shared-only); the request body's
|
|
605
|
-
`orgRole`/`isFullAccess` are ignored for client callers.
|
|
606
|
-
|
|
607
|
-
### Markdown (v3) notes CRUD
|
|
608
|
-
|
|
609
|
-
AI/MCP callers can manage markdown-native (v3) notes end-to-end without HTML conversion. Classic (v2) notes are untouched and keep using the existing notes operations. The operations depend on the note-service markdown API (NIM-42040/NIM-42041); until note-service ships it, calls fail upstream.
|
|
610
|
-
|
|
611
|
-
### Member removal and client deletion scheduling
|
|
612
|
-
|
|
613
|
-
Consumers can remove org members by numeric `userId`, remove workspace/portal members by `workspaceId` plus numeric `userId`, and schedule Client account deletion by numeric user id in the org context. Gate resolves internal membership ids before calling org-service.
|
|
614
|
-
|
|
615
|
-
### Bulk app magic-link invites
|
|
616
|
-
|
|
617
|
-
- App agents inviting several clients issue one call instead of N blocking sequential requests.
|
|
618
|
-
- Single `createAppMagicLink` is unchanged; behavior and response shape are preserved.
|
|
619
|
-
- Failures are isolated per invitee in `completed` mode — one bad invite does not fail the batch.
|
|
620
|
-
|
|
621
|
-
### Workspace and portal member read
|
|
622
|
-
|
|
623
|
-
Apps can list members of a specific facility workspace/portal instead of the whole-org roster, avoiding cross-facility leakage, and can obtain the membership `id` needed to remove a workspace/portal member. Both ops require `org.members.read` and org access. A workspace outside the org returns `404`.
|
|
624
|
-
|
|
625
|
-
### Add Portal Blank Note Block
|
|
626
|
-
|
|
627
|
-
- Additive: no existing operation changes. `addPortalNoteBlock` stays the way to
|
|
628
|
-
embed an already-created, already-shared note.
|
|
629
|
-
- Requires portal-service `>= 2.34.0` deployed; on an older portal-service the
|
|
630
|
-
operation returns 404 from the downstream route.
|
|
631
|
-
|
|
632
|
-
### Conditional removeOrgMember (optional preconditions)
|
|
633
|
-
|
|
634
|
-
- Existing callers are unaffected — omitting both params keeps today's behavior.
|
|
635
|
-
- Invite-cancel flows should capture a timestamp before inviting and pass it as `expectedJoinedAfter` (plus the invited `expectedRole`), so undoing an invite can never drop a pre-existing or since-promoted member.
|
|
636
|
-
- `expectedJoinedAfter` is in **seconds**, matching org-service `member.createdAt`. Passing milliseconds fails closed (409, nothing removed).
|
|
637
|
-
|
|
638
|
-
### Add portal Chat block + channel discovery
|
|
639
|
-
|
|
640
|
-
- Agents can add a Chat block by exact channel id or a DM user id without touching
|
|
641
|
-
chat-backend directly.
|
|
642
|
-
- Any caller passing `type='chat'` to `addPortalBlock` now gets a 400 and must use
|
|
643
|
-
`addPortalChatBlock`.
|
|
644
|
-
|
|
645
|
-
### Add Portal Embed Block
|
|
646
|
-
|
|
647
|
-
- Requires portal-service with the `/blocks/embed` route deployed; on an older
|
|
648
|
-
portal-service the operation returns 404 from the downstream route.
|
|
649
|
-
- Other `addPortalBlock` types are unchanged.
|
|
650
|
-
|
|
651
|
-
### createPortalPageWithNote moves into portal-service
|
|
652
|
-
|
|
653
|
-
- Backward compatible: existing callers/agents see the same operation and response.
|
|
654
|
-
- Requires the portal-service release that adds `POST /portals/:portalId/pages/note`
|
|
655
|
-
deployed first; on an older portal-service the operation returns 404 from the
|
|
656
|
-
downstream route. Deploy portal-service before this Gate change.
|
|
657
|
-
|
|
658
|
-
### List portal Chat DM target users
|
|
659
|
-
|
|
660
|
-
- Agents can resolve a DM target by name, username or email — or browse the
|
|
661
|
-
members when the person is not known — without guessing a user id and without
|
|
662
|
-
loading the full organization user list.
|
|
663
|
-
- `email` is returned so an agent can distinguish two users sharing a display name.
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
## Verification
|
|
667
|
-
|
|
668
|
-
### 2026-05-20-app-magic-link-redirectpath-fix
|
|
669
|
-
|
|
670
|
-
- `npm run build` — clean.
|
|
671
|
-
- `npm run lint` — 0 errors (5 pre-existing `dist/` warnings).
|
|
672
|
-
- `npm test` — 216 pass / 1 skipped. `tests/unit/app-magic-links-controller.test.ts`
|
|
673
|
-
updated: the two tests that previously asserted `redirectPath: null` on the
|
|
674
|
-
wire now assert the key is **absent** when omitted, plus a new case that an
|
|
675
|
-
empty-string `redirectPath` is forwarded verbatim (19 cases in the suite).
|
|
676
|
-
|
|
677
|
-
### 2026-05-27 — Visitor access vs open `/api/*` (skill guidance)
|
|
678
|
-
|
|
679
|
-
- `npm test -- --runInBand tests/unit/mcp-prompts.test.ts`
|
|
680
|
-
- `npm run mcp:skills:generate`
|
|
681
|
-
- `npm run mcp:skills:validate`
|
|
682
|
-
- `npm run mcp:skills:copy-to-apps-cli:local` (optional)
|
|
683
|
-
|
|
684
|
-
### Isolated store secret guidance
|
|
685
|
-
|
|
686
|
-
- Ran `npm run mcp:skills:generate`.
|
|
687
|
-
- `npm run mcp:skills:validate` is blocked in this environment because the `skills-ref` Python module is missing.
|
|
688
|
-
|
|
689
|
-
### Get organization URL
|
|
690
|
-
|
|
691
|
-
- `npm test -- tests/unit/org-url-service.test.ts tests/unit/orgs-controller.test.ts`
|
|
692
|
-
- `npm run build:sdk`
|
|
693
|
-
- `npm run mcp:skills:generate`
|
|
694
|
-
- `npm run mcp:skills:validate`
|
|
695
|
-
|
|
696
|
-
### createPortalFolder
|
|
697
|
-
|
|
698
|
-
- `npm run build` (regenerates runtime schema defs, tsc).
|
|
699
|
-
- `npm test` — unit tests pass, incl. new `createPortalFolder` coverage
|
|
700
|
-
(batch order, slug collision, icon, 409 retry, empty-name 400, 404).
|
|
701
|
-
- `npm run lint` — clean.
|
|
702
|
-
|
|
703
|
-
### duplicatePortalItem
|
|
704
|
-
|
|
705
|
-
- `npm run build:sdk` / `npm run build` (regenerates schema defs + SDK, tsc).
|
|
706
|
-
- `npm test` — unit tests pass, incl. new `duplicatePortalItem` coverage
|
|
707
|
-
(subtree copy with fresh ids + reused note + detached block, name override,
|
|
708
|
-
item-not-found 400, non-copyable-type 400, graceful block-read failure, 404).
|
|
709
|
-
- `npm run lint` — clean (only pre-existing `no-console` warnings).
|
|
710
|
-
|
|
711
|
-
### updatePortalAccess
|
|
712
|
-
|
|
713
|
-
- `npm run build` (regenerates runtime schema defs, tsc).
|
|
714
|
-
- `npm test` — 265 unit tests pass, incl. new `updatePortalAccess` coverage
|
|
715
|
-
(mode→flags mapping, console preservation, 409 retry, 404).
|
|
716
|
-
- `npm run lint` — clean.
|
|
717
|
-
|
|
718
|
-
### Bulk invite users to a portal
|
|
719
|
-
|
|
720
|
-
- `npm test -- tests/unit/portals-bulk-invite-controller.test.ts tests/unit/concurrency.test.ts tests/unit/portals-controller.test.ts`
|
|
721
|
-
- `npm run build`
|
|
722
|
-
- `npm run build:sdk`
|
|
723
|
-
- `npm run mcp:skills:generate` (validate blocked locally: `skills-ref` Python module unavailable in this environment)
|
|
724
|
-
|
|
725
|
-
### getMe returns user timezone and time-format preferences
|
|
726
|
-
|
|
727
|
-
- `npm run build`
|
|
728
|
-
- `npm test -- tests/unit/me-controller.test.ts`
|
|
729
|
-
|
|
730
|
-
### Token creation accepts isolated_store RLS break-glass permissions
|
|
731
|
-
|
|
732
|
-
- `npm test -- tests/unit/permissions.test.ts`
|
|
733
|
-
- Create a token via `POST /tokens` with `isolated_store.rls.bypass` in `permissions`.
|
|
734
|
-
|
|
735
|
-
### List an app's portal embeds
|
|
736
|
-
|
|
737
|
-
- `eslint` and `tsc --noEmit` clean on the changed files.
|
|
738
|
-
- Runtime (user): `GET /:orgId/apps/:appId/portal-embeds` with an internal user and with an app token — returns the deduped per-page list; a user lacking `portals.read` is denied.
|
|
739
|
-
|
|
740
|
-
### publishPortalDraft
|
|
741
|
-
|
|
742
|
-
- `npm run build` (regenerates runtime schema defs, tsc).
|
|
743
|
-
- `npm run build:sdk` (regenerates SDK + OpenAPI, with isolated-store feature flags).
|
|
744
|
-
- `npm test` — unit tests pass, incl. new `publishPortalDraft` coverage
|
|
745
|
-
(success, empty-draft 400, stale 409, portal-not-found 404).
|
|
746
|
-
- `npm run lint` — clean.
|
|
747
|
-
|
|
748
|
-
### App portal embeds — detect iframe embeds, add `kind`
|
|
749
|
-
|
|
750
|
-
- `eslint` and `tsc --noEmit` clean on the changed files (whole-project tsc: 0
|
|
751
|
-
errors).
|
|
752
|
-
- Runtime (user): embed an app via a regular `embed` block (managed or custom host,
|
|
753
|
-
with path/query) → page appears with `kind: 'iframe'`; brick + iframe on the same
|
|
754
|
-
page → one `app-feature` row; nimbus-ai down / app unpublished → brick results
|
|
755
|
-
returned normally. Works for internal-user and app-token auth.
|
|
756
|
-
|
|
757
|
-
### Append workspace note content
|
|
758
|
-
|
|
759
|
-
- `npm run build:sdk`
|
|
760
|
-
- `npm run mcp:skills:generate`
|
|
761
|
-
- `npm run mcp:skills:validate`
|
|
762
|
-
- `npm run mcp:skills:copy-to-apps-cli:local`
|
|
763
|
-
|
|
764
|
-
### Client → client app invite op (NIM-40541 / NIM-41954)
|
|
765
|
-
|
|
766
|
-
- `npm run build`, `npm run build:sdk` (generated SDK tsc green).
|
|
767
|
-
- Unit test: gate controller forwards the inviter id and maps the no-context
|
|
768
|
-
case to 403.
|
|
769
|
-
|
|
770
|
-
### Client → client portal invite authz (NIM-40541 / NIM-41950)
|
|
771
|
-
|
|
772
|
-
- `npm test` (controller authz matrix + inheritance, 16 cases in
|
|
773
|
-
`tests/unit/portals-bulk-invite-controller.test.ts`).
|
|
774
|
-
- `npm run build`, `npm run lint` (no new warnings).
|
|
775
|
-
|
|
776
|
-
### Markdown (v3) notes CRUD
|
|
777
|
-
|
|
778
|
-
- `npm run build`
|
|
779
|
-
- `npm run build:sdk`
|
|
780
|
-
- `npm run mcp:skills:generate`
|
|
781
|
-
- `npm run mcp:skills:validate`
|
|
782
|
-
- `npm test` (unit suites for the new controller, client, and contracts)
|
|
783
|
-
|
|
784
|
-
### Member removal and client deletion scheduling
|
|
785
|
-
|
|
786
|
-
- `npm test -- --runTestsByPath tests/unit/org-users-controller.test.ts`
|
|
787
|
-
- `npm run build:sdk`
|
|
788
|
-
- `npm run build`
|
|
789
|
-
- `npm run mcp:skills:generate`
|
|
790
|
-
- `npm run mcp:skills:validate`
|
|
791
|
-
|
|
792
|
-
### Bulk app magic-link invites
|
|
793
|
-
|
|
794
|
-
- `npm run build`
|
|
795
|
-
- `npm run build:sdk`
|
|
796
|
-
- `npm run mcp:skills:generate` + `npm run mcp:skills:validate`
|
|
797
|
-
- `npm test`, `npm run lint`
|
|
798
|
-
- Unit tests for the bulk controller follow in NIM-42093.
|
|
799
|
-
|
|
800
|
-
### Workspace and portal member read
|
|
801
|
-
|
|
802
|
-
- `npm test -- --runTestsByPath tests/unit/org-users-controller.test.ts`
|
|
803
|
-
- `npm run build:sdk`
|
|
804
|
-
- `npm run build`
|
|
805
|
-
- `npm run mcp:skills:generate`
|
|
806
|
-
- `npm run mcp:skills:validate`
|
|
807
|
-
|
|
808
|
-
### getMe no longer requires health.read
|
|
809
|
-
|
|
810
|
-
- `npm run test:unit -- tests/unit/authz-op-registry-bridge.test.ts`
|
|
811
|
-
- `npm run build`
|
|
812
|
-
|
|
813
|
-
### Add Portal Blank Note Block
|
|
814
|
-
|
|
815
|
-
- `npx tsc --noEmit`, `npm run lint`, `npx jest tests/unit/portals-controller.test.ts` (84 passed).
|
|
816
|
-
- `npm run mcp:skills:generate` + `npm run mcp:skills:validate`.
|
|
817
|
-
- portal-service: `npx jest lib/controllers/contents/addBlankNoteBlock.spec.ts` (13 passed).
|
|
818
|
-
|
|
819
|
-
### Conditional removeOrgMember (optional preconditions)
|
|
820
|
-
|
|
821
|
-
- `npm test`
|
|
822
|
-
- `npm run build`
|
|
823
|
-
- `npm run mcp:skills:generate && npm run mcp:skills:validate`
|
|
824
|
-
|
|
825
|
-
### Add portal Chat block + channel discovery
|
|
826
|
-
|
|
827
|
-
- `npm test -- tests/unit/portals-contracts.test.ts tests/unit/portals-controller.test.ts`
|
|
828
|
-
- `npm run build`
|
|
829
|
-
|
|
830
|
-
### Add Portal Embed Block
|
|
831
|
-
|
|
832
|
-
- portal-service: `npx jest lib/controllers/contents/embedNormalization.spec.ts
|
|
833
|
-
lib/controllers/contents/addPortalEmbedBlock.spec.ts` (green); `npm run build` green.
|
|
834
|
-
- gate: `npx tsc --noEmit`, `npm run lint`, `npx jest tests/unit/portals-controller.test.ts
|
|
835
|
-
tests/unit/portals-contracts.test.ts tests/unit/portals-embed-block-seam.test.ts`.
|
|
836
|
-
- `npm run mcp:skills:generate` + `npm run mcp:skills:validate`.
|
|
837
|
-
|
|
838
|
-
### createPortalPageWithNote moves into portal-service
|
|
839
|
-
|
|
840
|
-
- portal-service: `npx jest lib/controllers/contents/createPortalPageWithNote.spec.ts`.
|
|
841
|
-
- gate: `npm run build`, `npm run lint`,
|
|
842
|
-
`npm test -- tests/unit/portals-controller.test.ts tests/unit/portals-contracts.test.ts tests/unit/portals-page-with-note-seam.test.ts`.
|
|
843
|
-
- `npm run mcp:skills:generate` + `npm run mcp:skills:validate`.
|
|
844
|
-
|
|
845
|
-
### List portal Chat DM target users
|
|
846
|
-
|
|
847
|
-
- `npm test -- tests/unit/portals-contracts.test.ts tests/unit/portals-chat-block-seam.test.ts`
|
|
848
|
-
- `npm run build`
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
## Follow-ups
|
|
852
|
-
|
|
853
|
-
### 2026-05-20-app-magic-link-redirectpath-fix
|
|
854
|
-
|
|
855
|
-
- None. End-to-end QA re-runs the no-`redirectPath` invite and self-service
|
|
856
|
-
cases against the dev Gate after redeploy.
|
|
857
|
-
|
|
858
|
-
### Isolated store secret guidance
|
|
859
|
-
|
|
860
|
-
- Restore/install the local `skills_ref` validator dependency so MCP skill validation can run end to end.
|
|
861
|
-
|
|
862
|
-
### Get organization URL
|
|
863
|
-
|
|
864
|
-
- None.
|
|
865
|
-
|
|
866
|
-
### createPortalFolder
|
|
867
|
-
|
|
868
|
-
- No new portal-service endpoint was needed: reuses the existing draft endpoints
|
|
869
|
-
- `addPage`/`addMenuItem` reducer. Sibling content ops in NIM-40621
|
|
870
|
-
(createPortalPageWithNote, addPortalNoteBlock, duplicatePortalItem) follow the
|
|
871
|
-
same draft-staging pattern.
|
|
872
|
-
|
|
873
|
-
### duplicatePortalItem
|
|
874
|
-
|
|
875
|
-
- No new portal-service endpoint was needed: reuses the existing draft endpoints
|
|
876
|
-
- `addPage`/`addMenuItem`/`addBlock` reducer and the `GET /blocks` reader.
|
|
877
|
-
- A future iteration could deep-clone notes and replicate sync/permission/process
|
|
878
|
-
fidelity (closer to `apiPostMenusCopy`).
|
|
879
|
-
|
|
880
|
-
### updatePortalAccess
|
|
881
|
-
|
|
882
|
-
- No new portal-service endpoint was needed: reuses the existing draft endpoints
|
|
883
|
-
- `updateAccessSettings` reducer. Sibling content ops in NIM-40621 follow the
|
|
884
|
-
same draft-staging pattern.
|
|
885
|
-
|
|
886
|
-
### Bulk invite users to a portal
|
|
887
|
-
|
|
888
|
-
- Background mode is fire-and-forget within the request pod; if durable retry/observability is later required, move bulk processing to a worker/queue.
|
|
889
|
-
|
|
890
|
-
### getMe returns user timezone and time-format preferences
|
|
891
|
-
|
|
892
|
-
- SDK artifact regeneration (`npm run build:sdk`) is currently blocked by a pre-existing
|
|
893
|
-
IsolatedStores SDK drift (`generated/sdk-client/src/extras/sqlMigrationBundle.ts` imports
|
|
894
|
-
`IsolatedStoreSqlMigrationBundleContract`, which the regenerator no longer emits). The published
|
|
895
|
-
SDK should be rebuilt by the SDK publish pipeline once that drift is resolved.
|
|
896
|
-
|
|
897
|
-
### Token creation accepts isolated_store RLS break-glass permissions
|
|
898
|
-
|
|
899
|
-
- None.
|
|
900
|
-
|
|
901
|
-
### List an app's portal embeds
|
|
902
|
-
|
|
903
|
-
- Regenerate the SDK (`npm run build:sdk`) before downstream (public-api / frontend) consume the op.
|
|
904
|
-
- M3 (public-api proxy) and M4 (frontend Resources row) are separate milestones.
|
|
905
|
-
|
|
906
|
-
### publishPortalDraft
|
|
907
|
-
|
|
908
|
-
- No new portal-service endpoint was needed: reuses the existing
|
|
909
|
-
`POST /portals/:id/draft/publish` (NIM-41548). Sibling content ops in NIM-40621
|
|
910
|
-
stage into the same draft this op publishes.
|
|
911
|
-
|
|
912
|
-
### App portal embeds — detect iframe embeds, add `kind`
|
|
913
|
-
|
|
914
|
-
- Regenerate + publish the SDK (`npm run build:sdk` / `build:sdk:push`); note the
|
|
915
|
-
version for the M3A (public-api) and M4A (nx-frontend) pin bumps.
|
|
916
|
-
- M3A/M4A/M5A pass `kind` through unchanged.
|
|
917
|
-
|
|
918
|
-
### Append workspace note content
|
|
919
|
-
|
|
920
|
-
- Full note replacement/editing remains a separate product/API decision because it needs a canonical Markdown-to-editor-content conversion path.
|
|
921
|
-
|
|
922
|
-
### Client → client portal invite authz (NIM-40541 / NIM-41950)
|
|
923
|
-
|
|
924
|
-
- Enable `FEATURE_FLAGS=client_invite` per environment (dev for QA, prod on
|
|
925
|
-
rollout) — the feature is fail-closed until then.
|
|
926
|
-
- App client→client invite op is tracked separately (NIM-41954, ST-5).
|
|
927
|
-
|
|
928
|
-
### Markdown (v3) notes CRUD
|
|
929
|
-
|
|
930
|
-
- Switch the plain-HTTP note-service markdown client to the generated `@internal/note-service` SDK once note-service publishes the markdown endpoints.
|
|
931
|
-
- Add e2e-sdk/mcp-e2e coverage once the note-service contract is live (fake-note-service markdown routes should mirror the real service).
|
|
932
|
-
|
|
933
|
-
### Member removal and client deletion scheduling
|
|
934
|
-
|
|
935
|
-
- None.
|
|
936
|
-
|
|
937
|
-
### Bulk app magic-link invites
|
|
938
|
-
|
|
939
|
-
- Background mode is fire-and-forget within the request pod; if durable retry/observability is later required, move bulk processing to a worker/queue.
|
|
940
|
-
|
|
941
|
-
### Workspace and portal member read
|
|
942
|
-
|
|
943
|
-
- App-side Ovation Benchmarking integration is a separate task once Gate ships.
|
|
944
|
-
|
|
945
|
-
### Add Portal Blank Note Block
|
|
946
|
-
|
|
947
|
-
- No automatic unshare/remove compensation: a failed share or a terminal draft
|
|
948
|
-
append can leave an empty orphan note (logged as a structured warning with
|
|
949
|
-
phase/portalId/pageId/workspaceId/noteId/blockId). Cleanup is tracked separately.
|
|
950
|
-
- SDK/OpenAPI artifacts are regenerated by the release `build(sdk)` commit, not here.
|
|
951
|
-
|
|
952
|
-
### Conditional removeOrgMember (optional preconditions)
|
|
953
|
-
|
|
954
|
-
- Query-parameter schemas are referenced but not emitted into `components/schemas` by the OpenAPI generator (pre-existing — same for `EmailInQueryOptional`).
|
|
955
|
-
|
|
956
|
-
### Add portal Chat block + channel discovery
|
|
957
|
-
|
|
958
|
-
- `listPortalChatUsers` (DM user search) + the dedicated org-service search endpoint
|
|
959
|
-
are tracked separately: org-service member rows carry no name fields, so a proper
|
|
960
|
-
name search needs a new org-service endpoint joining user profiles + an SDK
|
|
961
|
-
regenerate/publish. The DM target already works end-to-end via `addPortalChatBlock`
|
|
962
|
-
when the caller has the userId.
|
|
963
|
-
|
|
964
|
-
### Add Portal Embed Block
|
|
965
|
-
|
|
966
|
-
- Private/loopback rejection is a literal-IP/well-known-name check (no DNS
|
|
967
|
-
resolution, per spec); a hostname that resolves to a private address is not caught.
|
|
968
|
-
- SDK/OpenAPI artifacts are regenerated by the release `build(sdk)` commit, not here.
|
|
969
|
-
|
|
970
|
-
### createPortalPageWithNote moves into portal-service
|
|
971
|
-
|
|
972
|
-
- No automatic unshare/remove compensation: a failed share, initial-content paste or
|
|
973
|
-
terminal draft append can leave an orphan note (logged as a structured warning with
|
|
974
|
-
phase/portalId/parentId/workspaceId/noteId/menuItemId/pageId/blockId). Cleanup is
|
|
975
|
-
tracked separately.
|
|
976
|
-
- SDK/OpenAPI artifacts are regenerated by the release `build(sdk)` commit, not here.
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
## Breaking
|
|
980
|
-
|
|
981
|
-
### Add Portal Embed Block
|
|
982
|
-
|
|
983
|
-
- `addPortalBlock` **rejects `type: "embed"`**. Direct callers that previously staged
|
|
984
|
-
an embed via the generic op must migrate:
|
|
985
|
-
|
|
986
|
-
```
|
|
987
|
-
// before
|
|
988
|
-
addPortalBlock({ pageId, type: "embed", title,
|
|
989
|
-
properties: { embedType, content, contentUrl, showEmbedLink } });
|
|
990
|
-
|
|
991
|
-
// after — named variant
|
|
992
|
-
addPortalEmbedBlock({ orgId, portalId, pageId, title, embedType, url });
|
|
993
|
-
// after — custom embed
|
|
994
|
-
addPortalEmbedBlock({ orgId, portalId, pageId, title, embedType: "custom", content });
|
|
995
|
-
```
|
|
996
|
-
|
|
997
|
-
`contentUrl` and the iframe are no longer supplied by the caller — the service
|
|
998
|
-
generates them from `url` (named) or stores `content` verbatim (custom).
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
## Changes
|
|
1002
|
-
|
|
1003
|
-
### 2026-05-27 — Visitor access vs open `/api/*` (skill guidance)
|
|
1004
|
-
|
|
1005
|
-
- `src/mcp/prompts/fusebase-auth.ts` — `1.1.0` → `1.2.0`: section **Visitor Access Vs Open API (Platform Edge)**.
|
|
1006
|
-
- `src/mcp/prompts/app-magic-links.ts` — `1.2.0` → `1.3.0`: section **Platform Edge: Visitor Token And `/api/*`**; tighten `visitor` principal wording.
|
|
1007
|
-
- Regenerated `generated/claude_skills/fusebase-gate/references/{fusebase-auth,app-magic-links}.md`.
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
## Consumer impact
|
|
1011
|
-
|
|
1012
|
-
### 2026-05-27 — Visitor access vs open `/api/*` (skill guidance)
|
|
1013
|
-
|
|
1014
|
-
Documentation / agent guidance only — no API or permission changes.
|
|
1015
|
-
|
|
1016
|
-
### getMe no longer requires health.read
|
|
1017
|
-
|
|
1018
|
-
- **Apps without `health.read` in the published grant** can call `getMe` again (service tokens, visitor browser tokens).
|
|
1019
|
-
- **Apps that already include `health.read`** are unchanged.
|
|
1020
|
-
- Do **not** remove `health.read` from grants solely because of this change — other ops may still require it.
|
|
1021
|
-
- Continue using `FBS_ORG_ID` / `getMyOrgAccess` for org resolution; do not rely on `getMe().auth.scopes` for visitor sessions.
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
## Dependency
|
|
1025
|
-
|
|
1026
|
-
### Client → client app invite op (NIM-40541 / NIM-41954)
|
|
1027
|
-
|
|
1028
|
-
- Requires the nimbus-ai endpoint `apiCreateAppClientInviteMagicLink`
|
|
1029
|
-
(`POST /orgs/:org/apps/:appId/client-invites`, secret/superuser-trusted),
|
|
1030
|
-
shipped on the nimbus-ai `NIM-40541` branch. Until it deploys, this op returns
|
|
1031
|
-
the upstream error.
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
## Limitations (iteration 1)
|
|
1035
|
-
|
|
1036
|
-
### duplicatePortalItem
|
|
1037
|
-
|
|
1038
|
-
- Same-portal only; cross-portal copy is out of scope.
|
|
1039
|
-
- Notes are referenced, not deep-cloned.
|
|
1040
|
-
- Sync-origin linkage, external-resource copies, process status, and per-page
|
|
1041
|
-
permission copies (handled by portal-service's heavyweight `apiPostMenusCopy`)
|
|
1042
|
-
are not replicated — copies use allow-all permissions and are detached from
|
|
1043
|
-
any sync origin. The owner reviews before publishing.
|
|
1044
|
-
- A copy that would exceed the portal-service draft cap (200 change events) is
|
|
1045
|
-
rejected with a clear 400.
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
## Why
|
|
1049
|
-
|
|
1050
|
-
### getMe no longer requires health.read
|
|
1051
|
-
|
|
1052
|
-
- `getMe` is the primary identity introspection op for Fusebase Apps.
|
|
1053
|
-
- Requiring `health.read` caused production lockouts when app grants omitted it after `fusebase analyze gate` / `--sync-gate-permissions` (see issue `041-benchmarking-app`, escalation `2026-07-13-platform-gate-2.3.29-auth-breaking-changes.md`).
|
|
1054
|
-
- `resolveOperationPermissions` now reports `required_permission: null` for `getMe`, aligned with `getHealth`.
|