@stigmer/sdk 3.14.0 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/__tests__/ensure-identity-account.test.d.ts +2 -0
  2. package/__tests__/ensure-identity-account.test.d.ts.map +1 -0
  3. package/__tests__/ensure-identity-account.test.js +108 -0
  4. package/__tests__/ensure-identity-account.test.js.map +1 -0
  5. package/__tests__/platform.test.d.ts +2 -0
  6. package/__tests__/platform.test.d.ts.map +1 -0
  7. package/__tests__/platform.test.js +32 -0
  8. package/__tests__/platform.test.js.map +1 -0
  9. package/__tests__/resource-availability.test.d.ts +2 -0
  10. package/__tests__/resource-availability.test.d.ts.map +1 -0
  11. package/__tests__/resource-availability.test.js +73 -0
  12. package/__tests__/resource-availability.test.js.map +1 -0
  13. package/ensure-identity-account.d.ts +72 -0
  14. package/ensure-identity-account.d.ts.map +1 -0
  15. package/ensure-identity-account.js +29 -0
  16. package/ensure-identity-account.js.map +1 -0
  17. package/gen/resource-availability.d.ts +6 -3
  18. package/gen/resource-availability.d.ts.map +1 -1
  19. package/gen/resource-availability.js +34 -11
  20. package/gen/resource-availability.js.map +1 -1
  21. package/index.d.ts +6 -5
  22. package/index.d.ts.map +1 -1
  23. package/index.js +10 -6
  24. package/index.js.map +1 -1
  25. package/package.json +2 -2
  26. package/platform.d.ts +5 -3
  27. package/platform.d.ts.map +1 -1
  28. package/platform.js +6 -3
  29. package/platform.js.map +1 -1
  30. package/resource-availability.d.ts +52 -11
  31. package/resource-availability.d.ts.map +1 -1
  32. package/resource-availability.js +101 -8
  33. package/resource-availability.js.map +1 -1
  34. package/src/__tests__/ensure-identity-account.test.ts +146 -0
  35. package/src/__tests__/platform.test.ts +44 -0
  36. package/src/__tests__/resource-availability.test.ts +108 -0
  37. package/src/ensure-identity-account.ts +87 -0
  38. package/src/gen/resource-availability.ts +34 -11
  39. package/src/index.ts +24 -11
  40. package/src/platform.ts +11 -4
  41. package/src/resource-availability.ts +117 -14
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The first-sign-in flow, once: make sure the authenticated caller has an
3
+ * identity account, creating it if this is the first time they arrive.
4
+ *
5
+ * Every edition serves `IdentityAccount` from one controller (20260911.11),
6
+ * and every surface that greets a signed-in person runs the same two-step
7
+ * flow — the console's `useIdentityAccountGate` (`@stigmer/react`), the
8
+ * CLI's `auth login` and `auth whoami`, and any platform builder's own
9
+ * gate. This module is that flow's one home, so the surfaces cannot drift
10
+ * on the question "what does a missing account mean?".
11
+ *
12
+ * Three facts it is built on:
13
+ *
14
+ * - `whoAmI` answers NOT_FOUND for an authenticated caller who has no
15
+ * account yet (the server's byte-pinned copy: "Identity account not found
16
+ * for the authenticated user"). That code, and ONLY that code, means
17
+ * "provision me". UNAUTHENTICATED, UNAVAILABLE, a transport fault — each
18
+ * is the caller's to handle and is rethrown as the same object.
19
+ * - `provisionMyAccount` is the one creator of a direct account and is
20
+ * idempotent by the caller's subject: two racing first sign-ins resolve
21
+ * to one row. Its failures are also rethrown untouched (the issuer's
22
+ * userinfo being down is UNAVAILABLE, the documented arm).
23
+ * - Whether THIS call created the account is part of the answer. A first
24
+ * sign-in should be visible ("your account was created"), not silent.
25
+ *
26
+ * The client parameter is structural, the `McpServerConnectLane` precedent:
27
+ * the full `Stigmer` client satisfies it, and a test can hand in two
28
+ * functions.
29
+ */
30
+ import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
31
+ import { isNotFound } from "./errors.js";
32
+
33
+ /**
34
+ * The slice of the identity-account client this flow needs. Errors are the
35
+ * SDK client's `StigmerError`s; `isNotFound` is the one classifier read.
36
+ */
37
+ export interface IdentityAccountLane {
38
+ whoAmI(): Promise<IdentityAccount>;
39
+ provisionMyAccount(): Promise<IdentityAccount>;
40
+ }
41
+
42
+ /** Options for {@link ensureMyIdentityAccount}. */
43
+ export interface EnsureMyIdentityAccountOptions {
44
+ /**
45
+ * Called once, before `provisionMyAccount` is issued, when `whoAmI` found
46
+ * no account — the moment a surface shows "Setting up your account…".
47
+ * Not called when the account already exists.
48
+ */
49
+ readonly onProvisioning?: () => void;
50
+ }
51
+
52
+ /** What {@link ensureMyIdentityAccount} learned. */
53
+ export interface EnsuredIdentityAccount {
54
+ /** The caller's account, existing or just created. */
55
+ readonly account: IdentityAccount;
56
+ /** `true` when this call created the account (a first sign-in). */
57
+ readonly created: boolean;
58
+ }
59
+
60
+ /**
61
+ * Resolve the caller's identity account, provisioning it on a first sign-in.
62
+ *
63
+ * `whoAmI` first; on NOT_FOUND — and only NOT_FOUND — `onProvisioning` fires
64
+ * and `provisionMyAccount` runs. Every other error, from either RPC, is
65
+ * rethrown as the same object so the caller's error UX owns it.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const { account, created } = await ensureMyIdentityAccount(stigmer, {
70
+ * onProvisioning: () => console.log("Setting up your account…"),
71
+ * });
72
+ * ```
73
+ */
74
+ export async function ensureMyIdentityAccount(
75
+ client: { readonly identityAccount: IdentityAccountLane },
76
+ options?: EnsureMyIdentityAccountOptions,
77
+ ): Promise<EnsuredIdentityAccount> {
78
+ try {
79
+ const account = await client.identityAccount.whoAmI();
80
+ return { account, created: false };
81
+ } catch (err: unknown) {
82
+ if (!isNotFound(err)) throw err;
83
+ }
84
+ options?.onProvisioning?.();
85
+ const account = await client.identityAccount.provisionMyAccount();
86
+ return { account, created: true };
87
+ }
@@ -1,20 +1,43 @@
1
1
  // Code generated by stigmer-codegen from api_resource_kind.proto kind_meta. DO NOT EDIT.
2
2
 
3
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
3
+ import { ApiResourceKind, ResourceTier } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
4
4
 
5
5
  /**
6
- * Resource kinds whose proto kind_meta.tier is cloud_only.
6
+ * The minimum edition that serves each resource kind (kind_meta.tier).
7
+ *
8
+ * Every kind with kind_meta is present, in enum-number order; a kind
9
+ * absent here has no tier and is a programming error to ask about.
7
10
  *
8
11
  * Source of truth: api_resource_kind.proto — each ApiResourceKind enum
9
12
  * value carries a ResourceTier in its kind_meta options.
10
13
  */
11
- export const CLOUD_ONLY_KINDS: ReadonlySet<ApiResourceKind> = new Set([
12
- ApiResourceKind.api_resource_version,
13
- ApiResourceKind.iam_policy,
14
- ApiResourceKind.identity_account,
15
- ApiResourceKind.invitation,
16
- ApiResourceKind.identity_provider,
17
- ApiResourceKind.oauth_app,
18
- ApiResourceKind.platform_client,
19
- ApiResourceKind.platform,
14
+ export const KIND_TIERS: ReadonlyMap<ApiResourceKind, ResourceTier> = new Map([
15
+ [ApiResourceKind.api_resource_version, ResourceTier.cloud_only],
16
+ [ApiResourceKind.iam_policy, ResourceTier.enterprise],
17
+ [ApiResourceKind.identity_account, ResourceTier.open_source],
18
+ [ApiResourceKind.api_key, ResourceTier.open_source],
19
+ [ApiResourceKind.invitation, ResourceTier.enterprise],
20
+ [ApiResourceKind.identity_provider, ResourceTier.enterprise],
21
+ [ApiResourceKind.oauth_app, ResourceTier.open_source],
22
+ [ApiResourceKind.platform_client, ResourceTier.cloud_only],
23
+ [ApiResourceKind.organization, ResourceTier.open_source],
24
+ [ApiResourceKind.platform, ResourceTier.enterprise],
25
+ [ApiResourceKind.agent, ResourceTier.open_source],
26
+ [ApiResourceKind.agent_execution, ResourceTier.open_source],
27
+ [ApiResourceKind.session, ResourceTier.open_source],
28
+ [ApiResourceKind.skill, ResourceTier.open_source],
29
+ [ApiResourceKind.mcp_server, ResourceTier.open_source],
30
+ [ApiResourceKind.agent_instance, ResourceTier.open_source],
31
+ [ApiResourceKind.agent_share, ResourceTier.open_source],
32
+ [ApiResourceKind.agent_channel, ResourceTier.open_source],
33
+ [ApiResourceKind.channel_app, ResourceTier.open_source],
34
+ [ApiResourceKind.workflow, ResourceTier.open_source],
35
+ [ApiResourceKind.workflow_instance, ResourceTier.open_source],
36
+ [ApiResourceKind.workflow_execution, ResourceTier.open_source],
37
+ [ApiResourceKind.environment, ResourceTier.open_source],
38
+ [ApiResourceKind.execution_context, ResourceTier.open_source],
39
+ [ApiResourceKind.artifact, ResourceTier.open_source],
40
+ [ApiResourceKind.schedule, ResourceTier.open_source],
41
+ [ApiResourceKind.memory, ResourceTier.open_source],
42
+ [ApiResourceKind.project, ResourceTier.open_source],
20
43
  ]);
package/src/index.ts CHANGED
@@ -55,12 +55,23 @@ export {
55
55
  getRpcMetadata,
56
56
  } from "./errors.js";
57
57
 
58
- // Resource availability
58
+ // Edition vocabulary and resource availability
59
59
  export {
60
60
  type DeploymentMode,
61
+ deploymentModeOf,
61
62
  isResourceAvailable,
62
63
  } from "./resource-availability.js";
63
64
 
65
+ // The first-sign-in flow: resolve the caller's identity account, provisioning
66
+ // it on a first sign-in. One home for every surface (console gate, CLI,
67
+ // platform builders' own gates).
68
+ export {
69
+ ensureMyIdentityAccount,
70
+ type EnsuredIdentityAccount,
71
+ type EnsureMyIdentityAccountOptions,
72
+ type IdentityAccountLane,
73
+ } from "./ensure-identity-account.js";
74
+
64
75
  // Authorization config and IAM role utilities
65
76
  export {
66
77
  getGrantableRoles,
@@ -126,10 +137,7 @@ export {
126
137
  } from "./github.js";
127
138
 
128
139
  // Platform client (server info / edition detection)
129
- export {
130
- PlatformClient,
131
- type ServerInfo,
132
- } from "./platform.js";
140
+ export { PlatformClient, type ServerInfo } from "./platform.js";
133
141
 
134
142
  // Manifest engine (kind-agnostic YAML ⇄ proto ⇄ apply)
135
143
  export {
@@ -200,7 +208,11 @@ export {
200
208
  type AgentShareInput,
201
209
  type AgentShareMessagesInput,
202
210
  } from "./gen/agentshare.js";
203
- export { ApiKeyClient, toApiKeyUpdateInput, type ApiKeyInput } from "./gen/apikey.js";
211
+ export {
212
+ ApiKeyClient,
213
+ toApiKeyUpdateInput,
214
+ type ApiKeyInput,
215
+ } from "./gen/apikey.js";
204
216
  export {
205
217
  ChannelAppClient,
206
218
  toChannelAppUpdateInput,
@@ -226,10 +238,7 @@ export {
226
238
  toIdentityAccountUpdateInput,
227
239
  type IdentityAccountInput,
228
240
  } from "./gen/identityaccount.js";
229
- export {
230
- InvitationClient,
231
- type InvitationInput,
232
- } from "./gen/invitation.js";
241
+ export { InvitationClient, type InvitationInput } from "./gen/invitation.js";
233
242
  export {
234
243
  IdentityProviderClient,
235
244
  toIdentityProviderUpdateInput,
@@ -274,7 +283,11 @@ export {
274
283
  toPlatformClientUpdateInput,
275
284
  type PlatformClientInput,
276
285
  } from "./gen/platformclient.js";
277
- export { ProjectClient, toProjectUpdateInput, type ProjectInput } from "./gen/project.js";
286
+ export {
287
+ ProjectClient,
288
+ toProjectUpdateInput,
289
+ type ProjectInput,
290
+ } from "./gen/project.js";
278
291
  export {
279
292
  ScheduleClient,
280
293
  buildScheduleProto,
package/src/platform.ts CHANGED
@@ -3,11 +3,16 @@ import { create } from "@bufbuild/protobuf";
3
3
  import {
4
4
  GetServerInfoInputSchema,
5
5
  PlatformQueryController,
6
+ } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
7
+ import type {
8
+ GetServerInfoOutput,
6
9
  ServerEdition,
7
10
  } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
8
- import type { GetServerInfoOutput } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
9
11
  import { wrapError } from "./gen/errors.js";
10
- import type { DeploymentMode } from "./resource-availability.js";
12
+ import {
13
+ deploymentModeOf,
14
+ type DeploymentMode,
15
+ } from "./resource-availability.js";
11
16
 
12
17
  /** Server identity information returned by {@link PlatformClient.getServerInfo}. */
13
18
  export interface ServerInfo {
@@ -36,8 +41,10 @@ export class PlatformClient {
36
41
  /**
37
42
  * Retrieve the connected server's edition and version.
38
43
  *
39
- * Maps the proto {@link ServerEdition} to a {@link DeploymentMode}:
44
+ * Maps the proto {@link ServerEdition} to a {@link DeploymentMode}
45
+ * through {@link deploymentModeOf}:
40
46
  * - `oss` -> `"local"`
47
+ * - `enterprise` -> `"enterprise"`
41
48
  * - `cloud` -> `"cloud"`
42
49
  * - unspecified/unknown -> `"cloud"` (safe default)
43
50
  */
@@ -47,7 +54,7 @@ export class PlatformClient {
47
54
  create(GetServerInfoInputSchema, {}),
48
55
  );
49
56
  return {
50
- deploymentMode: resp.edition === ServerEdition.oss ? "local" : "cloud",
57
+ deploymentMode: deploymentModeOf(resp.edition),
51
58
  edition: resp.edition,
52
59
  version: resp.version,
53
60
  };
@@ -1,27 +1,130 @@
1
- import type { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
2
- import { CLOUD_ONLY_KINDS } from "./gen/resource-availability.js";
1
+ /**
2
+ * The edition vocabulary of the SDK: which edition a server is, in the
3
+ * SDK's own words, and whether a resource kind is served there.
4
+ *
5
+ * Four facts this module is built on (editions program, DD-001):
6
+ *
7
+ * - A kind's `ResourceTier` names the MINIMUM edition that serves it. The
8
+ * editions are ordered oss < enterprise < cloud because each composes the
9
+ * previous one's extension units, so a tier admits its own edition and
10
+ * every edition above it.
11
+ * - The proto enums' numbers are wire identifiers, not ranks: `enterprise`
12
+ * was added after `cloud` / `cloud_only` and sits at 3 in both. The two
13
+ * rank functions below are the ONLY place a tier meets an edition; nothing
14
+ * compares enum numbers.
15
+ * - `DeploymentMode` is `ServerEdition` in string-literal form, the shape
16
+ * React consumers expect (`colorMode: "light" | "dark"` is the sibling),
17
+ * with `oss` spelled `"local"` for history's sake. `deploymentModeOf` is
18
+ * the ONE converter; it must never grow a second implementation.
19
+ * - An older client must not break against a newer server: an edition value
20
+ * this SDK does not know maps to `"cloud"` (nothing hidden) instead of
21
+ * throwing. The switch is exhaustive at compile time so a new edition is
22
+ * still a compile error here; the runtime fallback is for the wire.
23
+ *
24
+ * `DeploymentMode` answers a TIER question ("is this kind served here?").
25
+ * Sites that use it to ask a FACILITY question ("does this server have a
26
+ * wallet?") compare `=== "cloud"` deliberately and say which facility they
27
+ * mean; see the React SDK's deployment-mode consumers.
28
+ */
29
+ import {
30
+ ResourceTier,
31
+ type ApiResourceKind,
32
+ } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
33
+ import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
34
+ import { KIND_TIERS } from "./gen/resource-availability.js";
35
+
36
+ /**
37
+ * Edition of the Stigmer backend the client is connected to, as reported
38
+ * by {@link PlatformClient.getServerInfo}.
39
+ *
40
+ * - `"local"` — Stigmer, the open-source edition. `open_source`-tier
41
+ * resources are served.
42
+ * - `"enterprise"` — Stigmer Enterprise, self-hosted. `open_source`- and
43
+ * `enterprise`-tier resources are served.
44
+ * - `"cloud"` — Stigmer Cloud. Every resource is served.
45
+ */
46
+ export type DeploymentMode = "local" | "enterprise" | "cloud";
3
47
 
4
48
  /**
5
- * Deployment mode of the Stigmer backend the client is connected to.
49
+ * The one edition-to-mode converter.
6
50
  *
7
- * - `"local"` — Running against the local Go CLI server (OSS).
8
- * Only `open_source`-tier resources are available.
9
- * - `"cloud"` — Running against Stigmer Cloud.
10
- * All resources (including `cloud_only`) are available.
51
+ * `server_edition_unspecified` and any value this SDK does not know map to
52
+ * `"cloud"`: a broken or newer server hides nothing, which is the failure a
53
+ * client can live with. The `never` default keeps the switch exhaustive for
54
+ * the editions this SDK knows.
11
55
  */
12
- export type DeploymentMode = "local" | "cloud";
56
+ export function deploymentModeOf(edition: ServerEdition): DeploymentMode {
57
+ switch (edition) {
58
+ case ServerEdition.oss:
59
+ return "local";
60
+ case ServerEdition.enterprise:
61
+ return "enterprise";
62
+ case ServerEdition.cloud:
63
+ case ServerEdition.server_edition_unspecified:
64
+ return "cloud";
65
+ default: {
66
+ const _exhaustive: never = edition;
67
+ void _exhaustive;
68
+ return "cloud";
69
+ }
70
+ }
71
+ }
13
72
 
14
73
  /**
15
- * Check whether a resource kind is available in the given deployment mode.
74
+ * Check whether a resource kind is served in the given deployment mode.
16
75
  *
17
- * In cloud mode every resource is available. In local mode only
18
- * `open_source`-tier resources (those NOT in {@link CLOUD_ONLY_KINDS})
19
- * are available.
76
+ * A kind is served when the mode's edition ranks at or above the kind's
77
+ * minimum edition. Throws for a kind with no tier (only
78
+ * `api_resource_kind_unknown`): that is a programming error, and answering
79
+ * "available" for it would hide the bug.
20
80
  */
21
81
  export function isResourceAvailable(
22
82
  kind: ApiResourceKind,
23
83
  mode: DeploymentMode,
24
84
  ): boolean {
25
- if (mode === "cloud") return true;
26
- return !CLOUD_ONLY_KINDS.has(kind);
85
+ const tier = KIND_TIERS.get(kind);
86
+ if (tier === undefined) {
87
+ throw new Error(
88
+ `isResourceAvailable: kind ${kind} has no tier — only kinds with kind_meta can be asked about`,
89
+ );
90
+ }
91
+ return editionRank(mode) >= tierRank(tier);
92
+ }
93
+
94
+ /** Position of an edition in the oss < enterprise < cloud order. */
95
+ function editionRank(mode: DeploymentMode): number {
96
+ switch (mode) {
97
+ case "local":
98
+ return 1;
99
+ case "enterprise":
100
+ return 2;
101
+ case "cloud":
102
+ return 3;
103
+ default: {
104
+ const _exhaustive: never = mode;
105
+ throw new Error(`unknown deployment mode: ${String(_exhaustive)}`);
106
+ }
107
+ }
108
+ }
109
+
110
+ /** Position of a tier's minimum edition in the same order. */
111
+ function tierRank(tier: ResourceTier): number {
112
+ switch (tier) {
113
+ case ResourceTier.open_source:
114
+ return 1;
115
+ case ResourceTier.enterprise:
116
+ return 2;
117
+ case ResourceTier.cloud_only:
118
+ return 3;
119
+ case ResourceTier.resource_tier_unspecified:
120
+ // A kind without a declared tier is treated as core: the generator
121
+ // emits every kind_meta tier verbatim, so this arm is reachable only
122
+ // by a proto that forgot the field, and hiding a kind is the worse
123
+ // failure for a client.
124
+ return 1;
125
+ default: {
126
+ const _exhaustive: never = tier;
127
+ throw new Error(`unknown resource tier: ${String(_exhaustive)}`);
128
+ }
129
+ }
27
130
  }