@topolo/sdk 0.15.0 → 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 CHANGED
@@ -137,6 +137,16 @@ const actorEnvelope: ActorEnvelope = parseActorEnvelope({
137
137
  },
138
138
  issuedAt: new Date().toISOString(),
139
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
+ );
140
150
  ```
141
151
 
142
152
  The parser rejects unknown fields, unsupported principal types, partial
@@ -145,6 +155,10 @@ delegation chain. External agents require an installation and provider;
145
155
  managed agents require an agent instance; browser automation requires both an
146
156
  installation and a browser-session identity. `X-Topolo-Agent` remains useful
147
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.
148
162
 
149
163
  ## Modules
150
164
 
package/dist/index.cjs CHANGED
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  DEFAULT_APP_URLS: () => DEFAULT_APP_URLS,
28
28
  IdentityModule: () => IdentityModule,
29
29
  P2PModule: () => P2PModule,
30
+ TOPOLO_ACTOR_ENVELOPE_HEADER: () => TOPOLO_ACTOR_ENVELOPE_HEADER,
30
31
  TOPOLO_ENVIRONMENT_SERVICE_URLS: () => TOPOLO_ENVIRONMENT_SERVICE_URLS,
31
32
  TOPOLO_RUNTIME_ENVIRONMENTS: () => TOPOLO_RUNTIME_ENVIRONMENTS,
32
33
  TOPOLO_WIDGET_CONTRACT_VERSION: () => TOPOLO_WIDGET_CONTRACT_VERSION,
@@ -59,12 +60,14 @@ __export(index_exports, {
59
60
  createTopoloAgentExecutionContext: () => createTopoloAgentExecutionContext,
60
61
  createTopoloWidgetError: () => createTopoloWidgetError,
61
62
  createTopoloWidgetResponse: () => createTopoloWidgetResponse,
63
+ decodeActorEnvelopeHeader: () => decodeActorEnvelopeHeader,
62
64
  defineTopoloActionsWidget: () => defineTopoloActionsWidget,
63
65
  defineTopoloActivityWidget: () => defineTopoloActivityWidget,
64
66
  defineTopoloChartWidget: () => defineTopoloChartWidget,
65
67
  defineTopoloNotificationsWidget: () => defineTopoloNotificationsWidget,
66
68
  defineTopoloStatsWidget: () => defineTopoloStatsWidget,
67
69
  digestTopoloActionCatalog: () => digestTopoloActionCatalog,
70
+ encodeActorEnvelopeHeader: () => encodeActorEnvelopeHeader,
68
71
  encodeTopoloActionBase64: () => encodeTopoloActionBase64,
69
72
  enrichActionContract: () => enrichActionContract,
70
73
  extractTopoloServiceErrorCode: () => extractTopoloServiceErrorCode,
@@ -935,6 +938,242 @@ function isMultipleOf(value, divisor) {
935
938
  return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
936
939
  }
937
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
+
938
1177
  // src/action-upload.ts
939
1178
  function parseTopoloActionUploadContract(value) {
940
1179
  if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
@@ -1225,11 +1464,15 @@ var TopoloClient = class _TopoloClient {
1225
1464
  const platformAppId = resolveConfiguredAppId(resolvedService.serviceKey, this.appIds);
1226
1465
  if (platformAppId) headers.set("X-App-ID", platformAppId);
1227
1466
  applyAuthHeaders(headers, this.credential);
1228
- const requestId = generateRequestId();
1467
+ const actorEnvelope = opts.actorEnvelope === void 0 ? void 0 : parseActorEnvelope(opts.actorEnvelope);
1468
+ const requestId = actorEnvelope?.action.requestId ?? generateRequestId();
1229
1469
  applyAuditHeaders(headers, this.agent, requestId);
1230
1470
  if (opts.headers) {
1231
1471
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
1232
1472
  }
1473
+ if (actorEnvelope) {
1474
+ headers.set(TOPOLO_ACTOR_ENVELOPE_HEADER, encodeActorEnvelopeHeader(actorEnvelope));
1475
+ }
1233
1476
  if (opts.resource) {
1234
1477
  const resourceType = opts.resource.resourceType.trim();
1235
1478
  const resourceId = opts.resource.resourceId.trim();
@@ -1883,6 +2126,19 @@ var TopoloClient = class _TopoloClient {
1883
2126
  );
1884
2127
  }
1885
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
+ }
1886
2142
  const service = entry.appSlug || entry.appId;
1887
2143
  const result = await this.request({
1888
2144
  service,
@@ -1892,6 +2148,7 @@ var TopoloClient = class _TopoloClient {
1892
2148
  ...mapped.query ? { query: mapped.query } : {},
1893
2149
  ...mapped.body !== void 0 ? { body: mapped.body } : {},
1894
2150
  ...options.resource ? { resource: options.resource } : {},
2151
+ ...options.actorEnvelope ? { actorEnvelope: options.actorEnvelope } : {},
1895
2152
  headers: {
1896
2153
  [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
1897
2154
  [TOPOLO_ACTION_NAME_HEADER]: entry.name
@@ -3016,218 +3273,6 @@ var IdentityModule = class {
3016
3273
  }
3017
3274
  };
3018
3275
 
3019
- // src/actor-envelope.ts
3020
- var ACTOR_ENVELOPE_VERSION = 1;
3021
- var PRINCIPAL_TYPES = /* @__PURE__ */ new Set([
3022
- "human",
3023
- "managed_agent",
3024
- "external_agent",
3025
- "service",
3026
- "browser_automation"
3027
- ]);
3028
- var ACCOUNTABLE_SUBJECT_TYPES = /* @__PURE__ */ new Set(["human", "organization"]);
3029
- var CHANNELS = /* @__PURE__ */ new Set(["web", "mobile", "cli", "mcp", "api", "workflow", "browser_automation"]);
3030
- var ASSURANCE_LEVELS = /* @__PURE__ */ new Set(["authenticated", "workload_identity", "user_verified"]);
3031
- var HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
3032
- var ActorEnvelopeValidationError = class extends Error {
3033
- path;
3034
- constructor(path, message) {
3035
- super(`${path}: ${message}`);
3036
- this.name = "ActorEnvelopeValidationError";
3037
- this.path = path;
3038
- }
3039
- };
3040
- function parseActorEnvelope(value) {
3041
- const envelope = record(value, "actorEnvelope");
3042
- exactKeys(envelope, [
3043
- "schemaVersion",
3044
- "organizationId",
3045
- "accountableSubject",
3046
- "performer",
3047
- "delegation",
3048
- "channel",
3049
- "assurance",
3050
- "action",
3051
- "issuedAt",
3052
- "expiresAt"
3053
- ], "actorEnvelope");
3054
- if (envelope.schemaVersion !== ACTOR_ENVELOPE_VERSION) {
3055
- fail("actorEnvelope.schemaVersion", `must equal ${ACTOR_ENVELOPE_VERSION}`);
3056
- }
3057
- const accountableSubject = parseAccountableSubject(envelope.accountableSubject);
3058
- const performer = parseIdentity(envelope.performer, "actorEnvelope.performer");
3059
- const organizationId = text(envelope.organizationId, "actorEnvelope.organizationId");
3060
- const channel = member(envelope.channel, CHANNELS, "actorEnvelope.channel");
3061
- const issuedAt = timestamp(envelope.issuedAt, "actorEnvelope.issuedAt");
3062
- const expiresAt = envelope.expiresAt === void 0 ? void 0 : timestamp(envelope.expiresAt, "actorEnvelope.expiresAt");
3063
- if (expiresAt && Date.parse(expiresAt) <= Date.parse(issuedAt)) {
3064
- fail("actorEnvelope.expiresAt", "must be later than issuedAt");
3065
- }
3066
- const assuranceValue = record(envelope.assurance, "actorEnvelope.assurance");
3067
- exactKeys(assuranceValue, ["level", "methods"], "actorEnvelope.assurance");
3068
- const assurance = {
3069
- level: member(assuranceValue.level, ASSURANCE_LEVELS, "actorEnvelope.assurance.level"),
3070
- methods: textArray(assuranceValue.methods, "actorEnvelope.assurance.methods")
3071
- };
3072
- const actionValue = record(envelope.action, "actorEnvelope.action");
3073
- exactKeys(actionValue, ["requestId", "actionId", "resourceType", "resourceId", "inputHash"], "actorEnvelope.action");
3074
- const inputHash = text(actionValue.inputHash, "actorEnvelope.action.inputHash");
3075
- if (!HASH_PATTERN.test(inputHash)) fail("actorEnvelope.action.inputHash", "must be a lowercase sha256 digest");
3076
- const action = {
3077
- requestId: text(actionValue.requestId, "actorEnvelope.action.requestId"),
3078
- actionId: text(actionValue.actionId, "actorEnvelope.action.actionId"),
3079
- ...actionValue.resourceType === void 0 ? {} : { resourceType: text(actionValue.resourceType, "actorEnvelope.action.resourceType") },
3080
- ...actionValue.resourceId === void 0 ? {} : { resourceId: text(actionValue.resourceId, "actorEnvelope.action.resourceId") },
3081
- inputHash
3082
- };
3083
- if (action.resourceType === void 0 !== (action.resourceId === void 0)) {
3084
- fail("actorEnvelope.action", "resourceType and resourceId must be provided together");
3085
- }
3086
- const delegation = parseDelegation(envelope.delegation);
3087
- const directHuman = performer.principalType === "human" && performer.principalId === accountableSubject.principalId;
3088
- if (directHuman && delegation !== null) fail("actorEnvelope.delegation", "must be null for direct human action");
3089
- if (!directHuman && delegation === null) fail("actorEnvelope.delegation", "is required when performer differs from accountable subject");
3090
- if (delegation) {
3091
- const finalHop = delegation.lineage.at(-1);
3092
- if (!finalHop || finalHop.principalId !== performer.principalId || finalHop.principalType !== performer.principalType) {
3093
- fail("actorEnvelope.delegation.lineage", "must end with the performer");
3094
- }
3095
- }
3096
- if (performer.principalType === "external_agent" && (!performer.installationId || !performer.provider)) {
3097
- fail("actorEnvelope.performer", "external_agent requires installationId and provider");
3098
- }
3099
- if (performer.principalType === "managed_agent" && !performer.agentInstanceId) {
3100
- fail("actorEnvelope.performer.agentInstanceId", "is required for managed_agent");
3101
- }
3102
- if (performer.principalType === "browser_automation" && (!performer.installationId || !performer.browserSessionId)) {
3103
- fail("actorEnvelope.performer", "browser_automation requires installationId and browserSessionId");
3104
- }
3105
- return {
3106
- schemaVersion: ACTOR_ENVELOPE_VERSION,
3107
- organizationId,
3108
- accountableSubject,
3109
- performer,
3110
- delegation,
3111
- channel,
3112
- assurance,
3113
- action,
3114
- issuedAt,
3115
- ...expiresAt ? { expiresAt } : {}
3116
- };
3117
- }
3118
- function canonicalizeActorEnvelope(value) {
3119
- return canonicalJson(parseActorEnvelope(value));
3120
- }
3121
- function canonicalJson(value) {
3122
- if (value === null) return "null";
3123
- if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
3124
- if (typeof value === "number") {
3125
- if (!Number.isFinite(value)) throw new TypeError("Canonical JSON accepts finite numbers only.");
3126
- return JSON.stringify(value);
3127
- }
3128
- if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
3129
- if (typeof value !== "object" || !value || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
3130
- throw new TypeError("Canonical JSON accepts JSON objects, arrays, and primitives only.");
3131
- }
3132
- const entries = Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right));
3133
- return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
3134
- }
3135
- function parseAccountableSubject(value) {
3136
- const path = "actorEnvelope.accountableSubject";
3137
- const subject = record(value, path);
3138
- exactKeys(subject, ["principalType", "principalId", "displayName"], path);
3139
- return {
3140
- principalType: member(subject.principalType, ACCOUNTABLE_SUBJECT_TYPES, `${path}.principalType`),
3141
- principalId: text(subject.principalId, `${path}.principalId`),
3142
- ...subject.displayName === void 0 ? {} : { displayName: text(subject.displayName, `${path}.displayName`) }
3143
- };
3144
- }
3145
- async function sha256CanonicalValue(value) {
3146
- const bytes = new TextEncoder().encode(canonicalJson(value));
3147
- const digest = await crypto.subtle.digest("SHA-256", bytes);
3148
- return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
3149
- }
3150
- function parseIdentity(value, path) {
3151
- const identity = record(value, path);
3152
- exactKeys(identity, [
3153
- "principalType",
3154
- "principalId",
3155
- "displayName",
3156
- "installationId",
3157
- "agentInstanceId",
3158
- "browserSessionId",
3159
- "provider",
3160
- "model"
3161
- ], path);
3162
- return {
3163
- principalType: member(identity.principalType, PRINCIPAL_TYPES, `${path}.principalType`),
3164
- principalId: text(identity.principalId, `${path}.principalId`),
3165
- ...optionalTextFields(identity, path, [
3166
- "displayName",
3167
- "installationId",
3168
- "agentInstanceId",
3169
- "browserSessionId",
3170
- "provider",
3171
- "model"
3172
- ])
3173
- };
3174
- }
3175
- function parseDelegation(value) {
3176
- if (value === null) return null;
3177
- const delegation = record(value, "actorEnvelope.delegation");
3178
- exactKeys(delegation, ["taskId", "runId", "lineage"], "actorEnvelope.delegation");
3179
- if (!Array.isArray(delegation.lineage) || delegation.lineage.length === 0 || delegation.lineage.length > 16) {
3180
- fail("actorEnvelope.delegation.lineage", "must contain between 1 and 16 hops");
3181
- }
3182
- return {
3183
- taskId: text(delegation.taskId, "actorEnvelope.delegation.taskId"),
3184
- runId: text(delegation.runId, "actorEnvelope.delegation.runId"),
3185
- lineage: delegation.lineage.map((value2, index) => {
3186
- const hop = record(value2, `actorEnvelope.delegation.lineage[${index}]`);
3187
- exactKeys(hop, ["principalType", "principalId"], `actorEnvelope.delegation.lineage[${index}]`);
3188
- return {
3189
- principalType: member(hop.principalType, PRINCIPAL_TYPES, `actorEnvelope.delegation.lineage[${index}].principalType`),
3190
- principalId: text(hop.principalId, `actorEnvelope.delegation.lineage[${index}].principalId`)
3191
- };
3192
- })
3193
- };
3194
- }
3195
- function record(value, path) {
3196
- if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
3197
- return value;
3198
- }
3199
- function exactKeys(value, allowed, path) {
3200
- const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
3201
- if (unknown.length) fail(path, `contains unknown field ${unknown[0]}`);
3202
- }
3203
- function text(value, path) {
3204
- if (typeof value !== "string" || value.trim() !== value || value.length === 0 || value.length > 256) {
3205
- fail(path, "must be a non-empty trimmed string of at most 256 characters");
3206
- }
3207
- return value;
3208
- }
3209
- function timestamp(value, path) {
3210
- const result = text(value, path);
3211
- if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(result) || Number.isNaN(Date.parse(result))) {
3212
- fail(path, "must be an RFC 3339 UTC timestamp");
3213
- }
3214
- return result;
3215
- }
3216
- function member(value, values, path) {
3217
- if (typeof value !== "string" || !values.has(value)) fail(path, "contains an unsupported value");
3218
- return value;
3219
- }
3220
- function textArray(value, path) {
3221
- if (!Array.isArray(value) || value.length === 0 || value.length > 16) fail(path, "must contain between 1 and 16 values");
3222
- return value.map((item, index) => text(item, `${path}[${index}]`));
3223
- }
3224
- function optionalTextFields(value, path, keys) {
3225
- return Object.fromEntries(keys.flatMap((key) => value[key] === void 0 ? [] : [[key, text(value[key], `${path}.${key}`)]]));
3226
- }
3227
- function fail(path, message) {
3228
- throw new ActorEnvelopeValidationError(path, message);
3229
- }
3230
-
3231
3276
  // src/modules/workspaces.ts
3232
3277
  var ACTIONS = {
3233
3278
  list: "app_topolo_auth.workspaces.list",
@@ -4725,6 +4770,7 @@ function createTopolo(options) {
4725
4770
  DEFAULT_APP_URLS,
4726
4771
  IdentityModule,
4727
4772
  P2PModule,
4773
+ TOPOLO_ACTOR_ENVELOPE_HEADER,
4728
4774
  TOPOLO_ENVIRONMENT_SERVICE_URLS,
4729
4775
  TOPOLO_RUNTIME_ENVIRONMENTS,
4730
4776
  TOPOLO_WIDGET_CONTRACT_VERSION,
@@ -4757,12 +4803,14 @@ function createTopolo(options) {
4757
4803
  createTopoloAgentExecutionContext,
4758
4804
  createTopoloWidgetError,
4759
4805
  createTopoloWidgetResponse,
4806
+ decodeActorEnvelopeHeader,
4760
4807
  defineTopoloActionsWidget,
4761
4808
  defineTopoloActivityWidget,
4762
4809
  defineTopoloChartWidget,
4763
4810
  defineTopoloNotificationsWidget,
4764
4811
  defineTopoloStatsWidget,
4765
4812
  digestTopoloActionCatalog,
4813
+ encodeActorEnvelopeHeader,
4766
4814
  encodeTopoloActionBase64,
4767
4815
  enrichActionContract,
4768
4816
  extractTopoloServiceErrorCode,