@topolo/sdk 0.14.4 → 0.16.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/README.md +74 -0
- package/dist/index.cjs +301 -29
- package/dist/index.d.cts +67 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.js +292 -29
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -86,6 +86,80 @@ Every request sends:
|
|
|
86
86
|
Combined with the credential identity, this makes every platform API call
|
|
87
87
|
traceable to a specific agent invocation.
|
|
88
88
|
|
|
89
|
+
### Actor provenance
|
|
90
|
+
|
|
91
|
+
`ActorEnvelope` is the versioned authority and provenance contract for work
|
|
92
|
+
performed by humans, Topolo-managed agents, external harness installations,
|
|
93
|
+
services, and declared browser automation. It always records the accountable
|
|
94
|
+
subject and actual performer independently and binds them to one organization,
|
|
95
|
+
task/run lineage, channel, assurance level, action, resource, and canonical
|
|
96
|
+
input hash.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import {
|
|
100
|
+
parseActorEnvelope,
|
|
101
|
+
sha256CanonicalValue,
|
|
102
|
+
type ActorEnvelope,
|
|
103
|
+
} from '@topolo/sdk';
|
|
104
|
+
|
|
105
|
+
const actionInput = { designId: 'design_123', decision: 'approve' };
|
|
106
|
+
const actorEnvelope: ActorEnvelope = parseActorEnvelope({
|
|
107
|
+
schemaVersion: 1,
|
|
108
|
+
organizationId: 'org_acme',
|
|
109
|
+
accountableSubject: {
|
|
110
|
+
principalType: 'human',
|
|
111
|
+
principalId: 'usr_ashley',
|
|
112
|
+
displayName: 'Ashley Lane',
|
|
113
|
+
},
|
|
114
|
+
performer: {
|
|
115
|
+
principalType: 'external_agent',
|
|
116
|
+
principalId: 'installation_codex_macbook',
|
|
117
|
+
installationId: 'installation_codex_macbook',
|
|
118
|
+
provider: 'openai',
|
|
119
|
+
model: 'gpt-5',
|
|
120
|
+
},
|
|
121
|
+
delegation: {
|
|
122
|
+
taskId: 'task_123',
|
|
123
|
+
runId: 'run_456',
|
|
124
|
+
lineage: [
|
|
125
|
+
{ principalType: 'human', principalId: 'usr_ashley' },
|
|
126
|
+
{ principalType: 'external_agent', principalId: 'installation_codex_macbook' },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
channel: 'mcp',
|
|
130
|
+
assurance: { level: 'workload_identity', methods: ['oauth_client'] },
|
|
131
|
+
action: {
|
|
132
|
+
requestId: 'req_789',
|
|
133
|
+
actionId: 'app_topolo_design.designs.approve',
|
|
134
|
+
resourceType: 'design',
|
|
135
|
+
resourceId: 'design_123',
|
|
136
|
+
inputHash: await sha256CanonicalValue(actionInput),
|
|
137
|
+
},
|
|
138
|
+
issuedAt: new Date().toISOString(),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await topolo.client.callAction(
|
|
142
|
+
actorEnvelope.action.actionId,
|
|
143
|
+
actionInput,
|
|
144
|
+
{
|
|
145
|
+
actorEnvelope,
|
|
146
|
+
resource: { resourceType: 'design', resourceId: 'design_123' },
|
|
147
|
+
confirm: true,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The parser rejects unknown fields, unsupported principal types, partial
|
|
153
|
+
resource bindings, malformed hashes, and non-human performers without a
|
|
154
|
+
delegation chain. External agents require an installation and provider;
|
|
155
|
+
managed agents require an agent instance; browser automation requires both an
|
|
156
|
+
installation and a browser-session identity. `X-Topolo-Agent` remains useful
|
|
157
|
+
telemetry but is never trusted as provenance or approval authority.
|
|
158
|
+
`callAction` and `readAction` validate the envelope against the exact action,
|
|
159
|
+
input and resource before sending its canonical base64url representation in
|
|
160
|
+
`X-Topolo-Actor-Envelope`; the envelope request ID also becomes the transport
|
|
161
|
+
request ID.
|
|
162
|
+
|
|
89
163
|
## Modules
|
|
90
164
|
|
|
91
165
|
| Surface | Methods |
|
package/dist/index.cjs
CHANGED
|
@@ -21,10 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
ACTION_CATALOG_DIGEST_ALGORITHM: () => ACTION_CATALOG_DIGEST_ALGORITHM,
|
|
24
|
+
ACTOR_ENVELOPE_VERSION: () => ACTOR_ENVELOPE_VERSION,
|
|
25
|
+
ActorEnvelopeValidationError: () => ActorEnvelopeValidationError,
|
|
24
26
|
CrmModule: () => CrmModule,
|
|
25
27
|
DEFAULT_APP_URLS: () => DEFAULT_APP_URLS,
|
|
26
28
|
IdentityModule: () => IdentityModule,
|
|
27
29
|
P2PModule: () => P2PModule,
|
|
30
|
+
TOPOLO_ACTOR_ENVELOPE_HEADER: () => TOPOLO_ACTOR_ENVELOPE_HEADER,
|
|
28
31
|
TOPOLO_ENVIRONMENT_SERVICE_URLS: () => TOPOLO_ENVIRONMENT_SERVICE_URLS,
|
|
29
32
|
TOPOLO_RUNTIME_ENVIRONMENTS: () => TOPOLO_RUNTIME_ENVIRONMENTS,
|
|
30
33
|
TOPOLO_WIDGET_CONTRACT_VERSION: () => TOPOLO_WIDGET_CONTRACT_VERSION,
|
|
@@ -42,10 +45,12 @@ __export(index_exports, {
|
|
|
42
45
|
actionContractCompleteness: () => actionContractCompleteness,
|
|
43
46
|
applicationEntryFromCatalog: () => applicationEntryFromCatalog,
|
|
44
47
|
assertTopoloServiceUrlEnvironment: () => assertTopoloServiceUrlEnvironment,
|
|
48
|
+
canonicalJson: () => canonicalJson,
|
|
45
49
|
canonicalTopoloErrorCode: () => canonicalTopoloErrorCode,
|
|
46
50
|
canonicalTopoloRequestId: () => canonicalTopoloRequestId,
|
|
47
51
|
canonicalTopoloRequired: () => canonicalTopoloRequired,
|
|
48
52
|
canonicalTopoloRetryAfter: () => canonicalTopoloRetryAfter,
|
|
53
|
+
canonicalizeActorEnvelope: () => canonicalizeActorEnvelope,
|
|
49
54
|
compactActionCatalogEntry: () => compactActionCatalogEntry,
|
|
50
55
|
compactActionPlan: () => compactActionPlan,
|
|
51
56
|
compactActionPreparation: () => compactActionPreparation,
|
|
@@ -55,12 +60,14 @@ __export(index_exports, {
|
|
|
55
60
|
createTopoloAgentExecutionContext: () => createTopoloAgentExecutionContext,
|
|
56
61
|
createTopoloWidgetError: () => createTopoloWidgetError,
|
|
57
62
|
createTopoloWidgetResponse: () => createTopoloWidgetResponse,
|
|
63
|
+
decodeActorEnvelopeHeader: () => decodeActorEnvelopeHeader,
|
|
58
64
|
defineTopoloActionsWidget: () => defineTopoloActionsWidget,
|
|
59
65
|
defineTopoloActivityWidget: () => defineTopoloActivityWidget,
|
|
60
66
|
defineTopoloChartWidget: () => defineTopoloChartWidget,
|
|
61
67
|
defineTopoloNotificationsWidget: () => defineTopoloNotificationsWidget,
|
|
62
68
|
defineTopoloStatsWidget: () => defineTopoloStatsWidget,
|
|
63
69
|
digestTopoloActionCatalog: () => digestTopoloActionCatalog,
|
|
70
|
+
encodeActorEnvelopeHeader: () => encodeActorEnvelopeHeader,
|
|
64
71
|
encodeTopoloActionBase64: () => encodeTopoloActionBase64,
|
|
65
72
|
enrichActionContract: () => enrichActionContract,
|
|
66
73
|
extractTopoloServiceErrorCode: () => extractTopoloServiceErrorCode,
|
|
@@ -69,6 +76,7 @@ __export(index_exports, {
|
|
|
69
76
|
friendlyAppId: () => friendlyAppId,
|
|
70
77
|
isTopoloWidgetOutputSchema: () => isTopoloWidgetOutputSchema,
|
|
71
78
|
listApplicationDirectory: () => listApplicationDirectory,
|
|
79
|
+
parseActorEnvelope: () => parseActorEnvelope,
|
|
72
80
|
parseTopoloActionUploadContract: () => parseTopoloActionUploadContract,
|
|
73
81
|
preferExactActionMatches: () => preferExactActionMatches,
|
|
74
82
|
prepareTopoloActionBase64UploadInput: () => prepareTopoloActionBase64UploadInput,
|
|
@@ -77,6 +85,7 @@ __export(index_exports, {
|
|
|
77
85
|
resolveApplicationDirectoryEntry: () => resolveApplicationDirectoryEntry,
|
|
78
86
|
resolveCatalogServiceUrl: () => resolveCatalogServiceUrl,
|
|
79
87
|
resolveServiceUrl: () => resolveServiceUrl,
|
|
88
|
+
sha256CanonicalValue: () => sha256CanonicalValue,
|
|
80
89
|
topoloErrorHint: () => topoloErrorHint,
|
|
81
90
|
topoloServiceOrigins: () => topoloServiceOrigins,
|
|
82
91
|
validateTopoloActionInput: () => validateTopoloActionInput,
|
|
@@ -250,10 +259,10 @@ var TopoloPermissionError = class extends TopoloSdkError {
|
|
|
250
259
|
};
|
|
251
260
|
function extractTopoloServiceErrorCode(body) {
|
|
252
261
|
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
253
|
-
const
|
|
254
|
-
const topLevel = canonicalTopoloErrorCode(
|
|
262
|
+
const record2 = body;
|
|
263
|
+
const topLevel = canonicalTopoloErrorCode(record2.code);
|
|
255
264
|
if (topLevel) return topLevel;
|
|
256
|
-
const nested =
|
|
265
|
+
const nested = record2.error;
|
|
257
266
|
const nestedString = canonicalTopoloErrorCode(nested);
|
|
258
267
|
if (nestedString) return nestedString;
|
|
259
268
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
@@ -268,14 +277,14 @@ function serviceErrorCode(body, fallback) {
|
|
|
268
277
|
}
|
|
269
278
|
function extractTopoloServiceErrorDetails(body) {
|
|
270
279
|
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
271
|
-
const
|
|
272
|
-
let candidate =
|
|
273
|
-
const nested =
|
|
280
|
+
const record2 = body;
|
|
281
|
+
let candidate = record2.details;
|
|
282
|
+
const nested = record2.error;
|
|
274
283
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
275
284
|
const nestedRecord = nested;
|
|
276
285
|
if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
|
|
277
286
|
}
|
|
278
|
-
if (candidate === void 0) candidate =
|
|
287
|
+
if (candidate === void 0) candidate = record2;
|
|
279
288
|
if (Array.isArray(candidate)) {
|
|
280
289
|
const paths = canonicalTopoloValidationPaths(candidate);
|
|
281
290
|
return paths.length ? { paths } : void 0;
|
|
@@ -327,8 +336,8 @@ function canonicalTopoloValidationPaths(issues) {
|
|
|
327
336
|
const paths = [];
|
|
328
337
|
for (const issue of issues) {
|
|
329
338
|
if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
|
|
330
|
-
const
|
|
331
|
-
const path = canonicalTopoloValidationPath(
|
|
339
|
+
const record2 = issue;
|
|
340
|
+
const path = canonicalTopoloValidationPath(record2.field) ?? canonicalTopoloValidationPath(record2.path);
|
|
332
341
|
if (path && !paths.includes(path)) paths.push(path);
|
|
333
342
|
if (paths.length === 32) break;
|
|
334
343
|
}
|
|
@@ -929,6 +938,242 @@ function isMultipleOf(value, divisor) {
|
|
|
929
938
|
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
|
|
930
939
|
}
|
|
931
940
|
|
|
941
|
+
// src/actor-envelope.ts
|
|
942
|
+
var ACTOR_ENVELOPE_VERSION = 1;
|
|
943
|
+
var TOPOLO_ACTOR_ENVELOPE_HEADER = "X-Topolo-Actor-Envelope";
|
|
944
|
+
var PRINCIPAL_TYPES = /* @__PURE__ */ new Set([
|
|
945
|
+
"human",
|
|
946
|
+
"managed_agent",
|
|
947
|
+
"external_agent",
|
|
948
|
+
"service",
|
|
949
|
+
"browser_automation"
|
|
950
|
+
]);
|
|
951
|
+
var ACCOUNTABLE_SUBJECT_TYPES = /* @__PURE__ */ new Set(["human", "organization"]);
|
|
952
|
+
var CHANNELS = /* @__PURE__ */ new Set(["web", "mobile", "cli", "mcp", "api", "workflow", "browser_automation"]);
|
|
953
|
+
var ASSURANCE_LEVELS = /* @__PURE__ */ new Set(["authenticated", "workload_identity", "user_verified"]);
|
|
954
|
+
var HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
955
|
+
var ActorEnvelopeValidationError = class extends Error {
|
|
956
|
+
path;
|
|
957
|
+
constructor(path, message) {
|
|
958
|
+
super(`${path}: ${message}`);
|
|
959
|
+
this.name = "ActorEnvelopeValidationError";
|
|
960
|
+
this.path = path;
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
function parseActorEnvelope(value) {
|
|
964
|
+
const envelope = record(value, "actorEnvelope");
|
|
965
|
+
exactKeys(envelope, [
|
|
966
|
+
"schemaVersion",
|
|
967
|
+
"organizationId",
|
|
968
|
+
"accountableSubject",
|
|
969
|
+
"performer",
|
|
970
|
+
"delegation",
|
|
971
|
+
"channel",
|
|
972
|
+
"assurance",
|
|
973
|
+
"action",
|
|
974
|
+
"issuedAt",
|
|
975
|
+
"expiresAt"
|
|
976
|
+
], "actorEnvelope");
|
|
977
|
+
if (envelope.schemaVersion !== ACTOR_ENVELOPE_VERSION) {
|
|
978
|
+
fail("actorEnvelope.schemaVersion", `must equal ${ACTOR_ENVELOPE_VERSION}`);
|
|
979
|
+
}
|
|
980
|
+
const accountableSubject = parseAccountableSubject(envelope.accountableSubject);
|
|
981
|
+
const performer = parseIdentity(envelope.performer, "actorEnvelope.performer");
|
|
982
|
+
const organizationId = text(envelope.organizationId, "actorEnvelope.organizationId");
|
|
983
|
+
const channel = member(envelope.channel, CHANNELS, "actorEnvelope.channel");
|
|
984
|
+
const issuedAt = timestamp(envelope.issuedAt, "actorEnvelope.issuedAt");
|
|
985
|
+
const expiresAt = envelope.expiresAt === void 0 ? void 0 : timestamp(envelope.expiresAt, "actorEnvelope.expiresAt");
|
|
986
|
+
if (expiresAt && Date.parse(expiresAt) <= Date.parse(issuedAt)) {
|
|
987
|
+
fail("actorEnvelope.expiresAt", "must be later than issuedAt");
|
|
988
|
+
}
|
|
989
|
+
const assuranceValue = record(envelope.assurance, "actorEnvelope.assurance");
|
|
990
|
+
exactKeys(assuranceValue, ["level", "methods"], "actorEnvelope.assurance");
|
|
991
|
+
const assurance = {
|
|
992
|
+
level: member(assuranceValue.level, ASSURANCE_LEVELS, "actorEnvelope.assurance.level"),
|
|
993
|
+
methods: textArray(assuranceValue.methods, "actorEnvelope.assurance.methods")
|
|
994
|
+
};
|
|
995
|
+
const actionValue = record(envelope.action, "actorEnvelope.action");
|
|
996
|
+
exactKeys(actionValue, ["requestId", "actionId", "resourceType", "resourceId", "inputHash"], "actorEnvelope.action");
|
|
997
|
+
const inputHash = text(actionValue.inputHash, "actorEnvelope.action.inputHash");
|
|
998
|
+
if (!HASH_PATTERN.test(inputHash)) fail("actorEnvelope.action.inputHash", "must be a lowercase sha256 digest");
|
|
999
|
+
const action = {
|
|
1000
|
+
requestId: text(actionValue.requestId, "actorEnvelope.action.requestId"),
|
|
1001
|
+
actionId: text(actionValue.actionId, "actorEnvelope.action.actionId"),
|
|
1002
|
+
...actionValue.resourceType === void 0 ? {} : { resourceType: text(actionValue.resourceType, "actorEnvelope.action.resourceType") },
|
|
1003
|
+
...actionValue.resourceId === void 0 ? {} : { resourceId: text(actionValue.resourceId, "actorEnvelope.action.resourceId") },
|
|
1004
|
+
inputHash
|
|
1005
|
+
};
|
|
1006
|
+
if (action.resourceType === void 0 !== (action.resourceId === void 0)) {
|
|
1007
|
+
fail("actorEnvelope.action", "resourceType and resourceId must be provided together");
|
|
1008
|
+
}
|
|
1009
|
+
const delegation = parseDelegation(envelope.delegation);
|
|
1010
|
+
const directHuman = performer.principalType === "human" && performer.principalId === accountableSubject.principalId;
|
|
1011
|
+
if (directHuman && delegation !== null) fail("actorEnvelope.delegation", "must be null for direct human action");
|
|
1012
|
+
if (!directHuman && delegation === null) fail("actorEnvelope.delegation", "is required when performer differs from accountable subject");
|
|
1013
|
+
if (delegation) {
|
|
1014
|
+
const finalHop = delegation.lineage.at(-1);
|
|
1015
|
+
if (!finalHop || finalHop.principalId !== performer.principalId || finalHop.principalType !== performer.principalType) {
|
|
1016
|
+
fail("actorEnvelope.delegation.lineage", "must end with the performer");
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (performer.principalType === "external_agent" && (!performer.installationId || !performer.provider)) {
|
|
1020
|
+
fail("actorEnvelope.performer", "external_agent requires installationId and provider");
|
|
1021
|
+
}
|
|
1022
|
+
if (performer.principalType === "managed_agent" && !performer.agentInstanceId) {
|
|
1023
|
+
fail("actorEnvelope.performer.agentInstanceId", "is required for managed_agent");
|
|
1024
|
+
}
|
|
1025
|
+
if (performer.principalType === "browser_automation" && (!performer.installationId || !performer.browserSessionId)) {
|
|
1026
|
+
fail("actorEnvelope.performer", "browser_automation requires installationId and browserSessionId");
|
|
1027
|
+
}
|
|
1028
|
+
return {
|
|
1029
|
+
schemaVersion: ACTOR_ENVELOPE_VERSION,
|
|
1030
|
+
organizationId,
|
|
1031
|
+
accountableSubject,
|
|
1032
|
+
performer,
|
|
1033
|
+
delegation,
|
|
1034
|
+
channel,
|
|
1035
|
+
assurance,
|
|
1036
|
+
action,
|
|
1037
|
+
issuedAt,
|
|
1038
|
+
...expiresAt ? { expiresAt } : {}
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
function canonicalizeActorEnvelope(value) {
|
|
1042
|
+
return canonicalJson(parseActorEnvelope(value));
|
|
1043
|
+
}
|
|
1044
|
+
function encodeActorEnvelopeHeader(value) {
|
|
1045
|
+
const bytes = new TextEncoder().encode(canonicalizeActorEnvelope(value));
|
|
1046
|
+
const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join("");
|
|
1047
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
1048
|
+
}
|
|
1049
|
+
function decodeActorEnvelopeHeader(value) {
|
|
1050
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
1051
|
+
fail("actorEnvelopeHeader", "must be unpadded base64url");
|
|
1052
|
+
}
|
|
1053
|
+
const padded = value.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
1054
|
+
let bytes;
|
|
1055
|
+
try {
|
|
1056
|
+
bytes = Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
1057
|
+
} catch {
|
|
1058
|
+
fail("actorEnvelopeHeader", "must be valid base64url");
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
return parseActorEnvelope(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)));
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
if (error instanceof ActorEnvelopeValidationError) throw error;
|
|
1064
|
+
fail("actorEnvelopeHeader", "must contain a valid UTF-8 actor envelope");
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
function canonicalJson(value) {
|
|
1068
|
+
if (value === null) return "null";
|
|
1069
|
+
if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
1070
|
+
if (typeof value === "number") {
|
|
1071
|
+
if (!Number.isFinite(value)) throw new TypeError("Canonical JSON accepts finite numbers only.");
|
|
1072
|
+
return JSON.stringify(value);
|
|
1073
|
+
}
|
|
1074
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
1075
|
+
if (typeof value !== "object" || !value || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
|
|
1076
|
+
throw new TypeError("Canonical JSON accepts JSON objects, arrays, and primitives only.");
|
|
1077
|
+
}
|
|
1078
|
+
const entries = Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right));
|
|
1079
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
|
|
1080
|
+
}
|
|
1081
|
+
function parseAccountableSubject(value) {
|
|
1082
|
+
const path = "actorEnvelope.accountableSubject";
|
|
1083
|
+
const subject = record(value, path);
|
|
1084
|
+
exactKeys(subject, ["principalType", "principalId", "displayName"], path);
|
|
1085
|
+
return {
|
|
1086
|
+
principalType: member(subject.principalType, ACCOUNTABLE_SUBJECT_TYPES, `${path}.principalType`),
|
|
1087
|
+
principalId: text(subject.principalId, `${path}.principalId`),
|
|
1088
|
+
...subject.displayName === void 0 ? {} : { displayName: text(subject.displayName, `${path}.displayName`) }
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
async function sha256CanonicalValue(value) {
|
|
1092
|
+
const bytes = new TextEncoder().encode(canonicalJson(value));
|
|
1093
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
1094
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
1095
|
+
}
|
|
1096
|
+
function parseIdentity(value, path) {
|
|
1097
|
+
const identity = record(value, path);
|
|
1098
|
+
exactKeys(identity, [
|
|
1099
|
+
"principalType",
|
|
1100
|
+
"principalId",
|
|
1101
|
+
"displayName",
|
|
1102
|
+
"installationId",
|
|
1103
|
+
"agentInstanceId",
|
|
1104
|
+
"browserSessionId",
|
|
1105
|
+
"provider",
|
|
1106
|
+
"model"
|
|
1107
|
+
], path);
|
|
1108
|
+
return {
|
|
1109
|
+
principalType: member(identity.principalType, PRINCIPAL_TYPES, `${path}.principalType`),
|
|
1110
|
+
principalId: text(identity.principalId, `${path}.principalId`),
|
|
1111
|
+
...optionalTextFields(identity, path, [
|
|
1112
|
+
"displayName",
|
|
1113
|
+
"installationId",
|
|
1114
|
+
"agentInstanceId",
|
|
1115
|
+
"browserSessionId",
|
|
1116
|
+
"provider",
|
|
1117
|
+
"model"
|
|
1118
|
+
])
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function parseDelegation(value) {
|
|
1122
|
+
if (value === null) return null;
|
|
1123
|
+
const delegation = record(value, "actorEnvelope.delegation");
|
|
1124
|
+
exactKeys(delegation, ["taskId", "runId", "lineage"], "actorEnvelope.delegation");
|
|
1125
|
+
if (!Array.isArray(delegation.lineage) || delegation.lineage.length === 0 || delegation.lineage.length > 16) {
|
|
1126
|
+
fail("actorEnvelope.delegation.lineage", "must contain between 1 and 16 hops");
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
taskId: text(delegation.taskId, "actorEnvelope.delegation.taskId"),
|
|
1130
|
+
runId: text(delegation.runId, "actorEnvelope.delegation.runId"),
|
|
1131
|
+
lineage: delegation.lineage.map((value2, index) => {
|
|
1132
|
+
const hop = record(value2, `actorEnvelope.delegation.lineage[${index}]`);
|
|
1133
|
+
exactKeys(hop, ["principalType", "principalId"], `actorEnvelope.delegation.lineage[${index}]`);
|
|
1134
|
+
return {
|
|
1135
|
+
principalType: member(hop.principalType, PRINCIPAL_TYPES, `actorEnvelope.delegation.lineage[${index}].principalType`),
|
|
1136
|
+
principalId: text(hop.principalId, `actorEnvelope.delegation.lineage[${index}].principalId`)
|
|
1137
|
+
};
|
|
1138
|
+
})
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
function record(value, path) {
|
|
1142
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
|
|
1143
|
+
return value;
|
|
1144
|
+
}
|
|
1145
|
+
function exactKeys(value, allowed, path) {
|
|
1146
|
+
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
1147
|
+
if (unknown.length) fail(path, `contains unknown field ${unknown[0]}`);
|
|
1148
|
+
}
|
|
1149
|
+
function text(value, path) {
|
|
1150
|
+
if (typeof value !== "string" || value.trim() !== value || value.length === 0 || value.length > 256) {
|
|
1151
|
+
fail(path, "must be a non-empty trimmed string of at most 256 characters");
|
|
1152
|
+
}
|
|
1153
|
+
return value;
|
|
1154
|
+
}
|
|
1155
|
+
function timestamp(value, path) {
|
|
1156
|
+
const result = text(value, path);
|
|
1157
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(result) || Number.isNaN(Date.parse(result))) {
|
|
1158
|
+
fail(path, "must be an RFC 3339 UTC timestamp");
|
|
1159
|
+
}
|
|
1160
|
+
return result;
|
|
1161
|
+
}
|
|
1162
|
+
function member(value, values, path) {
|
|
1163
|
+
if (typeof value !== "string" || !values.has(value)) fail(path, "contains an unsupported value");
|
|
1164
|
+
return value;
|
|
1165
|
+
}
|
|
1166
|
+
function textArray(value, path) {
|
|
1167
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 16) fail(path, "must contain between 1 and 16 values");
|
|
1168
|
+
return value.map((item, index) => text(item, `${path}[${index}]`));
|
|
1169
|
+
}
|
|
1170
|
+
function optionalTextFields(value, path, keys) {
|
|
1171
|
+
return Object.fromEntries(keys.flatMap((key) => value[key] === void 0 ? [] : [[key, text(value[key], `${path}.${key}`)]]));
|
|
1172
|
+
}
|
|
1173
|
+
function fail(path, message) {
|
|
1174
|
+
throw new ActorEnvelopeValidationError(path, message);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
932
1177
|
// src/action-upload.ts
|
|
933
1178
|
function parseTopoloActionUploadContract(value) {
|
|
934
1179
|
if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
|
|
@@ -1219,11 +1464,15 @@ var TopoloClient = class _TopoloClient {
|
|
|
1219
1464
|
const platformAppId = resolveConfiguredAppId(resolvedService.serviceKey, this.appIds);
|
|
1220
1465
|
if (platformAppId) headers.set("X-App-ID", platformAppId);
|
|
1221
1466
|
applyAuthHeaders(headers, this.credential);
|
|
1222
|
-
const
|
|
1467
|
+
const actorEnvelope = opts.actorEnvelope === void 0 ? void 0 : parseActorEnvelope(opts.actorEnvelope);
|
|
1468
|
+
const requestId = actorEnvelope?.action.requestId ?? generateRequestId();
|
|
1223
1469
|
applyAuditHeaders(headers, this.agent, requestId);
|
|
1224
1470
|
if (opts.headers) {
|
|
1225
1471
|
for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
|
|
1226
1472
|
}
|
|
1473
|
+
if (actorEnvelope) {
|
|
1474
|
+
headers.set(TOPOLO_ACTOR_ENVELOPE_HEADER, encodeActorEnvelopeHeader(actorEnvelope));
|
|
1475
|
+
}
|
|
1227
1476
|
if (opts.resource) {
|
|
1228
1477
|
const resourceType = opts.resource.resourceType.trim();
|
|
1229
1478
|
const resourceId = opts.resource.resourceId.trim();
|
|
@@ -1670,14 +1919,14 @@ var TopoloClient = class _TopoloClient {
|
|
|
1670
1919
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1671
1920
|
throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is unavailable.");
|
|
1672
1921
|
}
|
|
1673
|
-
const
|
|
1674
|
-
if (
|
|
1922
|
+
const record2 = raw;
|
|
1923
|
+
if (record2.algorithm !== "sha256-v1" || typeof record2.digest !== "string" || !/^[a-f0-9]{64}$/.test(record2.digest) || !Number.isSafeInteger(record2.actionCount) || Number(record2.actionCount) < 0) {
|
|
1675
1924
|
throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is invalid.");
|
|
1676
1925
|
}
|
|
1677
1926
|
return {
|
|
1678
1927
|
algorithm: "sha256-v1",
|
|
1679
|
-
digest:
|
|
1680
|
-
actionCount: Number(
|
|
1928
|
+
digest: record2.digest,
|
|
1929
|
+
actionCount: Number(record2.actionCount)
|
|
1681
1930
|
};
|
|
1682
1931
|
}
|
|
1683
1932
|
/** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
|
|
@@ -1877,6 +2126,19 @@ var TopoloClient = class _TopoloClient {
|
|
|
1877
2126
|
);
|
|
1878
2127
|
}
|
|
1879
2128
|
const mapped = mapActionInput(entry, input);
|
|
2129
|
+
if (options.actorEnvelope) {
|
|
2130
|
+
const actorEnvelope = parseActorEnvelope(options.actorEnvelope);
|
|
2131
|
+
if (actorEnvelope.action.actionId !== entry.actionId) {
|
|
2132
|
+
throw new TopoloSdkError("actor_envelope_action_mismatch", "Actor envelope actionId does not match the executed action.");
|
|
2133
|
+
}
|
|
2134
|
+
if (actorEnvelope.action.inputHash !== await sha256CanonicalValue(input)) {
|
|
2135
|
+
throw new TopoloSdkError("actor_envelope_input_mismatch", "Actor envelope inputHash does not match the executed action input.");
|
|
2136
|
+
}
|
|
2137
|
+
const envelopeResource = actorEnvelope.action.resourceType && actorEnvelope.action.resourceId ? { resourceType: actorEnvelope.action.resourceType, resourceId: actorEnvelope.action.resourceId } : void 0;
|
|
2138
|
+
if (envelopeResource?.resourceType !== options.resource?.resourceType || envelopeResource?.resourceId !== options.resource?.resourceId) {
|
|
2139
|
+
throw new TopoloSdkError("actor_envelope_resource_mismatch", "Actor envelope resource does not match the executed action resource.");
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
1880
2142
|
const service = entry.appSlug || entry.appId;
|
|
1881
2143
|
const result = await this.request({
|
|
1882
2144
|
service,
|
|
@@ -1886,6 +2148,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
1886
2148
|
...mapped.query ? { query: mapped.query } : {},
|
|
1887
2149
|
...mapped.body !== void 0 ? { body: mapped.body } : {},
|
|
1888
2150
|
...options.resource ? { resource: options.resource } : {},
|
|
2151
|
+
...options.actorEnvelope ? { actorEnvelope: options.actorEnvelope } : {},
|
|
1889
2152
|
headers: {
|
|
1890
2153
|
[TOPOLO_ACTION_ID_HEADER]: entry.actionId,
|
|
1891
2154
|
[TOPOLO_ACTION_NAME_HEADER]: entry.name
|
|
@@ -2310,10 +2573,10 @@ function extractRequired(body) {
|
|
|
2310
2573
|
if (Array.isArray(maybe.required)) return canonicalTopoloRequired(maybe.required);
|
|
2311
2574
|
for (const nested of [maybe.details, maybe.error]) {
|
|
2312
2575
|
if (!nested || typeof nested !== "object" || Array.isArray(nested)) continue;
|
|
2313
|
-
const
|
|
2314
|
-
if (Array.isArray(
|
|
2315
|
-
if (
|
|
2316
|
-
const required =
|
|
2576
|
+
const record2 = nested;
|
|
2577
|
+
if (Array.isArray(record2.required)) return canonicalTopoloRequired(record2.required);
|
|
2578
|
+
if (record2.details && typeof record2.details === "object" && !Array.isArray(record2.details)) {
|
|
2579
|
+
const required = record2.details.required;
|
|
2317
2580
|
if (Array.isArray(required)) return canonicalTopoloRequired(required);
|
|
2318
2581
|
}
|
|
2319
2582
|
}
|
|
@@ -2390,8 +2653,8 @@ function compactResultValue(value, depth, budget) {
|
|
|
2390
2653
|
return value.length > ACTION_RESULT_MAX_ITEMS ? { count: value.length, items, omittedItems: value.length - ACTION_RESULT_MAX_ITEMS } : items;
|
|
2391
2654
|
}
|
|
2392
2655
|
if (typeof value === "object") {
|
|
2393
|
-
const
|
|
2394
|
-
const allKeys = Object.keys(
|
|
2656
|
+
const record2 = value;
|
|
2657
|
+
const allKeys = Object.keys(record2);
|
|
2395
2658
|
const ranked = allKeys.sort((left, right) => resultKeyPriority(left) - resultKeyPriority(right));
|
|
2396
2659
|
const prioritized = ranked.filter((key) => resultKeyPriority(key) < 2);
|
|
2397
2660
|
const ordinary = ranked.filter((key) => resultKeyPriority(key) === 2);
|
|
@@ -2400,7 +2663,7 @@ function compactResultValue(value, depth, budget) {
|
|
|
2400
2663
|
...prioritized,
|
|
2401
2664
|
...ordinary.slice(0, ordinaryLimit)
|
|
2402
2665
|
].slice(0, ACTION_RESULT_MAX_KEYS);
|
|
2403
|
-
const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(
|
|
2666
|
+
const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(record2[key], depth + 1, budget)]));
|
|
2404
2667
|
if (allKeys.length > keys.length) compact["_omittedKeys"] = allKeys.length - keys.length;
|
|
2405
2668
|
return compact;
|
|
2406
2669
|
}
|
|
@@ -2516,8 +2779,8 @@ function canonicalPermission(value, appId) {
|
|
|
2516
2779
|
function stableJson(value) {
|
|
2517
2780
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
2518
2781
|
if (value && typeof value === "object") {
|
|
2519
|
-
const
|
|
2520
|
-
return `{${Object.keys(
|
|
2782
|
+
const record2 = value;
|
|
2783
|
+
return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record2[key])}`).join(",")}}`;
|
|
2521
2784
|
}
|
|
2522
2785
|
return JSON.stringify(value);
|
|
2523
2786
|
}
|
|
@@ -2658,8 +2921,8 @@ function resourceSchema(resource) {
|
|
|
2658
2921
|
function derivedOutputSchema(action) {
|
|
2659
2922
|
const resource = resourceFor(action);
|
|
2660
2923
|
const operation = operationFor(action);
|
|
2661
|
-
const
|
|
2662
|
-
const records = { type: "array", items:
|
|
2924
|
+
const record2 = resourceSchema(resource);
|
|
2925
|
+
const records = { type: "array", items: record2 };
|
|
2663
2926
|
const provenance = {
|
|
2664
2927
|
"x-topolo-contract-source": "derived-action-metadata",
|
|
2665
2928
|
"x-topolo-contract-precision": "structural"
|
|
@@ -2691,12 +2954,12 @@ function derivedOutputSchema(action) {
|
|
|
2691
2954
|
title: `${action.title || action.name} response`,
|
|
2692
2955
|
description: `Resource response returned by ${action.name}. The service may return the resource directly or in an envelope.`,
|
|
2693
2956
|
oneOf: [
|
|
2694
|
-
|
|
2957
|
+
record2,
|
|
2695
2958
|
{
|
|
2696
2959
|
type: "object",
|
|
2697
2960
|
properties: {
|
|
2698
|
-
data:
|
|
2699
|
-
[resource]:
|
|
2961
|
+
data: record2,
|
|
2962
|
+
[resource]: record2
|
|
2700
2963
|
},
|
|
2701
2964
|
additionalProperties: true
|
|
2702
2965
|
}
|
|
@@ -2711,7 +2974,7 @@ function derivedOutputSchema(action) {
|
|
|
2711
2974
|
properties: {
|
|
2712
2975
|
success: { type: "boolean" },
|
|
2713
2976
|
id: { type: "string" },
|
|
2714
|
-
data: { oneOf: [
|
|
2977
|
+
data: { oneOf: [record2, { type: "object", additionalProperties: true }] },
|
|
2715
2978
|
result: { type: "object", additionalProperties: true },
|
|
2716
2979
|
message: { type: "string" }
|
|
2717
2980
|
},
|
|
@@ -4501,10 +4764,13 @@ function createTopolo(options) {
|
|
|
4501
4764
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4502
4765
|
0 && (module.exports = {
|
|
4503
4766
|
ACTION_CATALOG_DIGEST_ALGORITHM,
|
|
4767
|
+
ACTOR_ENVELOPE_VERSION,
|
|
4768
|
+
ActorEnvelopeValidationError,
|
|
4504
4769
|
CrmModule,
|
|
4505
4770
|
DEFAULT_APP_URLS,
|
|
4506
4771
|
IdentityModule,
|
|
4507
4772
|
P2PModule,
|
|
4773
|
+
TOPOLO_ACTOR_ENVELOPE_HEADER,
|
|
4508
4774
|
TOPOLO_ENVIRONMENT_SERVICE_URLS,
|
|
4509
4775
|
TOPOLO_RUNTIME_ENVIRONMENTS,
|
|
4510
4776
|
TOPOLO_WIDGET_CONTRACT_VERSION,
|
|
@@ -4522,10 +4788,12 @@ function createTopolo(options) {
|
|
|
4522
4788
|
actionContractCompleteness,
|
|
4523
4789
|
applicationEntryFromCatalog,
|
|
4524
4790
|
assertTopoloServiceUrlEnvironment,
|
|
4791
|
+
canonicalJson,
|
|
4525
4792
|
canonicalTopoloErrorCode,
|
|
4526
4793
|
canonicalTopoloRequestId,
|
|
4527
4794
|
canonicalTopoloRequired,
|
|
4528
4795
|
canonicalTopoloRetryAfter,
|
|
4796
|
+
canonicalizeActorEnvelope,
|
|
4529
4797
|
compactActionCatalogEntry,
|
|
4530
4798
|
compactActionPlan,
|
|
4531
4799
|
compactActionPreparation,
|
|
@@ -4535,12 +4803,14 @@ function createTopolo(options) {
|
|
|
4535
4803
|
createTopoloAgentExecutionContext,
|
|
4536
4804
|
createTopoloWidgetError,
|
|
4537
4805
|
createTopoloWidgetResponse,
|
|
4806
|
+
decodeActorEnvelopeHeader,
|
|
4538
4807
|
defineTopoloActionsWidget,
|
|
4539
4808
|
defineTopoloActivityWidget,
|
|
4540
4809
|
defineTopoloChartWidget,
|
|
4541
4810
|
defineTopoloNotificationsWidget,
|
|
4542
4811
|
defineTopoloStatsWidget,
|
|
4543
4812
|
digestTopoloActionCatalog,
|
|
4813
|
+
encodeActorEnvelopeHeader,
|
|
4544
4814
|
encodeTopoloActionBase64,
|
|
4545
4815
|
enrichActionContract,
|
|
4546
4816
|
extractTopoloServiceErrorCode,
|
|
@@ -4549,6 +4819,7 @@ function createTopolo(options) {
|
|
|
4549
4819
|
friendlyAppId,
|
|
4550
4820
|
isTopoloWidgetOutputSchema,
|
|
4551
4821
|
listApplicationDirectory,
|
|
4822
|
+
parseActorEnvelope,
|
|
4552
4823
|
parseTopoloActionUploadContract,
|
|
4553
4824
|
preferExactActionMatches,
|
|
4554
4825
|
prepareTopoloActionBase64UploadInput,
|
|
@@ -4557,6 +4828,7 @@ function createTopolo(options) {
|
|
|
4557
4828
|
resolveApplicationDirectoryEntry,
|
|
4558
4829
|
resolveCatalogServiceUrl,
|
|
4559
4830
|
resolveServiceUrl,
|
|
4831
|
+
sha256CanonicalValue,
|
|
4560
4832
|
topoloErrorHint,
|
|
4561
4833
|
topoloServiceOrigins,
|
|
4562
4834
|
validateTopoloActionInput,
|