@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/sdk",
3
- "version": "3.14.0",
3
+ "version": "3.15.0",
4
4
  "description": "Stigmer TypeScript SDK — typed API client for all Stigmer platform resources",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -45,6 +45,6 @@
45
45
  "peerDependencies": {
46
46
  "@bufbuild/protobuf": "^2.0.0",
47
47
  "@connectrpc/connect-node": "^2.0.0",
48
- "@stigmer/protos": "3.14.0"
48
+ "@stigmer/protos": "3.15.0"
49
49
  }
50
50
  }
package/platform.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type Transport } from "@connectrpc/connect";
2
- import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
3
- import type { DeploymentMode } from "./resource-availability.js";
2
+ import type { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
3
+ import { type DeploymentMode } from "./resource-availability.js";
4
4
  /** Server identity information returned by {@link PlatformClient.getServerInfo}. */
5
5
  export interface ServerInfo {
6
6
  /** Server edition mapped to a {@link DeploymentMode}. */
@@ -23,8 +23,10 @@ export declare class PlatformClient {
23
23
  /**
24
24
  * Retrieve the connected server's edition and version.
25
25
  *
26
- * Maps the proto {@link ServerEdition} to a {@link DeploymentMode}:
26
+ * Maps the proto {@link ServerEdition} to a {@link DeploymentMode}
27
+ * through {@link deploymentModeOf}:
27
28
  * - `oss` -> `"local"`
29
+ * - `enterprise` -> `"enterprise"`
28
30
  * - `cloud` -> `"cloud"`
29
31
  * - unspecified/unknown -> `"cloud"` (safe default)
30
32
  */
package/platform.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhF,OAAO,EAGL,aAAa,EACd,MAAM,uDAAuD,CAAC;AAG/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,oFAAoF;AACpF,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,qCAAqC;IACrC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;gBAEtD,SAAS,EAAE,SAAS;IAIhC;;;;;;;OAOG;IACG,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;CAc3C"}
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAMhF,OAAO,KAAK,EAEV,aAAa,EACd,MAAM,uDAAuD,CAAC;AAE/D,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,4BAA4B,CAAC;AAEpC,oFAAoF;AACpF,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,qCAAqC;IACrC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;gBAEtD,SAAS,EAAE,SAAS;IAIhC;;;;;;;;;OASG;IACG,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;CAc3C"}
package/platform.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { createClient } from "@connectrpc/connect";
2
2
  import { create } from "@bufbuild/protobuf";
3
- import { GetServerInfoInputSchema, PlatformQueryController, ServerEdition, } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
3
+ import { GetServerInfoInputSchema, PlatformQueryController, } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
4
4
  import { wrapError } from "./gen/errors.js";
5
+ import { deploymentModeOf, } from "./resource-availability.js";
5
6
  /**
6
7
  * Client for platform-level queries (server info, capabilities).
7
8
  *
@@ -17,8 +18,10 @@ export class PlatformClient {
17
18
  /**
18
19
  * Retrieve the connected server's edition and version.
19
20
  *
20
- * Maps the proto {@link ServerEdition} to a {@link DeploymentMode}:
21
+ * Maps the proto {@link ServerEdition} to a {@link DeploymentMode}
22
+ * through {@link deploymentModeOf}:
21
23
  * - `oss` -> `"local"`
24
+ * - `enterprise` -> `"enterprise"`
22
25
  * - `cloud` -> `"cloud"`
23
26
  * - unspecified/unknown -> `"cloud"` (safe default)
24
27
  */
@@ -26,7 +29,7 @@ export class PlatformClient {
26
29
  try {
27
30
  const resp = await this.platform.getServerInfo(create(GetServerInfoInputSchema, {}));
28
31
  return {
29
- deploymentMode: resp.edition === ServerEdition.oss ? "local" : "cloud",
32
+ deploymentMode: deploymentModeOf(resp.edition),
30
33
  edition: resp.edition,
31
34
  version: resp.version,
32
35
  };
package/platform.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA+B,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,aAAa,GACd,MAAM,uDAAuD,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAa5C;;;;;;GAMG;AACH,MAAM,OAAO,cAAc;IACR,QAAQ,CAAyC;IAElE,YAAY,SAAoB;QAC9B,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IACnE,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC;YACH,MAAM,IAAI,GAAwB,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CACjE,MAAM,CAAC,wBAAwB,EAAE,EAAE,CAAC,CACrC,CAAC;YACF,OAAO;gBACL,cAAc,EAAE,IAAI,CAAC,OAAO,KAAK,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;gBACtE,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,IAAI,CAAC,OAAO;aACtB,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,SAAS,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA+B,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EACL,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,uDAAuD,CAAC;AAK/D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACL,gBAAgB,GAEjB,MAAM,4BAA4B,CAAC;AAYpC;;;;;;GAMG;AACH,MAAM,OAAO,cAAc;IACR,QAAQ,CAAyC;IAElE,YAAY,SAAoB;QAC9B,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IACnE,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC;YACH,MAAM,IAAI,GAAwB,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CACjE,MAAM,CAAC,wBAAwB,EAAE,EAAE,CAAC,CACrC,CAAC;YACF,OAAO;gBACL,cAAc,EAAE,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC;gBAC9C,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,IAAI,CAAC,OAAO;aACtB,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,SAAS,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;CACF"}
@@ -1,19 +1,60 @@
1
- import type { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
2
1
  /**
3
- * Deployment mode of the Stigmer backend the client is connected to.
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
4
  *
5
- * - `"local"` — Running against the local Go CLI server (OSS).
6
- * Only `open_source`-tier resources are available.
7
- * - `"cloud"` — Running against Stigmer Cloud.
8
- * All resources (including `cloud_only`) are available.
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 { type ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
30
+ import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
31
+ /**
32
+ * Edition of the Stigmer backend the client is connected to, as reported
33
+ * by {@link PlatformClient.getServerInfo}.
34
+ *
35
+ * - `"local"` — Stigmer, the open-source edition. `open_source`-tier
36
+ * resources are served.
37
+ * - `"enterprise"` — Stigmer Enterprise, self-hosted. `open_source`- and
38
+ * `enterprise`-tier resources are served.
39
+ * - `"cloud"` — Stigmer Cloud. Every resource is served.
40
+ */
41
+ export type DeploymentMode = "local" | "enterprise" | "cloud";
42
+ /**
43
+ * The one edition-to-mode converter.
44
+ *
45
+ * `server_edition_unspecified` and any value this SDK does not know map to
46
+ * `"cloud"`: a broken or newer server hides nothing, which is the failure a
47
+ * client can live with. The `never` default keeps the switch exhaustive for
48
+ * the editions this SDK knows.
9
49
  */
10
- export type DeploymentMode = "local" | "cloud";
50
+ export declare function deploymentModeOf(edition: ServerEdition): DeploymentMode;
11
51
  /**
12
- * Check whether a resource kind is available in the given deployment mode.
52
+ * Check whether a resource kind is served in the given deployment mode.
13
53
  *
14
- * In cloud mode every resource is available. In local mode only
15
- * `open_source`-tier resources (those NOT in {@link CLOUD_ONLY_KINDS})
16
- * are available.
54
+ * A kind is served when the mode's edition ranks at or above the kind's
55
+ * minimum edition. Throws for a kind with no tier (only
56
+ * `api_resource_kind_unknown`): that is a programming error, and answering
57
+ * "available" for it would hide the bug.
17
58
  */
18
59
  export declare function isResourceAvailable(kind: ApiResourceKind, mode: DeploymentMode): boolean;
19
60
  //# sourceMappingURL=resource-availability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource-availability.d.ts","sourceRoot":"","sources":["../src/resource-availability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qFAAqF,CAAC;AAG3H;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,OAAO,CAAC;AAE/C;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,cAAc,GACnB,OAAO,CAGT"}
1
+ {"version":3,"file":"resource-availability.d.ts","sourceRoot":"","sources":["../src/resource-availability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,qFAAqF,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAC;AAGtF;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAevE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,cAAc,GACnB,OAAO,CAQT"}
@@ -1,14 +1,107 @@
1
- import { CLOUD_ONLY_KINDS } from "./gen/resource-availability.js";
2
1
  /**
3
- * Check whether a resource kind is available in the given deployment mode.
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
4
  *
5
- * In cloud mode every resource is available. In local mode only
6
- * `open_source`-tier resources (those NOT in {@link CLOUD_ONLY_KINDS})
7
- * are available.
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 { ResourceTier, } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
30
+ import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
31
+ import { KIND_TIERS } from "./gen/resource-availability.js";
32
+ /**
33
+ * The one edition-to-mode converter.
34
+ *
35
+ * `server_edition_unspecified` and any value this SDK does not know map to
36
+ * `"cloud"`: a broken or newer server hides nothing, which is the failure a
37
+ * client can live with. The `never` default keeps the switch exhaustive for
38
+ * the editions this SDK knows.
39
+ */
40
+ export function deploymentModeOf(edition) {
41
+ switch (edition) {
42
+ case ServerEdition.oss:
43
+ return "local";
44
+ case ServerEdition.enterprise:
45
+ return "enterprise";
46
+ case ServerEdition.cloud:
47
+ case ServerEdition.server_edition_unspecified:
48
+ return "cloud";
49
+ default: {
50
+ const _exhaustive = edition;
51
+ void _exhaustive;
52
+ return "cloud";
53
+ }
54
+ }
55
+ }
56
+ /**
57
+ * Check whether a resource kind is served in the given deployment mode.
58
+ *
59
+ * A kind is served when the mode's edition ranks at or above the kind's
60
+ * minimum edition. Throws for a kind with no tier (only
61
+ * `api_resource_kind_unknown`): that is a programming error, and answering
62
+ * "available" for it would hide the bug.
8
63
  */
9
64
  export function isResourceAvailable(kind, mode) {
10
- if (mode === "cloud")
11
- return true;
12
- return !CLOUD_ONLY_KINDS.has(kind);
65
+ const tier = KIND_TIERS.get(kind);
66
+ if (tier === undefined) {
67
+ throw new Error(`isResourceAvailable: kind ${kind} has no tier — only kinds with kind_meta can be asked about`);
68
+ }
69
+ return editionRank(mode) >= tierRank(tier);
70
+ }
71
+ /** Position of an edition in the oss < enterprise < cloud order. */
72
+ function editionRank(mode) {
73
+ switch (mode) {
74
+ case "local":
75
+ return 1;
76
+ case "enterprise":
77
+ return 2;
78
+ case "cloud":
79
+ return 3;
80
+ default: {
81
+ const _exhaustive = mode;
82
+ throw new Error(`unknown deployment mode: ${String(_exhaustive)}`);
83
+ }
84
+ }
85
+ }
86
+ /** Position of a tier's minimum edition in the same order. */
87
+ function tierRank(tier) {
88
+ switch (tier) {
89
+ case ResourceTier.open_source:
90
+ return 1;
91
+ case ResourceTier.enterprise:
92
+ return 2;
93
+ case ResourceTier.cloud_only:
94
+ return 3;
95
+ case ResourceTier.resource_tier_unspecified:
96
+ // A kind without a declared tier is treated as core: the generator
97
+ // emits every kind_meta tier verbatim, so this arm is reachable only
98
+ // by a proto that forgot the field, and hiding a kind is the worse
99
+ // failure for a client.
100
+ return 1;
101
+ default: {
102
+ const _exhaustive = tier;
103
+ throw new Error(`unknown resource tier: ${String(_exhaustive)}`);
104
+ }
105
+ }
13
106
  }
14
107
  //# sourceMappingURL=resource-availability.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource-availability.js","sourceRoot":"","sources":["../src/resource-availability.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAYlE;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAqB,EACrB,IAAoB;IAEpB,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"resource-availability.js","sourceRoot":"","sources":["../src/resource-availability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EACL,YAAY,GAEb,MAAM,qFAAqF,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAc5D;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACrD,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,aAAa,CAAC,GAAG;YACpB,OAAO,OAAO,CAAC;QACjB,KAAK,aAAa,CAAC,UAAU;YAC3B,OAAO,YAAY,CAAC;QACtB,KAAK,aAAa,CAAC,KAAK,CAAC;QACzB,KAAK,aAAa,CAAC,0BAA0B;YAC3C,OAAO,OAAO,CAAC;QACjB,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,OAAO,CAAC;YACnC,KAAK,WAAW,CAAC;YACjB,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAqB,EACrB,IAAoB;IAEpB,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,6BAA6B,IAAI,6DAA6D,CAC/F,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,oEAAoE;AACpE,SAAS,WAAW,CAAC,IAAoB;IACvC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,OAAO;YACV,OAAO,CAAC,CAAC;QACX,KAAK,YAAY;YACf,OAAO,CAAC,CAAC;QACX,KAAK,OAAO;YACV,OAAO,CAAC,CAAC;QACX,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,IAAI,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,SAAS,QAAQ,CAAC,IAAkB;IAClC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,YAAY,CAAC,WAAW;YAC3B,OAAO,CAAC,CAAC;QACX,KAAK,YAAY,CAAC,UAAU;YAC1B,OAAO,CAAC,CAAC;QACX,KAAK,YAAY,CAAC,UAAU;YAC1B,OAAO,CAAC,CAAC;QACX,KAAK,YAAY,CAAC,yBAAyB;YACzC,mEAAmE;YACnE,qEAAqE;YACrE,mEAAmE;YACnE,wBAAwB;YACxB,OAAO,CAAC,CAAC;QACX,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,IAAI,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Pins `ensureMyIdentityAccount` (`ensure-identity-account.ts`; 20260911.11
3
+ * A3): the ONE first-sign-in flow every surface runs — the console's
4
+ * `useIdentityAccountGate` delegates to it, the CLI's `auth whoami` and
5
+ * `auth login` call it — so a platform builder embedding Stigmer gets the
6
+ * same flow as a first-class import.
7
+ *
8
+ * 1. whoAmI answers → the account, `created: false`; nothing else is
9
+ * called;
10
+ * 2. whoAmI is NOT_FOUND → `onProvisioning` fires once (the hook's
11
+ * "provisioning" state), provisionMyAccount runs, the created account
12
+ * comes back with `created: true`;
13
+ * 3. any other whoAmI error propagates untouched (UNAUTHENTICATED,
14
+ * UNAVAILABLE, a network fault) — the caller's error UX owns it;
15
+ * 4. a provisionMyAccount failure propagates untouched (UNAVAILABLE when
16
+ * the issuer's userinfo is down is the documented arm).
17
+ *
18
+ * The doubles reject with `StigmerError`, the vocabulary the real
19
+ * `IdentityAccountClient` speaks (every RPC failure passes through
20
+ * `wrapError`), so `isNotFound` — the SDK's one classifier — is what the
21
+ * helper is proven against (refinement 11, slice 4).
22
+ */
23
+ import { Code } from "@connectrpc/connect";
24
+ import { create } from "@bufbuild/protobuf";
25
+ import {
26
+ IdentityAccountSchema,
27
+ type IdentityAccount,
28
+ } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
29
+ import { describe, expect, it } from "vitest";
30
+
31
+ import {
32
+ ensureMyIdentityAccount,
33
+ type IdentityAccountLane,
34
+ } from "../ensure-identity-account";
35
+ import { StigmerError } from "../gen/errors";
36
+
37
+ interface Calls {
38
+ whoAmI: number;
39
+ provisionMyAccount: number;
40
+ }
41
+
42
+ function clientWith(behaviour: {
43
+ whoAmI: () => Promise<IdentityAccount>;
44
+ provisionMyAccount?: () => Promise<IdentityAccount>;
45
+ }): { client: { identityAccount: IdentityAccountLane }; calls: Calls } {
46
+ const calls: Calls = { whoAmI: 0, provisionMyAccount: 0 };
47
+ const client = {
48
+ identityAccount: {
49
+ whoAmI: () => {
50
+ calls.whoAmI += 1;
51
+ return behaviour.whoAmI();
52
+ },
53
+ provisionMyAccount: () => {
54
+ calls.provisionMyAccount += 1;
55
+ return (
56
+ behaviour.provisionMyAccount ??
57
+ (() => Promise.reject(new Error("must not be called")))
58
+ )();
59
+ },
60
+ },
61
+ };
62
+ return { client, calls };
63
+ }
64
+
65
+ const EXISTING = create(IdentityAccountSchema, {
66
+ metadata: { id: "ida_existing" },
67
+ spec: { idpId: "auth0|existing" },
68
+ });
69
+ const CREATED = create(IdentityAccountSchema, {
70
+ metadata: { id: "ida_created" },
71
+ spec: { idpId: "auth0|created" },
72
+ });
73
+
74
+ const NOT_FOUND = () =>
75
+ new StigmerError(
76
+ "not-found",
77
+ "Identity account not found for the authenticated user",
78
+ Code.NotFound,
79
+ );
80
+
81
+ describe("ensureMyIdentityAccount", () => {
82
+ it("answers the existing account without provisioning", async () => {
83
+ const { client, calls } = clientWith({
84
+ whoAmI: () => Promise.resolve(EXISTING),
85
+ });
86
+ const result = await ensureMyIdentityAccount(client);
87
+ expect(result).toEqual({ account: EXISTING, created: false });
88
+ expect(calls).toEqual({ whoAmI: 1, provisionMyAccount: 0 });
89
+ });
90
+
91
+ it("provisions on NOT_FOUND, telling the caller once that it is provisioning", async () => {
92
+ const { client, calls } = clientWith({
93
+ whoAmI: () => Promise.reject(NOT_FOUND()),
94
+ provisionMyAccount: () => Promise.resolve(CREATED),
95
+ });
96
+ let provisioning = 0;
97
+ const result = await ensureMyIdentityAccount(client, {
98
+ onProvisioning: () => (provisioning += 1),
99
+ });
100
+ expect(result).toEqual({ account: CREATED, created: true });
101
+ expect(provisioning).toBe(1);
102
+ expect(calls).toEqual({ whoAmI: 1, provisionMyAccount: 1 });
103
+ });
104
+
105
+ it("does not fire onProvisioning when the account already exists", async () => {
106
+ const { client } = clientWith({ whoAmI: () => Promise.resolve(EXISTING) });
107
+ let provisioning = 0;
108
+ await ensureMyIdentityAccount(client, {
109
+ onProvisioning: () => (provisioning += 1),
110
+ });
111
+ expect(provisioning).toBe(0);
112
+ });
113
+
114
+ it("propagates any other whoAmI error untouched — only NOT_FOUND means 'provision me'", async () => {
115
+ const unauthenticated = new StigmerError(
116
+ "unauthenticated",
117
+ "authentication token missing",
118
+ Code.Unauthenticated,
119
+ );
120
+ const { client, calls } = clientWith({
121
+ whoAmI: () => Promise.reject(unauthenticated),
122
+ });
123
+ await expect(ensureMyIdentityAccount(client)).rejects.toBe(unauthenticated);
124
+ expect(calls.provisionMyAccount).toBe(0);
125
+
126
+ // A value that is not a StigmerError at all (a lane double that does
127
+ // not wrap, a transport fault) is also "any other error": propagated
128
+ // as the same object, never classified as "provision me".
129
+ const network = new TypeError("Load failed");
130
+ const offline = clientWith({ whoAmI: () => Promise.reject(network) });
131
+ await expect(ensureMyIdentityAccount(offline.client)).rejects.toBe(network);
132
+ });
133
+
134
+ it("propagates a provisioning failure untouched (the issuer's userinfo down is UNAVAILABLE)", async () => {
135
+ const unavailable = new StigmerError(
136
+ "unavailable",
137
+ "Failed to fetch user profile from identity provider: userinfo answered 503",
138
+ Code.Unavailable,
139
+ );
140
+ const { client } = clientWith({
141
+ whoAmI: () => Promise.reject(NOT_FOUND()),
142
+ provisionMyAccount: () => Promise.reject(unavailable),
143
+ });
144
+ await expect(ensureMyIdentityAccount(client)).rejects.toBe(unavailable);
145
+ });
146
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pins PlatformClient.getServerInfo's edition-to-mode mapping: the client
3
+ * reports the server's edition through the one converter
4
+ * (`deploymentModeOf`), so a third edition on the wire reaches consumers
5
+ * as its own mode and never collapses into "cloud" by accident. The raw
6
+ * edition rides beside it for callers that want the wire value.
7
+ */
8
+ import { describe, expect, it } from "vitest";
9
+ import { createRouterTransport } from "@connectrpc/connect";
10
+ import { create } from "@bufbuild/protobuf";
11
+ import {
12
+ GetServerInfoOutputSchema,
13
+ PlatformQueryController,
14
+ ServerEdition,
15
+ } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
16
+
17
+ import { PlatformClient } from "../platform";
18
+
19
+ function clientAnswering(edition: ServerEdition): PlatformClient {
20
+ return new PlatformClient(
21
+ createRouterTransport(({ service }) => {
22
+ service(PlatformQueryController, {
23
+ getServerInfo: () =>
24
+ create(GetServerInfoOutputSchema, { edition, version: "1.2.3" }),
25
+ });
26
+ }),
27
+ );
28
+ }
29
+
30
+ describe("PlatformClient.getServerInfo", () => {
31
+ it.each([
32
+ [ServerEdition.oss, "local"],
33
+ [ServerEdition.enterprise, "enterprise"],
34
+ [ServerEdition.cloud, "cloud"],
35
+ ] as const)(
36
+ "maps edition %d to deploymentMode %s and keeps the raw edition",
37
+ async (edition, mode) => {
38
+ const info = await clientAnswering(edition).getServerInfo();
39
+ expect(info.deploymentMode).toBe(mode);
40
+ expect(info.edition).toBe(edition);
41
+ expect(info.version).toBe("1.2.3");
42
+ },
43
+ );
44
+ });
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Pins the edition vocabulary (`resource-availability.ts`): the one
3
+ * edition-to-mode converter, the rank comparison behind
4
+ * `isResourceAvailable`, and the two properties the DDs assumed and this
5
+ * module has to make true —
6
+ *
7
+ * - ranks, never wire numbers: `enterprise = 3` sits ABOVE `cloud = 2`
8
+ * and `cloud_only = 2` on the wire, so a comparison on enum values
9
+ * would say an Enterprise-tier kind is not served by a Cloud server;
10
+ * - forward compatibility: an edition value this SDK does not know
11
+ * (a newer server) maps to `"cloud"` instead of throwing, so an older
12
+ * console never breaks against a newer server.
13
+ *
14
+ * The kinds named here are chosen one per tier; re-tiering any of them is
15
+ * a contract change and must touch this file (editions program, DD-001).
16
+ */
17
+ import { describe, expect, it } from "vitest";
18
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
19
+ import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
20
+
21
+ import {
22
+ deploymentModeOf,
23
+ isResourceAvailable,
24
+ type DeploymentMode,
25
+ } from "../resource-availability";
26
+
27
+ const MODES: readonly DeploymentMode[] = ["local", "enterprise", "cloud"];
28
+
29
+ describe("deploymentModeOf — the one edition-to-mode converter", () => {
30
+ it("maps each known edition to its mode", () => {
31
+ expect(deploymentModeOf(ServerEdition.oss)).toBe("local");
32
+ expect(deploymentModeOf(ServerEdition.enterprise)).toBe("enterprise");
33
+ expect(deploymentModeOf(ServerEdition.cloud)).toBe("cloud");
34
+ });
35
+
36
+ it("maps unspecified to cloud — the documented safe default (nothing hidden on a broken server)", () => {
37
+ expect(deploymentModeOf(ServerEdition.server_edition_unspecified)).toBe(
38
+ "cloud",
39
+ );
40
+ });
41
+
42
+ it("maps an edition this SDK does not know to cloud instead of throwing — older client, newer server", () => {
43
+ // A future fourth edition arrives off the wire as a number the enum
44
+ // type does not name. The cast is the test's point.
45
+ expect(deploymentModeOf(99 as ServerEdition)).toBe("cloud");
46
+ });
47
+ });
48
+
49
+ describe("isResourceAvailable — a tier is a minimum edition", () => {
50
+ it("open_source-tier kinds are available in every mode", () => {
51
+ for (const mode of MODES) {
52
+ expect(isResourceAvailable(ApiResourceKind.agent, mode), mode).toBe(true);
53
+ }
54
+ });
55
+
56
+ it("enterprise-tier kinds are available in enterprise and cloud, not local", () => {
57
+ expect(isResourceAvailable(ApiResourceKind.iam_policy, "local")).toBe(
58
+ false,
59
+ );
60
+ expect(isResourceAvailable(ApiResourceKind.iam_policy, "enterprise")).toBe(
61
+ true,
62
+ );
63
+ // The inversion case: enterprise = 3 > cloud = 2 on the wire. A
64
+ // wire-number comparison fails exactly here.
65
+ expect(isResourceAvailable(ApiResourceKind.iam_policy, "cloud")).toBe(true);
66
+ });
67
+
68
+ it("cloud_only-tier kinds are available in cloud only", () => {
69
+ expect(
70
+ isResourceAvailable(ApiResourceKind.api_resource_version, "local"),
71
+ ).toBe(false);
72
+ expect(
73
+ isResourceAvailable(ApiResourceKind.api_resource_version, "enterprise"),
74
+ ).toBe(false);
75
+ expect(
76
+ isResourceAvailable(ApiResourceKind.api_resource_version, "cloud"),
77
+ ).toBe(true);
78
+ });
79
+
80
+ it("the kinds this entry re-tiers read as their new tier", () => {
81
+ // oauth_app: served by the OSS server; the cloud_only tier was drift.
82
+ expect(isResourceAvailable(ApiResourceKind.oauth_app, "local")).toBe(true);
83
+ // platform: the operator seat a self-hosting Enterprise company owns.
84
+ expect(isResourceAvailable(ApiResourceKind.platform, "local")).toBe(false);
85
+ expect(isResourceAvailable(ApiResourceKind.platform, "enterprise")).toBe(
86
+ true,
87
+ );
88
+ // identity_account: open_source since the open-source server serves the
89
+ // domain (20260911.11 slice 5); the tier moved in the same change as
90
+ // the serving code, never ahead of it.
91
+ expect(isResourceAvailable(ApiResourceKind.identity_account, "local")).toBe(
92
+ true,
93
+ );
94
+ // agent_share and channel_app: served by OSS; they never moved.
95
+ expect(isResourceAvailable(ApiResourceKind.agent_share, "local")).toBe(
96
+ true,
97
+ );
98
+ expect(isResourceAvailable(ApiResourceKind.channel_app, "local")).toBe(
99
+ true,
100
+ );
101
+ });
102
+
103
+ it("refuses a kind with no tier — a programming error, never silently available", () => {
104
+ expect(() =>
105
+ isResourceAvailable(ApiResourceKind.api_resource_kind_unknown, "cloud"),
106
+ ).toThrow(/no tier/);
107
+ });
108
+ });