@evomap/evolver-adapter-public 2.0.19 → 2.0.24
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/dist/auth/credentialStore.d.ts +4 -1
- package/dist/auth/credentialStore.js +3 -1
- package/dist/auth/legacyShim.d.ts +2 -2
- package/dist/auth/legacyShim.js +2 -2
- package/dist/hubCapability.d.ts +2 -0
- package/dist/hubCapability.js +68 -0
- package/dist/hubFetch.d.ts +3 -3
- package/dist/hubFetch.js +55 -13
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { linkSync } from 'node:fs';
|
|
1
|
+
import { linkSync, renameSync } from 'node:fs';
|
|
2
2
|
import type { hub } from '@evomap/evolver-core';
|
|
3
3
|
export interface CredentialStoreOptions {
|
|
4
4
|
/**
|
|
@@ -15,6 +15,8 @@ export interface CredentialStoreOptions {
|
|
|
15
15
|
windowsParentStateReader?: (path: string) => readonly PathSecurityState[];
|
|
16
16
|
/** Injectable filesystem primitive for deterministic no-clobber publication tests. */
|
|
17
17
|
linkFile?: typeof linkSync;
|
|
18
|
+
/** Injectable filesystem primitive for deterministic atomic replacement tests. */
|
|
19
|
+
renameFile?: typeof renameSync;
|
|
18
20
|
}
|
|
19
21
|
export interface WindowsCredentialAclOps {
|
|
20
22
|
assertTrustedParent(path: string, strictCreate: boolean): void;
|
|
@@ -44,6 +46,7 @@ export declare class CredentialStore {
|
|
|
44
46
|
private readonly darwinAclReader;
|
|
45
47
|
private readonly windowsParentStateReader;
|
|
46
48
|
private readonly linkFile;
|
|
49
|
+
private readonly renameFile;
|
|
47
50
|
private securedDirectoryState;
|
|
48
51
|
private securedCredentialState;
|
|
49
52
|
private readonly securedAncestorStates;
|
|
@@ -19,6 +19,7 @@ export class CredentialStore {
|
|
|
19
19
|
darwinAclReader;
|
|
20
20
|
windowsParentStateReader;
|
|
21
21
|
linkFile;
|
|
22
|
+
renameFile;
|
|
22
23
|
// ctime detects in-place ACL drift while dev/ino detects entry replacement.
|
|
23
24
|
securedDirectoryState = null;
|
|
24
25
|
securedCredentialState = null;
|
|
@@ -33,6 +34,7 @@ export class CredentialStore {
|
|
|
33
34
|
this.darwinAclReader = options.darwinAclReader ?? readDarwinAcl;
|
|
34
35
|
this.windowsParentStateReader = options.windowsParentStateReader ?? parentSecurityStates;
|
|
35
36
|
this.linkFile = options.linkFile ?? linkSync;
|
|
37
|
+
this.renameFile = options.renameFile ?? renameSync;
|
|
36
38
|
}
|
|
37
39
|
load() {
|
|
38
40
|
if (!this.prepareDirectory(false))
|
|
@@ -153,7 +155,7 @@ export class CredentialStore {
|
|
|
153
155
|
this.assertDirectoryIdentity(directory, directoryIdentity);
|
|
154
156
|
if (replaceExisting) {
|
|
155
157
|
this.assertSafeDestination();
|
|
156
|
-
|
|
158
|
+
this.renameFile(temporaryPath, this.path);
|
|
157
159
|
}
|
|
158
160
|
else if (!this.publishIfAbsent(temporaryPath)) {
|
|
159
161
|
const incumbentFd = this.openCredentialFile();
|
|
@@ -13,8 +13,8 @@ export type NodeSecretVersionHandler = (nodeSecretVersion?: number) => void;
|
|
|
13
13
|
*/
|
|
14
14
|
export type NodeSecretDivergenceHandler = () => void;
|
|
15
15
|
/**
|
|
16
|
-
* node_secret
|
|
17
|
-
*
|
|
16
|
+
* LegacyAuthShim exposes node_secret as a transport-neutral credential field. HubFetch promotes it to
|
|
17
|
+
* Authorization: Bearer for GET and strict GEP envelope endpoints before egress.
|
|
18
18
|
*/
|
|
19
19
|
export declare class LegacyAuthShim implements hub.AuthProvider {
|
|
20
20
|
private nodeSecret;
|
package/dist/auth/legacyShim.js
CHANGED
|
@@ -13,8 +13,8 @@ export function parseNodeSecretVersion(value) {
|
|
|
13
13
|
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* node_secret
|
|
17
|
-
*
|
|
16
|
+
* LegacyAuthShim exposes node_secret as a transport-neutral credential field. HubFetch promotes it to
|
|
17
|
+
* Authorization: Bearer for GET and strict GEP envelope endpoints before egress.
|
|
18
18
|
*/
|
|
19
19
|
export class LegacyAuthShim {
|
|
20
20
|
nodeSecret;
|
package/dist/hubCapability.d.ts
CHANGED
|
@@ -194,6 +194,8 @@ export declare class PublicHubCapability implements hub.HubCapability {
|
|
|
194
194
|
createRecipe(request: hub.RecipeCreateRequest): Promise<hub.RecipeReceipt>;
|
|
195
195
|
publishRecipe(recipeId: string, options?: hub.RecipePublishOptions): Promise<hub.RecipeReceipt>;
|
|
196
196
|
getRecipe(recipeId: string): Promise<hub.RecipeFetchReceipt>;
|
|
197
|
+
searchRecipes(request?: hub.RecipeSearchRequest): Promise<hub.RecipeSearchReceipt>;
|
|
198
|
+
listRecipes(request?: hub.RecipeSearchRequest): Promise<hub.RecipeSearchReceipt>;
|
|
197
199
|
expressRecipe(recipeId: string, request?: hub.RecipeExpressRequest): Promise<hub.RecipeExpressionReceipt>;
|
|
198
200
|
task: {
|
|
199
201
|
claim: (taskId: string) => Promise<{
|
package/dist/hubCapability.js
CHANGED
|
@@ -103,6 +103,8 @@ export class PublicHubCapability {
|
|
|
103
103
|
publish: async (recipeId, options) => this.publishRecipe(recipeId, options),
|
|
104
104
|
get: async (recipeId) => this.getRecipe(recipeId),
|
|
105
105
|
express: async (recipeId, request = {}) => this.expressRecipe(recipeId, request),
|
|
106
|
+
search: async (request = {}) => this.searchRecipes(request),
|
|
107
|
+
list: async (request = {}) => this.listRecipes(request),
|
|
106
108
|
};
|
|
107
109
|
constructor(opts) {
|
|
108
110
|
this.opts = opts;
|
|
@@ -633,6 +635,20 @@ export class PublicHubCapability {
|
|
|
633
635
|
...(recipe !== undefined ? { recipe } : {}),
|
|
634
636
|
};
|
|
635
637
|
}
|
|
638
|
+
async searchRecipes(request = {}) {
|
|
639
|
+
if (isHubDryRunEnabled()) {
|
|
640
|
+
return dryRunRecipeSearchReceipt('search_recipe', request);
|
|
641
|
+
}
|
|
642
|
+
const body = await this.http.call('GET', '/a2a/recipe/search', undefined, recipeSearchQuery(request));
|
|
643
|
+
return recipeSearchReceiptFromBody(body);
|
|
644
|
+
}
|
|
645
|
+
async listRecipes(request = {}) {
|
|
646
|
+
if (isHubDryRunEnabled()) {
|
|
647
|
+
return dryRunRecipeSearchReceipt('list_recipe', request);
|
|
648
|
+
}
|
|
649
|
+
const body = await this.http.call('GET', '/a2a/recipe/list', undefined, recipeSearchQuery(request));
|
|
650
|
+
return recipeSearchReceiptFromBody(body);
|
|
651
|
+
}
|
|
636
652
|
async expressRecipe(recipeId, request = {}) {
|
|
637
653
|
if (isHubDryRunEnabled()) {
|
|
638
654
|
return dryRunRecipeReceipt('express_recipe', recipeId, { input_payload: request.inputPayload ?? {} });
|
|
@@ -878,6 +894,58 @@ function recipeOrganismIdFromPayload(payload) {
|
|
|
878
894
|
? stringField(organism, 'id') ?? stringField(organism, 'organism_id') ?? stringField(organism, 'organismId')
|
|
879
895
|
: undefined;
|
|
880
896
|
}
|
|
897
|
+
function recipeSearchQuery(request) {
|
|
898
|
+
return {
|
|
899
|
+
...(request.q ? { q: request.q } : {}),
|
|
900
|
+
...(request.limit !== undefined ? { limit: request.limit } : {}),
|
|
901
|
+
...(request.cursor ? { cursor: request.cursor } : {}),
|
|
902
|
+
...(request.sort ? { sort: request.sort } : {}),
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function recipeListFromRecord(value) {
|
|
906
|
+
for (const key of ['recipes', 'items', 'results']) {
|
|
907
|
+
const found = value[key];
|
|
908
|
+
if (Array.isArray(found))
|
|
909
|
+
return found;
|
|
910
|
+
}
|
|
911
|
+
const nested = asRecord(value['data']);
|
|
912
|
+
if (!nested)
|
|
913
|
+
return undefined;
|
|
914
|
+
for (const key of ['recipes', 'items', 'results']) {
|
|
915
|
+
const found = nested[key];
|
|
916
|
+
if (Array.isArray(found))
|
|
917
|
+
return found;
|
|
918
|
+
}
|
|
919
|
+
return undefined;
|
|
920
|
+
}
|
|
921
|
+
function recipeSearchReceiptFromBody(body) {
|
|
922
|
+
const payload = recipePayload(body);
|
|
923
|
+
const recipes = recipeListFromRecord(payload) ?? recipeListFromRecord(body) ?? [];
|
|
924
|
+
const nextCursor = stringField(payload, 'next_cursor')
|
|
925
|
+
?? stringField(payload, 'nextCursor')
|
|
926
|
+
?? stringField(body, 'next_cursor')
|
|
927
|
+
?? stringField(body, 'nextCursor');
|
|
928
|
+
const hasMore = booleanField(payload, 'has_more')
|
|
929
|
+
?? booleanField(payload, 'hasMore')
|
|
930
|
+
?? booleanField(body, 'has_more')
|
|
931
|
+
?? booleanField(body, 'hasMore');
|
|
932
|
+
return {
|
|
933
|
+
recipes,
|
|
934
|
+
...(nextCursor ? { nextCursor } : {}),
|
|
935
|
+
...(hasMore !== undefined ? { hasMore } : {}),
|
|
936
|
+
raw: body,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function dryRunRecipeSearchReceipt(action, request) {
|
|
940
|
+
return {
|
|
941
|
+
recipes: [],
|
|
942
|
+
raw: {
|
|
943
|
+
dry_run: true,
|
|
944
|
+
would: action,
|
|
945
|
+
...request,
|
|
946
|
+
},
|
|
947
|
+
};
|
|
948
|
+
}
|
|
881
949
|
function recipeReceiptFromBody(body) {
|
|
882
950
|
const payload = recipePayload(body);
|
|
883
951
|
const recipe = asRecord(payload['recipe']);
|
package/dist/hubFetch.d.ts
CHANGED
|
@@ -84,9 +84,9 @@ export interface HubFetchDeps {
|
|
|
84
84
|
deadlineScheduler?: HubDeadlineScheduler;
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
|
-
* 公版 hub HTTP 客户端(M6-6). 每请求经 AuthProvider 取凭证:
|
|
88
|
-
* 走 **Authorization: Bearer <node_secret>**
|
|
89
|
-
* sender_id 是标识非凭证, 留 query/body.
|
|
87
|
+
* 公版 hub HTTP 客户端(M6-6). 每请求经 AuthProvider 取凭证: legacy node_secret 对 GET 与 strict
|
|
88
|
+
* GEP envelope POST 走 **Authorization: Bearer <node_secret>** 头,绝不进入 query 或 envelope body;
|
|
89
|
+
* 其余兼容 REST POST 保留既有 body contract。sender_id 是标识非凭证, 留 query/body.
|
|
90
90
|
* 401/403→AuthError(reauth), 4xx→HubClientError(终态), 5xx→重试.
|
|
91
91
|
* 非 JSON Hub 响应(WAF/HTML/captive portal/gateway text)→HubUnreachableError, 不触发 auth recovery.
|
|
92
92
|
*/
|
package/dist/hubFetch.js
CHANGED
|
@@ -72,6 +72,46 @@ const PROTECTED_REQUEST_HEADERS = new Set([
|
|
|
72
72
|
'x-evomap-signature',
|
|
73
73
|
'x-node-secret',
|
|
74
74
|
]);
|
|
75
|
+
const LEGACY_BEARER_POST_PATHS = new Set([
|
|
76
|
+
'/a2a/hello',
|
|
77
|
+
'/a2a/publish',
|
|
78
|
+
'/a2a/validate',
|
|
79
|
+
'/a2a/fetch',
|
|
80
|
+
'/a2a/events/poll',
|
|
81
|
+
'/a2a/mailbox/outbound',
|
|
82
|
+
]);
|
|
83
|
+
function requestHeaderName(headers, name) {
|
|
84
|
+
const normalized = name.toLowerCase();
|
|
85
|
+
return Object.keys(headers).find((headerName) => headerName.toLowerCase() === normalized);
|
|
86
|
+
}
|
|
87
|
+
function setLegacyBearerFallback(headers, nodeSecret) {
|
|
88
|
+
const existingName = requestHeaderName(headers, 'authorization');
|
|
89
|
+
if (existingName !== undefined && headers[existingName]?.trim())
|
|
90
|
+
return false;
|
|
91
|
+
if (existingName !== undefined)
|
|
92
|
+
delete headers[existingName];
|
|
93
|
+
headers['authorization'] = `Bearer ${nodeSecret}`;
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
function legacyNodeSecret(value) {
|
|
97
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value) ? value : undefined;
|
|
98
|
+
}
|
|
99
|
+
function isGepEnvelope(body) {
|
|
100
|
+
return body?.['protocol'] === 'gep-a2a'
|
|
101
|
+
&& body['protocol_version'] === '1.0.0'
|
|
102
|
+
&& typeof body['message_type'] === 'string'
|
|
103
|
+
&& body['message_type'].trim().length > 0
|
|
104
|
+
&& typeof body['message_id'] === 'string'
|
|
105
|
+
&& body['message_id'].trim().length > 0
|
|
106
|
+
&& typeof body['timestamp'] === 'string'
|
|
107
|
+
&& Number.isFinite(Date.parse(body['timestamp']))
|
|
108
|
+
&& Object.prototype.hasOwnProperty.call(body, 'payload')
|
|
109
|
+
&& body['payload'] !== undefined;
|
|
110
|
+
}
|
|
111
|
+
function usesLegacyBearerForPost(method, path, body) {
|
|
112
|
+
return method.toUpperCase() === 'POST'
|
|
113
|
+
&& (LEGACY_BEARER_POST_PATHS.has(path) || isGepEnvelope(body));
|
|
114
|
+
}
|
|
75
115
|
function mergeRequestHeaders(requestHeaders, signedHeaders) {
|
|
76
116
|
const signedNames = new Set(Object.keys(signedHeaders ?? {}).map((name) => name.toLowerCase()));
|
|
77
117
|
const headers = {};
|
|
@@ -93,9 +133,9 @@ function mergeRequestHeaders(requestHeaders, signedHeaders) {
|
|
|
93
133
|
return { ...headers, ...signedHeaders };
|
|
94
134
|
}
|
|
95
135
|
/**
|
|
96
|
-
* 公版 hub HTTP 客户端(M6-6). 每请求经 AuthProvider 取凭证:
|
|
97
|
-
* 走 **Authorization: Bearer <node_secret>**
|
|
98
|
-
* sender_id 是标识非凭证, 留 query/body.
|
|
136
|
+
* 公版 hub HTTP 客户端(M6-6). 每请求经 AuthProvider 取凭证: legacy node_secret 对 GET 与 strict
|
|
137
|
+
* GEP envelope POST 走 **Authorization: Bearer <node_secret>** 头,绝不进入 query 或 envelope body;
|
|
138
|
+
* 其余兼容 REST POST 保留既有 body contract。sender_id 是标识非凭证, 留 query/body.
|
|
99
139
|
* 401/403→AuthError(reauth), 4xx→HubClientError(终态), 5xx→重试.
|
|
100
140
|
* 非 JSON Hub 响应(WAF/HTML/captive portal/gateway text)→HubUnreachableError, 不触发 auth recovery.
|
|
101
141
|
*/
|
|
@@ -148,26 +188,28 @@ export class HubFetch {
|
|
|
148
188
|
qs.set(k, String(v)); // non-credential GET params (e.g. semantic-search q)
|
|
149
189
|
// #8: credentials must NOT go in the query (leaks to access logs / proxies even over https).
|
|
150
190
|
// node_secret travels via Authorization: Bearer; the hub reads it there, never from the query.
|
|
151
|
-
const nodeSecret = creds['node_secret'];
|
|
152
|
-
if (nodeSecret !== undefined
|
|
153
|
-
headers
|
|
191
|
+
const nodeSecret = legacyNodeSecret(creds['node_secret']);
|
|
192
|
+
if (nodeSecret !== undefined)
|
|
193
|
+
setLegacyBearerFallback(headers, nodeSecret);
|
|
154
194
|
const q = qs.toString();
|
|
155
195
|
if (q)
|
|
156
196
|
url += `?${q}`;
|
|
157
197
|
}
|
|
158
198
|
else {
|
|
159
199
|
const postCreds = { ...creds };
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
200
|
+
const postBody = { ...(bodyObj ?? {}) };
|
|
201
|
+
const nodeSecret = legacyNodeSecret(postCreds['node_secret']);
|
|
202
|
+
if (usesLegacyBearerForPost(method, path, bodyObj)) {
|
|
203
|
+
delete postBody['node_secret'];
|
|
204
|
+
if (nodeSecret !== undefined && setLegacyBearerFallback(headers, nodeSecret)) {
|
|
205
|
+
delete postCreds['node_secret'];
|
|
206
|
+
}
|
|
165
207
|
}
|
|
166
208
|
if (path === '/a2a/mailbox/outbound' && sender) {
|
|
167
209
|
const qs = new URLSearchParams({ sender_id: sender });
|
|
168
210
|
url += `?${qs.toString()}`;
|
|
169
211
|
}
|
|
170
|
-
body = JSON.stringify({ ...(sender ? { sender_id: sender } : {}), ...postCreds, ...
|
|
212
|
+
body = JSON.stringify({ ...(sender ? { sender_id: sender } : {}), ...postCreds, ...postBody });
|
|
171
213
|
}
|
|
172
214
|
let res;
|
|
173
215
|
try {
|
|
@@ -246,7 +288,7 @@ function hubOperationForRequest(path, bodyObj) {
|
|
|
246
288
|
&& payload['search_only'] === true)
|
|
247
289
|
return 'search';
|
|
248
290
|
}
|
|
249
|
-
if (path === '/a2a/assets/semantic-search' || path === '/a2a/directory/search')
|
|
291
|
+
if (path === '/a2a/assets/semantic-search' || path === '/a2a/directory/search' || path === '/a2a/recipe/search' || path === '/a2a/recipe/list')
|
|
250
292
|
return 'search';
|
|
251
293
|
if (path === '/a2a/heartbeat')
|
|
252
294
|
return 'heartbeat';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver-adapter-public",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.24",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@evomap/atp-sdk": "^0.1.0",
|
|
20
|
-
"@evomap/evolver-core": "2.0.
|
|
20
|
+
"@evomap/evolver-core": "2.0.24",
|
|
21
21
|
"undici": "^6.27.0"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|