@collective-intelligence/external-apps-server-sdk 0.1.9 → 0.1.11

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
@@ -503,47 +503,41 @@ const sent = await ci.messages.send({
503
503
  reached its rate limit; it is retryable, and the client repeats it with the
504
504
  same key.
505
505
 
506
- ## Member users
506
+ ## Application users and roles
507
507
 
508
- A confidential application administers the `member` users of its own
509
- Organization: people whose only role there is `member` (Organization identity
510
- and their own Knowledge; no CI Studio or dashboard). The Organization is the
511
- one the credentials were authorized for through `organizationId`; a request
512
- cannot reach another Organization.
508
+ Use `ci.users` and `ci.roles` with a delegated user token or service
509
+ credentials. CI checks the caller's current `users.create` and `assignRoles`
510
+ permissions. These methods replace the former `ci.members` API.
513
511
 
514
512
  ```ts
515
- const ci = createServerClient({
516
- authentication: { credentials, mode: "service" },
517
- tenantUrl
518
- })
519
-
520
- const created = await ci.members.createOrGet({
513
+ const created = await ci.users.createOrGet({
521
514
  email: "info@rossi.example",
522
515
  name: "Rossi Srl",
523
- organizationId
516
+ organizationId,
517
+ roles: ["patient"],
518
+ serverRole: "onboarding" // Optional for service calls; omission uses the credential default.
524
519
  })
525
- // created.value.result: { userId, email, name, outcome }
520
+ // On success: created.value.result = { userId, email, name, created, kind }
521
+ // kind is "member" or "external_app_user".
526
522
  ```
527
523
 
528
- - `createOrGet` resolves an email to one member user. `outcome` is `created`
529
- for a new CI user, `linked` when an existing CI user joined the Organization
530
- as a member, and `existing` when the user already was one. The email is
531
- matched ignoring letter case, and concurrent or retried calls return the
532
- same `userId`. It never sends CI's activation invitation and never accepts
533
- a pending Organization invitation for the email; the application owns
534
- onboarding.
535
- - `get({ userId })`, `list({ page?, pageSize? })` (at most 100 per page),
536
- `update({ userId, name })` and `remove({ userId })` act only on member users.
537
- `update` changes the name only of a user who belongs to no other
538
- Organization. `remove` ends the `member` membership and keeps the CI user.
539
- - A user who holds any other role in the Organization (an administrator, for
540
- example) is refused with `conflict` by every operation. `get`, `update` and
541
- `remove` answer `not_found` for a user who is not in the Organization.
542
- - Reads need the `members:read` scope and writes `members:write`. Only service
543
- credentials may call them: a delegated user token or an anonymous visitor
544
- gets `operation_not_allowed`.
545
- - Only `temporarily_unavailable` is retryable (the client retries it); a
546
- creation CI refuses is a non-retryable `internal_error`.
524
+ - `createOrGet` reuses the person's auth identity. A new app user has no
525
+ Organization membership. An existing member remains a member.
526
+ - `get({ organizationId, userId })` and
527
+ `list({ organizationId, page?, pageSize? })` read users accessible to this
528
+ app. A list result contains `items`.
529
+ - `deactivate({ organizationId, userId })` ends access to the calling app for
530
+ an app user it may deactivate; it does not remove Organization membership.
531
+ - `roles.list({ organizationId })` returns `items` with role `id` and `key`.
532
+ `roles.assign({ organizationId, userId, roles })` and `roles.revoke(...)`
533
+ change this app's assignments within the caller's `assignRoles` permission.
534
+ They cannot write Organization Roles, Groups, or another app's roles.
535
+ - Pass `serverRole` on a service request to select an allowed Server Role via
536
+ `x-external-server-role`. It applies only to that request; credentials and
537
+ subsequent calls retain their default. It does not impersonate a user.
538
+ - Refusals preserve the structured error, including a reason such as
539
+ `role_not_assignable`. Non-retryable refusals are returned without retrying.
540
+ CI records the actual user, or application and Server Role, as the actor.
547
541
 
548
542
  ## Durable files
549
543
 
@@ -579,6 +573,79 @@ await ci.workflows.invoke({
579
573
  - It needs the `ai:attachments` scope. Only service credentials may call it: a
580
574
  delegated user token or an anonymous visitor gets `operation_not_allowed`.
581
575
 
576
+ ### Entity File fields
577
+
578
+ A File field of an entity schema takes a different flow: the file becomes a
579
+ File value of the entity, not a stored file a workflow reads.
580
+ `resources.createUpload` asks CI for a single-use upload slot, the server PUTs
581
+ the bytes straight to storage, and the entity write through CI's resources API
582
+ names the slot's `uploadId` as `{ upload }` for that File field key:
583
+
584
+ ```ts
585
+ const slot = await ci.resources.createUpload({
586
+ field: "memberLogo",
587
+ filename: "logo.png",
588
+ mimeType: "image/png",
589
+ organizationId,
590
+ schemaId: "apito.vetrina.schemas.member-company"
591
+ })
592
+ if (slot.ok) {
593
+ const { headers, method, uploadId, url } = slot.value.result
594
+ await fetch(url, { body: logo, headers, method })
595
+ // The entity write then sends { memberLogo: { upload: uploadId } }.
596
+ }
597
+ ```
598
+
599
+ - Two credentials can ask for a slot, as for entity writes: the member's
600
+ delegated token (`exchangeMemberSession`), writing as that member; or the
601
+ application's service credential, for schemas declared by the application's
602
+ own package only, writing as the Organization default service principal.
603
+ `schemaId` is the schema id or FQN.
604
+ - A refusal resolves `{ ok: false, error }` with CI's `correlationId` and a
605
+ `code`; `upload_not_supported` means the Organization storage cannot presign
606
+ uploads. Only `retryable` failures are retried.
607
+ - Each attempt, request and response body, is bounded by `timeoutMs`
608
+ (10 seconds by default); a CI that stops answering ends in a retryable
609
+ `unavailable`. `signal` cancels: a cancelled request rejects with the
610
+ signal's reason.
611
+ - The PUT is the caller's: send exactly `headers` to `url` with `method`. No
612
+ file bytes pass through CI's API. A slot is single use: the first successful
613
+ write consumes it; request a new one to retry.
614
+ - The write form of a File field key is `{ upload }` to add the uploaded file,
615
+ `{ id }` (or a File value read before) to keep a file, an ordered array for a
616
+ multiple field, or `null` to remove every file. Omit the key to keep its
617
+ files.
618
+ - `/runtime/effect` exports the Effect schemas `EntityFileValue` (a File value
619
+ as CI reads return it), `EntityFileWrite` (one file of a write) and
620
+ `EntityFileFieldWrite` (the write form of a File field key). Their types
621
+ equal the client SDK types of the same name. They validate CI entity
622
+ values; a portable resource form has no File field, so
623
+ `projectResourceSchema` refuses them.
624
+
625
+ ## Runtime origin
626
+
627
+ The browser origins of an Organization are the `allowedOrigins` that its
628
+ installed applications declare, plus one runtime origin per application. A
629
+ confidential application sets its runtime origin, for example the URL of each
630
+ new deployment; CI keeps storage CORS in line with the Organization origins, so
631
+ a browser on that origin can upload to a presigned storage URL.
632
+
633
+ ```ts
634
+ await ci.origins.setRuntime({
635
+ organizationId,
636
+ origin: `https://${process.env.VERCEL_URL}`
637
+ })
638
+ // value.result: { origin }
639
+ ```
640
+
641
+ - Each call replaces the application's previous runtime origin, so the list
642
+ does not grow; declare stable origins, such as the production site, in
643
+ `allowedOrigins`.
644
+ - `origin` is an exact HTTPS origin (`https://host[:port]`, no path), or a
645
+ loopback HTTP origin with a port; other values get `invalid_request`.
646
+ - It needs the `origins:write` scope. Only service credentials may call it: a
647
+ delegated user token or an anonymous visitor gets `operation_not_allowed`.
648
+
582
649
  ## Member company links
583
650
 
584
651
  An application links a member of its Organization to the Knowledge company
@@ -1146,8 +1213,12 @@ const handle = createResourceHttpHandler({
1146
1213
 
1147
1214
  `auth` is the existing `createFederatedAuth(...)` instance. Its server-only
1148
1215
  `getEntityContext` resolves current CI application, user, organization, scopes,
1149
- roles and group IDs; browser session JSON remains identity and expiry only.
1150
- Missing CI policy context denies entity access. Local Better Auth sessions do
1216
+ the user's effective app roles and App Permissions. CI never sends Group IDs or
1217
+ Platform Permissions. The browser session JSON at `<basePath>/session` carries
1218
+ `expiresAt` and an `identity` with `userId`, `organizationId`, `appId`, `kind`
1219
+ (`member` or `external_app_user`), `roles`, `appPermissions`, `email` and
1220
+ `name`; it never carries the Access token. A user without a role on the app has
1221
+ no session and no entity access. Local Better Auth sessions do
1151
1222
  not establish CI entity authority. Browser framework adapters require this
1152
1223
  verified context instead of boolean authorization. Machine gateway calls retain
1153
1224
  their separate caller policy.
@@ -1343,8 +1414,9 @@ A renewal applies the same admission rule as `/oauth/identity`:
1343
1414
 
1344
1415
  - the external Application is enabled;
1345
1416
  - the CI session is live;
1346
- - the user is not banned and is still admitted to the Organization, which
1347
- today means the Organization membership of the user and its role;
1417
+ - the user is not banned, is a member or an active External App User of the
1418
+ Organization, and holds at least one role on the app other than
1419
+ `anonymous` (Direct Role Assignments replace the roles that Groups give);
1348
1420
  - the scopes of the sign-in are still allowed.
1349
1421
 
1350
1422
  A refusal ends nothing. When the condition is corrected, the same Renewal
@@ -1426,7 +1498,7 @@ const caller = await tokens.verify(bearerToken)
1426
1498
  // userId, organizationId, applicationId, clientId, scopes, sessionId, expiresAt
1427
1499
 
1428
1500
  const identity = await tokens.identity(bearerToken, { signal: request.signal })
1429
- // the same fields, and user { id, email, name }, roles, groupIds
1501
+ // the same fields, and user { id, email, name }, kind, roles, appPermissions
1430
1502
  ```
1431
1503
 
1432
1504
  - `verify` is offline against the issuer key set: ES256 signature, issuer,
@@ -1434,7 +1506,8 @@ const identity = await tokens.identity(bearerToken, { signal: request.signal })
1434
1506
  It does not see a revocation before the token expires.
1435
1507
  - `identity` runs `verify`, then reads CI's current identity for the token and
1436
1508
  checks that it describes the same grant. Use it when the API needs the
1437
- user's roles or groups, or must stop at once when CI revokes the token. It
1509
+ user's app roles or App Permissions, or must stop at once when CI revokes
1510
+ the token or a role. It
1438
1511
  is one CI request for each call, with a 10-second limit; cache the result
1439
1512
  for as long as a role change may take to reach your API. Pass the `signal`
1440
1513
  of the request you serve: when it aborts, the CI request ends and the call
@@ -1562,3 +1635,22 @@ values use `soft_delete` and `hard_delete`, and gateway principal kinds use
1562
1635
  `detached_user` and `service_principal`. Public method and capability property
1563
1636
  names such as `softDelete` remain unchanged. External HTTP, OAuth, JSON Schema
1564
1637
  and provider values retain their required spelling at their adapter boundaries.
1638
+
1639
+ ### CI relation-member reads
1640
+
1641
+ CI-backed resources created by `defineCiEntityResource` accept the common client
1642
+ `listMembers(parentId, field, query, { signal })` operation. The shared Fetch handler
1643
+ mounts `POST /records/:parentId/relation-members/search` below the resource base
1644
+ path and forwards `{ field, ...query }` to the bound CI schema route. Existing
1645
+ verified tenant, organization, resource and user-grant checks still apply;
1646
+ `authorizeWithUserAuth` classifies this operation under `readScope`.
1647
+
1648
+ The page envelope and exact totals remain unchanged. Member records are JSON
1649
+ objects validated independently of the parent schema, as are member query field
1650
+ names; CI resolves and authorizes the actual member schema. Abort signals and
1651
+ correlation IDs pass through the proxy. All four typed read-limit metrics
1652
+ (`work`, `nodes`, `relation_ids`, `bytes`) retain `invalid_request` details
1653
+ `{ reason: "read_limit_exceeded", budget }` and rejected read outcomes. Local
1654
+ `defineEntityResource` repositories have no CI relation resolver and return
1655
+ `capability_not_supported`; the legacy CRUD facade does not expose this route.
1656
+ See the client SDK README for complete IDs, bounded includes and narrowing guidance.