@assembly-js/app-foundation 0.4.1 → 0.4.3

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/CHANGELOG.md CHANGED
@@ -19,6 +19,12 @@ A patch release never changes what an app's build requires; a minor release may
19
19
  not by hand; see `changes/README.md`. CI fails a PR that changes a check script without a
20
20
  `bump: minor` change file.
21
21
 
22
+ ## 0.4.3
23
+ - [guidance] The build agent now knows `viewerClient` works for cross-app calls made by a client contact, and keeps `installerClient` for team members, cron, webhooks and background work.
24
+
25
+ ## 0.4.2
26
+ - [guidance] The build agent's main guide now says a client contact's cross-app calls use `viewerClient`, while team members and background work keep `installerClient`.
27
+
22
28
  ## 0.4.1
23
29
  - [guidance] Build agents append to `requiredSecrets` instead of rewriting it, and remove an entry only when the user asks to disconnect that integration.
24
30
 
package/CLAUDE.md CHANGED
@@ -52,7 +52,7 @@ Assembly-specific gotchas live in `.claude/skills/<name>/SKILL.md`. Invoke them
52
52
  - `assembly-app-inner-location` — mount `<LocationReporter />` so the host mirrors the app's inner page in its address bar; deep-link a notification or link to a specific record with `linkTo({ to: 'app', installId, path })`, never to the app root. Never hand-write location reporting.
53
53
  <!-- /feature:deep-link-app-inner-location -->
54
54
  <!-- feature:cross-app-apis -->
55
- - `assembly-app-clients` — calling another workspace app through its generated client in `generated/apps/<appId>/` (`installerClient`, by verb and path), and rendering an empty state that names the app when the call is refused instead of a 500.
55
+ - `assembly-app-clients` — calling another workspace app through its generated client in `generated/apps/<appId>/` (`viewerClient` for a client contact, `installerClient` for team members and background work, by verb and path), and rendering an empty state that names the app when the call is refused instead of a 500.
56
56
  <!-- /feature:cross-app-apis -->
57
57
  - `assembly-ai` — AI features, including speech-to-text, via Assembly's OpenAI proxy (no API keys needed)
58
58
  - `assembly-storage` — app-private file uploads/downloads (NOT the portal Files module). The storage helpers come from `@assembly-js/app-foundation/server` — import them, don't rewrite them. POST only `{ fileName }` (a bare name) and store the key the platform returns; never build the S3 key yourself.
@@ -462,7 +462,7 @@ Relationships:
462
462
  If the user names another app, read its file in `.assembly/docs/apps/` and follow the call recipe at the top of that file. If that folder is absent, the app is not callable (it may still need the Security update). Cross-app calls from pages, server actions and routes use `getInstallerAssembly()`; cron and webhooks use `getOfflineAssembly()`. Never return another app's response to a client unfiltered — scope any client data you forward. While a person is using the app, those calls use the installer client, because the platform does not yet prove which person the call is for. Once it can, use the viewer client for that person's data and keep the installer client for staff-wide reads.
463
463
  <!-- /feature:api-resources -->
464
464
  <!-- feature:cross-app-apis -->
465
- When `generated/apps/<appId>/` exists for that app, call it through the generated client instead (see the `assembly-app-clients` skill); the docs recipe is the fallback for an app with no folder there.
465
+ When `generated/apps/<appId>/` exists for that app, call it through the generated client instead (see the `assembly-app-clients` skill); the docs recipe is the fallback for an app with no folder there. The generated client supersedes the installer-client note above: a client contact's own calls use `viewerClient`, which the platform scopes to that contact; team members and background work use `installerClient`.
466
466
  <!-- /feature:cross-app-apis -->
467
467
 
468
468
  ---
@@ -115,8 +115,7 @@ export interface AppProxyOptions {
115
115
  *
116
116
  * `installer` calls as this app, with the same per-request mint as
117
117
  * getInstallerAssembly. `viewer` forwards the signed-in user's verified bearer,
118
- * which the platform refuses with `viewer_not_allowed` until it can prove which
119
- * person a cross-app call is for.
118
+ * which the platform scopes to that contact and refuses for a team member.
120
119
  */
121
120
  export declare function appProxyOptions(appId: string, caller: AppProxyCaller): AppProxyOptions;
122
121
  export declare function getAssemblyForRequest(req: NextRequest): Promise<AssemblyAPI | NextResponse>;
package/dist/assembly.js CHANGED
@@ -389,8 +389,7 @@ export async function getInstallerAssembly() {
389
389
  *
390
390
  * `installer` calls as this app, with the same per-request mint as
391
391
  * getInstallerAssembly. `viewer` forwards the signed-in user's verified bearer,
392
- * which the platform refuses with `viewer_not_allowed` until it can prove which
393
- * person a cross-app call is for.
392
+ * which the platform scopes to that contact and refuses for a team member.
394
393
  */
395
394
  export function appProxyOptions(appId, caller) {
396
395
  const origin = (trustedBaseUrl ?? OpenAPI.BASE).replace(/\/+$/, '');
package/dist/codegen.js CHANGED
@@ -131,10 +131,10 @@ import type { paths } from './types.js';
131
131
 
132
132
  export const APP_ID = ${JSON.stringify(appId)};
133
133
 
134
- // Calls as this app. Use it from pages, server actions, routes, cron and webhooks.
134
+ // Calls as this app. Use it for team members, cron, webhooks and background work.
135
135
  export const installerClient = createClient<paths>(appProxyOptions(APP_ID, 'installer'));
136
136
 
137
- // Calls as the signed-in person. Refused with viewer_not_allowed for now.
137
+ // Calls as the signed-in client contact. Refused for a team member.
138
138
  export const viewerClient = createClient<paths>(appProxyOptions(APP_ID, 'viewer'));
139
139
  `;
140
140
  }
@@ -155,8 +155,8 @@ const { data, error } = await installerClient.GET('/projects/{id}', {
155
155
  });
156
156
  \`\`\`
157
157
 
158
- - \`installerClient\` calls as this app. Use it from pages, server actions, routes, cron and webhooks, and filter anything you show to a client.
159
- - \`viewerClient\` calls as the signed-in person. The platform refuses it with \`viewer_not_allowed\` until it can prove which person the call is for, so use \`installerClient\` for now.
158
+ - \`installerClient\` calls as this app. Use it for pages and actions a team member runs, and from cron, webhooks and background work. Filter anything you show to a client.
159
+ - \`viewerClient\` calls as the signed-in client contact. The platform scopes the call to that contact, so do not filter what it returns. Use it for pages and actions a contact runs; it is refused for a team member.
160
160
 
161
161
  ## Operations
162
162
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assembly-js/app-foundation",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Versioned foundation helpers and build context for Assembly generated apps",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,7 +1,7 @@
1
1
  <!-- feature:cross-app-apis -->
2
2
  ---
3
3
  name: assembly-app-clients
4
- description: Use when the app calls another workspace app and `generated/apps/<appId>/` exists for it. Covers reading the generated README and types, calling `installerClient` by verb and path, and rendering an empty state that names the app when the call is refused (app_not_found, operation_not_found, viewer_not_allowed, callee_error) instead of throwing a 500.
4
+ description: Use when the app calls another workspace app and `generated/apps/<appId>/` exists for it. Covers reading the generated README and types, calling `viewerClient` or `installerClient` by verb and path, and rendering an empty state that names the app when the call is refused (app_not_found, operation_not_found, viewer_not_allowed, callee_error) instead of throwing a 500.
5
5
  ---
6
6
 
7
7
  ## Where the client lives
@@ -31,9 +31,11 @@ Call it from server code only: pages, server actions, route handlers, cron and w
31
31
 
32
32
  ## Installer or viewer
33
33
 
34
- Use `installerClient`. It calls as this app, so filter anything it returns to what the viewing client may see before you show it to a client.
34
+ `viewerClient` calls as the signed-in client contact. The platform scopes the call to that contact, so do not filter or double-check what it returns. Use it for pages and actions a contact runs. The platform refuses it for a team member.
35
35
 
36
- `viewerClient` calls as the signed-in person. The platform refuses it with `viewer_not_allowed` until it can prove which person the call is for, so use `installerClient` for now.
36
+ `installerClient` calls as this app, not as a person. Use it for pages and actions a team member runs, and from cron, webhooks and other background work. Filter anything you show to a client.
37
+
38
+ If an operation can be called by a contact, it must not make platform calls about clients, companies or the workspace as the caller. Contacts are refused on those routes and the call surfaces as a generic failure. Look things up with the app's own access, or store the display names on the row when the item is assigned.
37
39
 
38
40
  ## When the other app cannot answer
39
41
 
@@ -43,7 +45,7 @@ The other app can be uninstalled, can drop an operation, or can fail on its own.
43
45
  |---|---|---|
44
46
  | `app_not_found` | The app is not installed in this workspace, or this app may not call it. | Empty state: "<App name> is not available in this workspace." |
45
47
  | `operation_not_found` | The app no longer offers this operation. | Empty state: "<App name> no longer provides this." |
46
- | `viewer_not_allowed` | The call used `viewerClient`. | Nothing at runtime: switch the call to `installerClient`. |
48
+ | `viewer_not_allowed` | The platform refused the call for this person: a team member used `viewerClient`, or a contact could not be resolved. | For a team member, call with `installerClient`. For a contact, render an empty or permission state naming the app and never retry with `installerClient`. |
47
49
  | `callee_error` | The other app answered with its own failure. The most common case at runtime. | Error state: "<App name> could not load this right now." |
48
50
 
49
51
  Branch on the result and render a state that names the app: