@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.
- package/__tests__/ensure-identity-account.test.d.ts +2 -0
- package/__tests__/ensure-identity-account.test.d.ts.map +1 -0
- package/__tests__/ensure-identity-account.test.js +108 -0
- package/__tests__/ensure-identity-account.test.js.map +1 -0
- package/__tests__/platform.test.d.ts +2 -0
- package/__tests__/platform.test.d.ts.map +1 -0
- package/__tests__/platform.test.js +32 -0
- package/__tests__/platform.test.js.map +1 -0
- package/__tests__/resource-availability.test.d.ts +2 -0
- package/__tests__/resource-availability.test.d.ts.map +1 -0
- package/__tests__/resource-availability.test.js +73 -0
- package/__tests__/resource-availability.test.js.map +1 -0
- package/ensure-identity-account.d.ts +72 -0
- package/ensure-identity-account.d.ts.map +1 -0
- package/ensure-identity-account.js +29 -0
- package/ensure-identity-account.js.map +1 -0
- package/gen/resource-availability.d.ts +6 -3
- package/gen/resource-availability.d.ts.map +1 -1
- package/gen/resource-availability.js +34 -11
- package/gen/resource-availability.js.map +1 -1
- package/index.d.ts +6 -5
- package/index.d.ts.map +1 -1
- package/index.js +10 -6
- package/index.js.map +1 -1
- package/package.json +2 -2
- package/platform.d.ts +5 -3
- package/platform.d.ts.map +1 -1
- package/platform.js +6 -3
- package/platform.js.map +1 -1
- package/resource-availability.d.ts +52 -11
- package/resource-availability.d.ts.map +1 -1
- package/resource-availability.js +101 -8
- package/resource-availability.js.map +1 -1
- package/src/__tests__/ensure-identity-account.test.ts +146 -0
- package/src/__tests__/platform.test.ts +44 -0
- package/src/__tests__/resource-availability.test.ts +108 -0
- package/src/ensure-identity-account.ts +87 -0
- package/src/gen/resource-availability.ts +34 -11
- package/src/index.ts +24 -11
- package/src/platform.ts +11 -4
- 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.
|
|
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.
|
|
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
|
|
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;
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
*
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - `
|
|
8
|
-
*
|
|
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
|
|
50
|
+
export declare function deploymentModeOf(edition: ServerEdition): DeploymentMode;
|
|
11
51
|
/**
|
|
12
|
-
* Check whether a resource kind is
|
|
52
|
+
* Check whether a resource kind is served in the given deployment mode.
|
|
13
53
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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,
|
|
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"}
|
package/resource-availability.js
CHANGED
|
@@ -1,14 +1,107 @@
|
|
|
1
|
-
import { CLOUD_ONLY_KINDS } from "./gen/resource-availability.js";
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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":"
|
|
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
|
+
});
|