@topolo/sdk 0.21.2 → 0.21.3

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/index.js CHANGED
@@ -18,60 +18,6 @@ function generateRequestId() {
18
18
  return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
19
19
  }
20
20
 
21
- // src/dpop.ts
22
- var encoder = new TextEncoder();
23
- function encodeBase64Url(value) {
24
- const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
25
- let binary = "";
26
- for (const byte of bytes) binary += String.fromCharCode(byte);
27
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
28
- }
29
- function encodeJson(value) {
30
- return encodeBase64Url(encoder.encode(JSON.stringify(value)));
31
- }
32
- async function sha256Base64Url(value) {
33
- return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
34
- }
35
- function publicJwk(privateJwk) {
36
- const { crv, kty, x, y } = privateJwk;
37
- if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
38
- throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
39
- }
40
- return { crv, kty, x, y };
41
- }
42
- function canonicalTargetUrl(value) {
43
- const url = new URL(value);
44
- return `${url.origin}${url.pathname}`;
45
- }
46
- async function calculateDpopJkt(privateJwk) {
47
- const jwk = publicJwk(privateJwk);
48
- return sha256Base64Url(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }));
49
- }
50
- async function createDpopProof(input) {
51
- const jwk = publicJwk(input.privateJwk);
52
- const key = await crypto.subtle.importKey(
53
- "jwk",
54
- input.privateJwk,
55
- { name: "ECDSA", namedCurve: "P-256" },
56
- false,
57
- ["sign"]
58
- );
59
- const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
60
- const claims = encodeJson({
61
- jti: crypto.randomUUID(),
62
- iat: Math.floor(Date.now() / 1e3),
63
- htm: input.method.toUpperCase(),
64
- htu: canonicalTargetUrl(input.targetUrl),
65
- ath: await sha256Base64Url(input.accessToken)
66
- });
67
- const signature = await crypto.subtle.sign(
68
- { name: "ECDSA", hash: "SHA-256" },
69
- key,
70
- encoder.encode(`${header}.${claims}`)
71
- );
72
- return `${header}.${claims}.${encodeBase64Url(signature)}`;
73
- }
74
-
75
21
  // src/errors.ts
76
22
  var TopoloSdkError = class extends Error {
77
23
  code;
@@ -395,6 +341,139 @@ var TopoloHttpError = class extends TopoloSdkError {
395
341
  }
396
342
  };
397
343
 
344
+ // src/action-secrets.ts
345
+ var ACTION_SECRET_PREFIX = "topolo-secret:";
346
+ function isActionSecretReference(value) {
347
+ return typeof value === "string" && value.startsWith(ACTION_SECRET_PREFIX);
348
+ }
349
+ function isRecord(value) {
350
+ return typeof value === "object" && value !== null && !Array.isArray(value);
351
+ }
352
+ function sensitive(key, value) {
353
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
354
+ if (normalized === "capability") {
355
+ if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
356
+ const text2 = value.trim();
357
+ return Boolean(text2) && !/^[a-z][a-z0-9_-]{0,31}$/.test(text2);
358
+ }
359
+ return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
360
+ }
361
+ function maskActionSecrets(value, secrets) {
362
+ let masked = value;
363
+ for (const [secret, reference] of secrets) {
364
+ const encoded = encodeURIComponent(secret);
365
+ const variants = [.../* @__PURE__ */ new Set([secret, encoded])].map((item) => item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
366
+ masked = masked.replace(new RegExp(variants.join("|"), "g"), (match) => match === secret ? reference : encodeURIComponent(reference));
367
+ }
368
+ return masked;
369
+ }
370
+ async function resolveActionSecrets(value, store, secrets) {
371
+ if (isActionSecretReference(value)) {
372
+ if (!store) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
373
+ const resolved = await store.load(value);
374
+ if (typeof resolved === "string" && resolved) secrets.set(resolved, value);
375
+ return resolved;
376
+ }
377
+ if (Array.isArray(value)) return Promise.all(value.map((item) => resolveActionSecrets(item, store, secrets)));
378
+ if (!isRecord(value)) return value;
379
+ return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await resolveActionSecrets(item, store, secrets)])));
380
+ }
381
+ async function protectActionResult(value, store) {
382
+ const secrets = /* @__PURE__ */ new Map();
383
+ async function protect(item, key = "") {
384
+ if (isActionSecretReference(item)) return item;
385
+ if (sensitive(key, item)) {
386
+ const reference = store ? await store.save(item) : "[REDACTED]";
387
+ if (typeof item === "string" && item) secrets.set(item, reference);
388
+ return reference;
389
+ }
390
+ if (Array.isArray(item)) return Promise.all(item.map((child) => protect(child)));
391
+ if (!isRecord(item)) return item;
392
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([name, child]) => [name, await protect(child, name)])));
393
+ }
394
+ const protectedValue = await protect(value);
395
+ async function protectEmbedded(item) {
396
+ if (typeof item === "string" && !isActionSecretReference(item) && maskActionSecrets(item, secrets) !== item) {
397
+ return store ? store.save(item) : maskActionSecrets(item, secrets);
398
+ }
399
+ if (Array.isArray(item)) return Promise.all(item.map(protectEmbedded));
400
+ if (!isRecord(item)) return item;
401
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([key, child]) => [key, await protectEmbedded(child)])));
402
+ }
403
+ return protectEmbedded(protectedValue);
404
+ }
405
+ function maskActionSecretValue(value, secrets) {
406
+ if (value instanceof Error) return maskActionSecretError(value, secrets);
407
+ if (typeof value === "string") return maskActionSecrets(value, secrets);
408
+ if (Array.isArray(value)) return value.map((item) => maskActionSecretValue(item, secrets));
409
+ if (!isRecord(value)) return value;
410
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, maskActionSecretValue(item, secrets)]));
411
+ }
412
+ function maskActionSecretError(error, secrets) {
413
+ if (!(error instanceof Error)) return maskActionSecretValue(error, secrets);
414
+ const masked = Object.create(Object.getPrototypeOf(error));
415
+ for (const key of Object.getOwnPropertyNames(error)) {
416
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
417
+ if ("value" in descriptor) descriptor.value = maskActionSecretValue(descriptor.value, secrets);
418
+ Object.defineProperty(masked, key, descriptor);
419
+ }
420
+ return masked;
421
+ }
422
+
423
+ // src/dpop.ts
424
+ var encoder = new TextEncoder();
425
+ function encodeBase64Url(value) {
426
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
427
+ let binary = "";
428
+ for (const byte of bytes) binary += String.fromCharCode(byte);
429
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
430
+ }
431
+ function encodeJson(value) {
432
+ return encodeBase64Url(encoder.encode(JSON.stringify(value)));
433
+ }
434
+ async function sha256Base64Url(value) {
435
+ return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
436
+ }
437
+ function publicJwk(privateJwk) {
438
+ const { crv, kty, x, y } = privateJwk;
439
+ if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
440
+ throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
441
+ }
442
+ return { crv, kty, x, y };
443
+ }
444
+ function canonicalTargetUrl(value) {
445
+ const url = new URL(value);
446
+ return `${url.origin}${url.pathname}`;
447
+ }
448
+ async function calculateDpopJkt(privateJwk) {
449
+ const jwk = publicJwk(privateJwk);
450
+ return sha256Base64Url(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }));
451
+ }
452
+ async function createDpopProof(input) {
453
+ const jwk = publicJwk(input.privateJwk);
454
+ const key = await crypto.subtle.importKey(
455
+ "jwk",
456
+ input.privateJwk,
457
+ { name: "ECDSA", namedCurve: "P-256" },
458
+ false,
459
+ ["sign"]
460
+ );
461
+ const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
462
+ const claims = encodeJson({
463
+ jti: crypto.randomUUID(),
464
+ iat: Math.floor(Date.now() / 1e3),
465
+ htm: input.method.toUpperCase(),
466
+ htu: canonicalTargetUrl(input.targetUrl),
467
+ ath: await sha256Base64Url(input.accessToken)
468
+ });
469
+ const signature = await crypto.subtle.sign(
470
+ { name: "ECDSA", hash: "SHA-256" },
471
+ key,
472
+ encoder.encode(`${header}.${claims}`)
473
+ );
474
+ return `${header}.${claims}.${encodeBase64Url(signature)}`;
475
+ }
476
+
398
477
  // src/environment.ts
399
478
  var TOPOLO_RUNTIME_ENVIRONMENTS = ["production", "staging", "development", "local"];
400
479
  var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
@@ -700,9 +779,9 @@ function inspectSchema(schema, path, root, depth) {
700
779
  return { path, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
701
780
  }
702
781
  const children = [];
703
- if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
704
- if (isRecord(schema.$defs)) children.push(...Object.entries(schema.$defs));
705
- if (isRecord(schema.definitions)) children.push(...Object.entries(schema.definitions));
782
+ if (isRecord2(schema.properties)) children.push(...Object.entries(schema.properties));
783
+ if (isRecord2(schema.$defs)) children.push(...Object.entries(schema.$defs));
784
+ if (isRecord2(schema.definitions)) children.push(...Object.entries(schema.definitions));
706
785
  children.push(
707
786
  ["additionalProperties", schema.additionalProperties],
708
787
  ["propertyNames", schema.propertyNames],
@@ -712,7 +791,7 @@ function inspectSchema(schema, path, root, depth) {
712
791
  ...arraySchemaEntries("oneOf", schema.oneOf)
713
792
  );
714
793
  for (const [key, child] of children) {
715
- if (!isRecord(child)) continue;
794
+ if (!isRecord2(child)) continue;
716
795
  const issue = inspectSchema(child, `${path}.${key}`, root, depth + 1);
717
796
  if (issue) return issue;
718
797
  }
@@ -736,7 +815,7 @@ function validateNode(schema, value, path, root, errors, depth) {
736
815
  }
737
816
  if (Array.isArray(schema.allOf)) {
738
817
  for (const candidate of schema.allOf) {
739
- if (isRecord(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
818
+ if (isRecord2(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
740
819
  }
741
820
  }
742
821
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
@@ -766,7 +845,7 @@ function validateNode(schema, value, path, root, errors, depth) {
766
845
  if (typeof value === "string") validateString(schema, value, path, errors);
767
846
  if (typeof value === "number") validateNumber(schema, value, path, errors);
768
847
  if (Array.isArray(value)) validateArray(schema, value, path, root, errors, depth);
769
- if (isRecord(value)) validateObject(schema, value, path, root, errors, depth);
848
+ if (isRecord2(value)) validateObject(schema, value, path, root, errors, depth);
770
849
  }
771
850
  function validateObject(schema, value, path, root, errors, depth) {
772
851
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
@@ -775,25 +854,25 @@ function validateObject(schema, value, path, root, errors, depth) {
775
854
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
776
855
  errors.push({ path, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
777
856
  }
778
- const properties = isRecord(schema.properties) ? schema.properties : {};
857
+ const properties = isRecord2(schema.properties) ? schema.properties : {};
779
858
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
780
859
  for (const key of required) {
781
860
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
782
861
  errors.push({ path: `${path}.${key}`, code: "required", message: "Required property is missing." });
783
862
  }
784
863
  }
785
- if (isRecord(schema.propertyNames)) {
864
+ if (isRecord2(schema.propertyNames)) {
786
865
  for (const key of Object.keys(value)) {
787
866
  validateNode(schema.propertyNames, key, `${path}.${key}`, root, errors, depth + 1);
788
867
  }
789
868
  }
790
869
  for (const [key, child] of Object.entries(value)) {
791
870
  const childSchema = properties[key];
792
- if (isRecord(childSchema)) {
871
+ if (isRecord2(childSchema)) {
793
872
  validateNode(childSchema, child, `${path}.${key}`, root, errors, depth + 1);
794
873
  } else if (schema.additionalProperties === false) {
795
874
  errors.push({ path: `${path}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
796
- } else if (isRecord(schema.additionalProperties)) {
875
+ } else if (isRecord2(schema.additionalProperties)) {
797
876
  validateNode(schema.additionalProperties, child, `${path}.${key}`, root, errors, depth + 1);
798
877
  }
799
878
  }
@@ -809,7 +888,7 @@ function validateArray(schema, value, path, root, errors, depth) {
809
888
  const unique = new Set(value.map((item) => JSON.stringify(item)));
810
889
  if (unique.size !== value.length) errors.push({ path, code: "unique_items", message: "Expected every item to be unique." });
811
890
  }
812
- if (isRecord(schema.items)) {
891
+ if (isRecord2(schema.items)) {
813
892
  value.forEach((item, index) => validateNode(schema.items, item, `${path}[${index}]`, root, errors, depth + 1));
814
893
  }
815
894
  }
@@ -847,7 +926,7 @@ function validateNumber(schema, value, path, errors) {
847
926
  }
848
927
  }
849
928
  function candidateMatches(candidate, value, root, depth) {
850
- if (!isRecord(candidate)) return false;
929
+ if (!isRecord2(candidate)) return false;
851
930
  const issues = [];
852
931
  validateNode(candidate, value, "$", root, issues, depth + 1);
853
932
  return issues.length === 0;
@@ -861,24 +940,24 @@ function resolveLocalReference(root, reference) {
861
940
  current = current[Number(token)];
862
941
  continue;
863
942
  }
864
- if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
943
+ if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
865
944
  current = current[token];
866
945
  }
867
- return isRecord(current) ? current : null;
946
+ return isRecord2(current) ? current : null;
868
947
  }
869
948
  function matchesType(type, value) {
870
949
  if (type === void 0) return true;
871
950
  if (Array.isArray(type)) return type.some((candidate) => matchesType(candidate, value));
872
951
  if (type === "null") return value === null;
873
952
  if (type === "array") return Array.isArray(value);
874
- if (type === "object") return isRecord(value);
953
+ if (type === "object") return isRecord2(value);
875
954
  if (type === "integer") return typeof value === "number" && Number.isInteger(value);
876
955
  return typeof value === type;
877
956
  }
878
957
  function sameJsonValue(left, right) {
879
958
  return JSON.stringify(left) === JSON.stringify(right);
880
959
  }
881
- function isRecord(value) {
960
+ function isRecord2(value) {
882
961
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
883
962
  }
884
963
  function isUrl(value) {
@@ -1135,7 +1214,7 @@ function fail(path, message) {
1135
1214
 
1136
1215
  // src/action-upload.ts
1137
1216
  function parseTopoloActionUploadContract(value) {
1138
- if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
1217
+ if (!isRecord3(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
1139
1218
  throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
1140
1219
  }
1141
1220
  const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
@@ -1214,17 +1293,17 @@ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, op
1214
1293
  };
1215
1294
  }
1216
1295
  function topoloUploadResponseValue(response, path) {
1217
- return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
1296
+ return path.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, response);
1218
1297
  }
1219
1298
  function redactTopoloUrlSessionResult(response, uploadUrlPath) {
1220
1299
  const redacted = cloneUploadValue(response);
1221
1300
  let target = redacted;
1222
1301
  const segments = uploadUrlPath.split(".");
1223
1302
  for (const segment of segments.slice(0, -1)) {
1224
- target = isRecord2(target) ? target[segment] : void 0;
1303
+ target = isRecord3(target) ? target[segment] : void 0;
1225
1304
  }
1226
1305
  const finalSegment = segments.at(-1);
1227
- if (finalSegment && isRecord2(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1306
+ if (finalSegment && isRecord3(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1228
1307
  redactUploadHeaders(redacted);
1229
1308
  return redacted;
1230
1309
  }
@@ -1248,7 +1327,7 @@ function mapUploadInput(contract, contentBase64, input, options) {
1248
1327
  }
1249
1328
  function cloneUploadValue(value) {
1250
1329
  if (Array.isArray(value)) return value.map(cloneUploadValue);
1251
- if (!isRecord2(value)) return value;
1330
+ if (!isRecord3(value)) return value;
1252
1331
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneUploadValue(item)]));
1253
1332
  }
1254
1333
  function redactUploadHeaders(value) {
@@ -1256,9 +1335,9 @@ function redactUploadHeaders(value) {
1256
1335
  value.forEach(redactUploadHeaders);
1257
1336
  return;
1258
1337
  }
1259
- if (!isRecord2(value)) return;
1338
+ if (!isRecord3(value)) return;
1260
1339
  for (const [key, item] of Object.entries(value)) {
1261
- if (key.toLowerCase() === "headers" && isRecord2(item)) {
1340
+ if (key.toLowerCase() === "headers" && isRecord3(item)) {
1262
1341
  for (const header of Object.keys(item)) {
1263
1342
  if (["authorization", "cookie", "set-cookie", "proxy-authorization", "x-api-key"].includes(header.toLowerCase())) {
1264
1343
  item[header] = "[REDACTED]";
@@ -1287,7 +1366,7 @@ function decodedBase64Size(contentBase64) {
1287
1366
  return contentBase64.length / 4 * 3 - padding;
1288
1367
  }
1289
1368
  function parseCompleteContract(value) {
1290
- if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1369
+ if (!isRecord3(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1291
1370
  return {
1292
1371
  actionId: requiredPath(value.actionId, "complete.actionId"),
1293
1372
  inputField: requiredField(value.inputField, "complete.inputField"),
@@ -1325,7 +1404,7 @@ function encodeTopoloActionBase64(bytes) {
1325
1404
  }
1326
1405
  return btoa(binary);
1327
1406
  }
1328
- function isRecord2(value) {
1407
+ function isRecord3(value) {
1329
1408
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1330
1409
  }
1331
1410
 
@@ -1338,6 +1417,8 @@ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
1338
1417
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
1339
1418
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
1340
1419
  var TopoloClient = class _TopoloClient {
1420
+ actionSecretStore;
1421
+ actionSecrets = /* @__PURE__ */ new Map();
1341
1422
  credential;
1342
1423
  agent;
1343
1424
  actorContext;
@@ -1367,6 +1448,7 @@ var TopoloClient = class _TopoloClient {
1367
1448
  _TopoloClient.appIdCache.clear();
1368
1449
  }
1369
1450
  constructor(options) {
1451
+ this.actionSecretStore = options.actionSecretStore;
1370
1452
  if (!options.credential) throw new TopoloAuthError("credential is required");
1371
1453
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
1372
1454
  this.credential = options.credential;
@@ -1388,6 +1470,7 @@ var TopoloClient = class _TopoloClient {
1388
1470
  }
1389
1471
  emit(event) {
1390
1472
  if (!this.debug) return;
1473
+ event = maskActionSecretValue(event, this.actionSecrets);
1391
1474
  try {
1392
1475
  this.debug(event);
1393
1476
  } catch {
@@ -1773,7 +1856,7 @@ var TopoloClient = class _TopoloClient {
1773
1856
  }
1774
1857
  const entries = [];
1775
1858
  for (const item of raw) {
1776
- if (!isRecord3(item)) continue;
1859
+ if (!isRecord4(item)) continue;
1777
1860
  const s = item;
1778
1861
  const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
1779
1862
  if (!appId) continue;
@@ -1848,8 +1931,8 @@ var TopoloClient = class _TopoloClient {
1848
1931
  "app_topolo_auth.service_api_key_resources.list",
1849
1932
  { appId: service.appId }
1850
1933
  );
1851
- const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
1852
- if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
1934
+ const payload = isRecord4(response) && isRecord4(response.data) ? response.data : response;
1935
+ if (!isRecord4(payload) || !Array.isArray(payload.resources)) {
1853
1936
  throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
1854
1937
  }
1855
1938
  return {
@@ -2030,17 +2113,20 @@ var TopoloClient = class _TopoloClient {
2030
2113
  }
2031
2114
  async validateActionInput(action, input = {}) {
2032
2115
  const entry = await this.getAction(action);
2033
- return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input) };
2116
+ const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
2117
+ return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, resolved) };
2034
2118
  }
2035
2119
  async planAction(action, input = {}, options = {}) {
2036
2120
  const entry = await this.getAction(action);
2037
- return this.planResolvedAction(entry, input, options);
2121
+ const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
2122
+ return this.planResolvedAction(entry, resolved, options);
2038
2123
  }
2039
2124
  async prepareAction(action, input, options = {}) {
2040
2125
  const entry = await this.getAction(action);
2041
2126
  const example = (entry.agentContract?.examples ?? []).find((candidate) => Boolean(candidate?.input) && typeof candidate.input === "object" && !Array.isArray(candidate.input) && validateTopoloActionInput(entry.inputSchema, candidate.input).valid) ?? null;
2042
2127
  const selectedInput = input ?? example?.input ?? {};
2043
- const plan = this.planResolvedAction(entry, selectedInput, options);
2128
+ const resolved = await resolveActionSecrets(selectedInput, this.actionSecretStore, this.actionSecrets);
2129
+ const plan = this.planResolvedAction(entry, resolved, options);
2044
2130
  return {
2045
2131
  action: entry,
2046
2132
  example,
@@ -2077,7 +2163,7 @@ var TopoloClient = class _TopoloClient {
2077
2163
  confirmationRequired,
2078
2164
  confirmationProvided: options.confirm === true,
2079
2165
  executable: validation.valid && agentAccess !== "off" && (!confirmationRequired || options.confirm === true),
2080
- request,
2166
+ request: maskActionSecretValue(request, this.actionSecrets),
2081
2167
  verification: stringArray(entry.agentContract?.verify),
2082
2168
  rollbackActionId: pickActionString(entry.agentContract?.rollbackActionId),
2083
2169
  nextActionIds: stringArray(entry.agentContract?.nextActionIds)
@@ -2107,6 +2193,7 @@ var TopoloClient = class _TopoloClient {
2107
2193
  `Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
2108
2194
  );
2109
2195
  }
2196
+ input = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
2110
2197
  const validation = validateTopoloActionInput(entry.inputSchema, input);
2111
2198
  if (!validation.valid) {
2112
2199
  throw new TopoloSdkError(
@@ -2137,22 +2224,31 @@ var TopoloClient = class _TopoloClient {
2137
2224
  }
2138
2225
  }
2139
2226
  const service = entry.appSlug || entry.appId;
2140
- const result = await this.request({
2141
- service,
2142
- ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
2143
- method: entry.method,
2144
- path: mapped.path,
2145
- ...mapped.query ? { query: mapped.query } : {},
2146
- ...mapped.body !== void 0 ? { body: mapped.body } : {},
2147
- ...options.resource ? { resource: options.resource } : {},
2148
- ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
2149
- headers: {
2150
- [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
2151
- [TOPOLO_ACTION_NAME_HEADER]: entry.name
2152
- },
2153
- confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
2154
- });
2155
- return preserveSensitiveResult ? result : redactActionResult(result);
2227
+ let result;
2228
+ try {
2229
+ result = await this.request({
2230
+ service,
2231
+ ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
2232
+ method: entry.method,
2233
+ path: mapped.path,
2234
+ ...mapped.query ? { query: mapped.query } : {},
2235
+ ...mapped.body !== void 0 ? { body: mapped.body } : {},
2236
+ ...options.resource ? { resource: options.resource } : {},
2237
+ ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
2238
+ headers: {
2239
+ [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
2240
+ [TOPOLO_ACTION_NAME_HEADER]: entry.name
2241
+ },
2242
+ confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
2243
+ });
2244
+ } catch (error) {
2245
+ throw maskActionSecretError(error, this.actionSecrets);
2246
+ }
2247
+ return preserveSensitiveResult ? result : await protectActionResult(result, this.actionSecretStore);
2248
+ }
2249
+ async releaseActionSecret(reference) {
2250
+ if (!this.actionSecretStore) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
2251
+ await this.actionSecretStore.delete(reference);
2156
2252
  }
2157
2253
  async buildVerifiedAgentActorEnvelope(actionId, input, resource) {
2158
2254
  if (!this.actorContext) return void 0;
@@ -2299,7 +2395,7 @@ function resolveConfiguredAppId(slug, appIds) {
2299
2395
  return null;
2300
2396
  }
2301
2397
  function normalizeApiKeyResourceMetadata(input) {
2302
- if (!isRecord3(input)) return null;
2398
+ if (!isRecord4(input)) return null;
2303
2399
  const resourceType = readString(input.resourceType ?? input.resource_type);
2304
2400
  const label = readString(input.label);
2305
2401
  const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
@@ -2333,7 +2429,7 @@ function normalizeApiKeyResourceMetadataList(input) {
2333
2429
  return resources;
2334
2430
  }
2335
2431
  function normalizeBindableResource(input) {
2336
- if (!isRecord3(input)) return null;
2432
+ if (!isRecord4(input)) return null;
2337
2433
  const resourceType = readString(input.resourceType ?? input.resource_type);
2338
2434
  const resourceId = readString(input.resourceId ?? input.resource_id);
2339
2435
  if (!resourceType || !resourceId) return null;
@@ -2344,13 +2440,13 @@ function normalizeBindableResource(input) {
2344
2440
  slug: readOptionalString(input.slug),
2345
2441
  status: readOptionalString(input.status),
2346
2442
  organizationId: readOptionalString(input.organizationId ?? input.organization_id),
2347
- metadata: isRecord3(input.metadata) ? input.metadata : {}
2443
+ metadata: isRecord4(input.metadata) ? input.metadata : {}
2348
2444
  };
2349
2445
  }
2350
2446
  function normalizeResourceBindings(input) {
2351
2447
  if (!Array.isArray(input)) return [];
2352
2448
  return input.flatMap((binding) => {
2353
- if (!isRecord3(binding)) return [];
2449
+ if (!isRecord4(binding)) return [];
2354
2450
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
2355
2451
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
2356
2452
  return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
@@ -2363,30 +2459,9 @@ function readOptionalString(value) {
2363
2459
  const normalized = readString(value);
2364
2460
  return normalized || null;
2365
2461
  }
2366
- function isRecord3(value) {
2462
+ function isRecord4(value) {
2367
2463
  return typeof value === "object" && value !== null && !Array.isArray(value);
2368
2464
  }
2369
- function redactActionResult(value) {
2370
- if (Array.isArray(value)) return value.map(redactActionResult);
2371
- if (!isRecord3(value)) return value;
2372
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
2373
- key,
2374
- isSensitiveActionResultKey(key, item) ? "[REDACTED]" : redactActionResult(item)
2375
- ]));
2376
- }
2377
- function isSensitiveActionResultKey(key, value) {
2378
- const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
2379
- if (normalized === "capability") return isCredentialShaped(value);
2380
- return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
2381
- }
2382
- var PLAIN_ENUM_VALUE = /^[a-z][a-z0-9_-]{0,31}$/;
2383
- function isCredentialShaped(value) {
2384
- if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
2385
- const trimmed = value.trim();
2386
- if (!trimmed) return false;
2387
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return true;
2388
- return !PLAIN_ENUM_VALUE.test(trimmed);
2389
- }
2390
2465
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2391
2466
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2392
2467
  const configured = serviceUrls?.[serviceKey];
@@ -2456,14 +2531,14 @@ function catalogSearchQuery(options) {
2456
2531
  return query;
2457
2532
  }
2458
2533
  function normalizeCatalogPage(value) {
2459
- if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
2534
+ if (!isRecord4(value)) return { nextCursor: null, hasMore: false };
2460
2535
  return {
2461
2536
  nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
2462
2537
  hasMore: value.hasMore === true
2463
2538
  };
2464
2539
  }
2465
2540
  function normalizeServiceCatalogEntry(item) {
2466
- if (!isRecord3(item)) return null;
2541
+ if (!isRecord4(item)) return null;
2467
2542
  const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
2468
2543
  if (!appId) return null;
2469
2544
  const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
@@ -2928,7 +3003,7 @@ var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
2928
3003
  ["restore", "archive"],
2929
3004
  ["unpublish", "publish"]
2930
3005
  ]);
2931
- function isRecord4(value) {
3006
+ function isRecord5(value) {
2932
3007
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
2933
3008
  }
2934
3009
  function nonEmptyStrings(value) {
@@ -2966,10 +3041,10 @@ function collectionFor(action, resource) {
2966
3041
  return resource.endsWith("s") ? resource : `${resource}s`;
2967
3042
  }
2968
3043
  function isConcreteOutputSchema(schema) {
2969
- if (!isRecord4(schema)) return false;
3044
+ if (!isRecord5(schema)) return false;
2970
3045
  if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
2971
- if (schema.type === "array" && isRecord4(schema.items) && Object.keys(schema.items).length > 0) return true;
2972
- if (isRecord4(schema.properties) && Object.keys(schema.properties).length > 0) return true;
3046
+ if (schema.type === "array" && isRecord5(schema.items) && Object.keys(schema.items).length > 0) return true;
3047
+ if (isRecord5(schema.properties) && Object.keys(schema.properties).length > 0) return true;
2973
3048
  return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
2974
3049
  }
2975
3050
  function resourceSchema(resource) {
@@ -3096,10 +3171,10 @@ function isDynamicIdentifier(hint) {
3096
3171
  return words === "id" || words.endsWith(" id");
3097
3172
  }
3098
3173
  function localReference(rootSchema, reference) {
3099
- if (!isRecord4(rootSchema) || typeof reference !== "string" || !reference.startsWith("#/")) return null;
3174
+ if (!isRecord5(rootSchema) || typeof reference !== "string" || !reference.startsWith("#/")) return null;
3100
3175
  return reference.slice(2).split("/").reduce((value, segment) => {
3101
3176
  const key = segment.replaceAll("~1", "/").replaceAll("~0", "~");
3102
- return isRecord4(value) ? value[key] : void 0;
3177
+ return isRecord5(value) ? value[key] : void 0;
3103
3178
  }, rootSchema);
3104
3179
  }
3105
3180
  function representativeNumber(schema, hint) {
@@ -3113,9 +3188,9 @@ function representativeNumber(schema, hint) {
3113
3188
  return integer ? Math.round(value) : value;
3114
3189
  }
3115
3190
  function sampleForSchema(schema, depth = 0, hint = "value", rootSchema = schema) {
3116
- if (!isRecord4(schema) || depth > 20) return null;
3191
+ if (!isRecord5(schema) || depth > 20) return null;
3117
3192
  const referenced = localReference(rootSchema, schema.$ref);
3118
- if (isRecord4(referenced)) return sampleForSchema(referenced, depth + 1, hint, rootSchema);
3193
+ if (isRecord5(referenced)) return sampleForSchema(referenced, depth + 1, hint, rootSchema);
3119
3194
  if (schema.const !== void 0) return schema.const;
3120
3195
  if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
3121
3196
  if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1, hint, rootSchema);
@@ -3135,8 +3210,8 @@ function sampleForSchema(schema, depth = 0, hint = "value", rootSchema = schema)
3135
3210
  }
3136
3211
  if (schema.type === "string") return isDynamicIdentifier(hint) ? void 0 : sampleString(schema, hint);
3137
3212
  if (schema.type === "null") return null;
3138
- if (schema.type === "object" || isRecord4(schema.properties)) {
3139
- const properties = isRecord4(schema.properties) ? schema.properties : {};
3213
+ if (schema.type === "object" || isRecord5(schema.properties)) {
3214
+ const properties = isRecord5(schema.properties) ? schema.properties : {};
3140
3215
  const required = new Set(Array.isArray(schema.required) ? schema.required : []);
3141
3216
  const entries = Object.entries(properties).filter(([key]) => required.has(key)).map(([key, child]) => [key, sampleForSchema(child, depth + 1, key, rootSchema)]).filter(([, value]) => value !== void 0);
3142
3217
  return Object.fromEntries(entries);
@@ -3144,9 +3219,9 @@ function sampleForSchema(schema, depth = 0, hint = "value", rootSchema = schema)
3144
3219
  return null;
3145
3220
  }
3146
3221
  function hasRequiredDynamicIdentifier(schema, depth = 0, rootSchema = schema) {
3147
- if (!isRecord4(schema) || depth > 20) return false;
3222
+ if (!isRecord5(schema) || depth > 20) return false;
3148
3223
  const referenced = localReference(rootSchema, schema.$ref);
3149
- if (isRecord4(referenced)) return hasRequiredDynamicIdentifier(referenced, depth + 1, rootSchema);
3224
+ if (isRecord5(referenced)) return hasRequiredDynamicIdentifier(referenced, depth + 1, rootSchema);
3150
3225
  if (Array.isArray(schema.oneOf)) {
3151
3226
  return schema.oneOf.some((candidate) => hasRequiredDynamicIdentifier(candidate, depth + 1, rootSchema));
3152
3227
  }
@@ -3156,10 +3231,10 @@ function hasRequiredDynamicIdentifier(schema, depth = 0, rootSchema = schema) {
3156
3231
  if (schema.type === "array") {
3157
3232
  return hasRequiredDynamicIdentifier(schema.items, depth + 1, rootSchema);
3158
3233
  }
3159
- if (schema.type !== "object" && !isRecord4(schema.properties)) return false;
3160
- const properties = isRecord4(schema.properties) ? schema.properties : {};
3234
+ if (schema.type !== "object" && !isRecord5(schema.properties)) return false;
3235
+ const properties = isRecord5(schema.properties) ? schema.properties : {};
3161
3236
  const required = new Set(Array.isArray(schema.required) ? schema.required : []);
3162
- return Object.entries(properties).some(([key, child]) => required.has(key) && (isRecord4(child) && child.type === "string" && isDynamicIdentifier(key) || hasRequiredDynamicIdentifier(child, depth + 1, rootSchema)));
3237
+ return Object.entries(properties).some(([key, child]) => required.has(key) && (isRecord5(child) && child.type === "string" && isDynamicIdentifier(key) || hasRequiredDynamicIdentifier(child, depth + 1, rootSchema)));
3163
3238
  }
3164
3239
  function actionPrefix(name, operation) {
3165
3240
  const tokens = String(name || "").split(/[._:-]+/).filter(Boolean);
@@ -3232,13 +3307,13 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
3232
3307
  const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
3233
3308
  const resource = resourceFor(action);
3234
3309
  const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
3235
- const existing = isRecord4(action.agentContract) ? structuredClone(action.agentContract) : {};
3310
+ const existing = isRecord5(action.agentContract) ? structuredClone(action.agentContract) : {};
3236
3311
  const declaredAgentContract = Object.keys(existing).length > 0;
3237
- const recovery = isRecord4(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
3312
+ const recovery = isRecord5(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
3238
3313
  const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
3239
3314
  const inputExample = sampleForSchema(action.inputSchema);
3240
3315
  const expectedExample = readOnly ? `A successful response conforming to outputSchema; verify the returned ${resource.replaceAll("_", " ")} belongs to the selected resource context.` : `A successful response conforming to outputSchema; re-read the affected ${resource.replaceAll("_", " ")} to verify the intended change.`;
3241
- const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples.map((example) => isRecord4(example) && Object.hasOwn(example, "input") ? { ...example, expected: typeof example.expected === "string" && example.expected.trim() ? example.expected : expectedExample } : { input: example, expected: expectedExample }) : [{
3316
+ const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples.map((example) => isRecord5(example) && Object.hasOwn(example, "input") ? { ...example, expected: typeof example.expected === "string" && example.expected.trim() ? example.expected : expectedExample } : { input: example, expected: expectedExample }) : [{
3242
3317
  input: inputExample ?? {},
3243
3318
  expected: expectedExample
3244
3319
  }];
@@ -3251,7 +3326,7 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
3251
3326
  ...existing,
3252
3327
  docsUrl,
3253
3328
  ...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
3254
- resource: isRecord4(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
3329
+ resource: isRecord5(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
3255
3330
  effects: nonEmptyStrings(existing.effects) ? existing.effects : [readOnly ? `Reads ${resource.replaceAll("_", " ")} data without changing service state.` : action.destructive === true || action.method === "DELETE" ? `Permanently removes or invalidates ${resource.replaceAll("_", " ")} state.` : `Changes ${resource.replaceAll("_", " ")} state in the selected Topolo resource context.`],
3256
3331
  errors: mergeErrors(defaultErrors(action), existing.errors),
3257
3332
  verify: nonEmptyStrings(existing.verify) ? existing.verify : [`Validate the response against outputSchema and ${readOnly ? "confirm it belongs to the selected resource context" : "re-read the affected resource to confirm the intended state"}.`],
@@ -3266,15 +3341,15 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
3266
3341
  function actionContractCompleteness(action) {
3267
3342
  const contract = action?.agentContract;
3268
3343
  return {
3269
- inputSchema: isRecord4(action?.inputSchema),
3344
+ inputSchema: isRecord5(action?.inputSchema),
3270
3345
  outputSchema: isConcreteOutputSchema(action?.outputSchema),
3271
3346
  effects: nonEmptyStrings(contract?.effects),
3272
3347
  errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
3273
3348
  docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
3274
3349
  examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
3275
3350
  verification: nonEmptyStrings(contract?.verify),
3276
- recovery: isRecord4(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
3277
- agentContract: isRecord4(contract) && isRecord4(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
3351
+ recovery: isRecord5(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
3352
+ agentContract: isRecord5(contract) && isRecord5(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
3278
3353
  };
3279
3354
  }
3280
3355
 
@@ -3751,7 +3826,7 @@ var DIMENSION_VALUE_TYPES = ["string", "number", "boolean", "date", "datetime"];
3751
3826
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
3752
3827
  function validateTopoloWidgetResponse(response) {
3753
3828
  const errors = [];
3754
- if (!isRecord5(response)) return { ok: false, errors: ["response must be an object"] };
3829
+ if (!isRecord6(response)) return { ok: false, errors: ["response must be an object"] };
3755
3830
  exactValue(response.contractVersion, CONTRACT_VERSION, "contractVersion", errors);
3756
3831
  nonEmptyString(response.appId, "appId", errors);
3757
3832
  nonEmptyString(response.serviceName, "serviceName", errors);
@@ -3947,7 +4022,7 @@ function validateContribution(value, path, widget, errors) {
3947
4022
  nonEmptyString(value.workspaceSelector.resourceType, `${path}.workspaceSelector.resourceType`, errors);
3948
4023
  nonEmptyString(value.workspaceSelector.label, `${path}.workspaceSelector.label`, errors);
3949
4024
  booleanValue(value.workspaceSelector.supportsMultiple, `${path}.workspaceSelector.supportsMultiple`, errors);
3950
- if (value.workspaceSelector.mode === "required" && requiresContent(widget) && isRecord5(widget.resource) && typeof widget.resource.resourceId !== "string") {
4025
+ if (value.workspaceSelector.mode === "required" && requiresContent(widget) && isRecord6(widget.resource) && typeof widget.resource.resourceId !== "string") {
3951
4026
  errors.push(`${path}.workspaceSelector requires a bound resourceId for ready or stale data`);
3952
4027
  }
3953
4028
  }
@@ -4099,36 +4174,36 @@ function widgetActionKeys(widget) {
4099
4174
  const keys = /* @__PURE__ */ new Set();
4100
4175
  for (const collection of [widget.controls, widget.actions]) {
4101
4176
  if (!Array.isArray(collection)) continue;
4102
- for (const action of collection) if (isRecord5(action) && typeof action.id === "string") keys.add(action.id);
4177
+ for (const action of collection) if (isRecord6(action) && typeof action.id === "string") keys.add(action.id);
4103
4178
  }
4104
4179
  return keys;
4105
4180
  }
4106
4181
  function validateRuntimeSemantics(widget, contributionPath, declared, errors) {
4107
4182
  const metrics = Array.isArray(widget.metrics) ? widget.metrics : [];
4108
4183
  for (const metric of metrics) {
4109
- if (!isRecord5(metric) || typeof metric.key !== "string") continue;
4184
+ if (!isRecord6(metric) || typeof metric.key !== "string") continue;
4110
4185
  if (!declared.metricKeys.has(metric.key)) errors.push(`${contributionPath}.metricDefinitions is missing runtime metric ${metric.key}`);
4111
4186
  validateRuntimeDimensions(metric.dimensions, declared.dimensionKeys, contributionPath, errors);
4112
- if (isRecord5(metric.series)) {
4187
+ if (isRecord6(metric.series)) {
4113
4188
  if (typeof metric.series.id === "string" && !declared.seriesKeys.has(metric.series.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${metric.series.id}`);
4114
4189
  validateSeriesDimensions(metric.series, declared.dimensionKeys, contributionPath, errors);
4115
4190
  }
4116
4191
  }
4117
4192
  const series2 = Array.isArray(widget.series) ? widget.series : [];
4118
4193
  for (const item of series2) {
4119
- if (!isRecord5(item)) continue;
4194
+ if (!isRecord6(item)) continue;
4120
4195
  if (typeof item.id === "string" && !declared.seriesKeys.has(item.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${item.id}`);
4121
4196
  validateSeriesDimensions(item, declared.dimensionKeys, contributionPath, errors);
4122
4197
  }
4123
4198
  }
4124
4199
  function validateSeriesDimensions(series2, known, path, errors) {
4125
4200
  if (!Array.isArray(series2.points)) return;
4126
- for (const point of series2.points) if (isRecord5(point)) validateRuntimeDimensions(point.dimensions, known, path, errors);
4201
+ for (const point of series2.points) if (isRecord6(point)) validateRuntimeDimensions(point.dimensions, known, path, errors);
4127
4202
  }
4128
4203
  function validateRuntimeDimensions(value, known, path, errors) {
4129
4204
  if (!Array.isArray(value)) return;
4130
4205
  for (const dimension2 of value) {
4131
- if (isRecord5(dimension2) && typeof dimension2.key === "string" && !known.has(dimension2.key)) {
4206
+ if (isRecord6(dimension2) && typeof dimension2.key === "string" && !known.has(dimension2.key)) {
4132
4207
  errors.push(`${path}.dimensions is missing runtime dimension ${dimension2.key}`);
4133
4208
  }
4134
4209
  }
@@ -4230,10 +4305,10 @@ function validateNotifications(value, path, errors, requireOne) {
4230
4305
  });
4231
4306
  }
4232
4307
  function requiresContent(widget) {
4233
- return isRecord5(widget.state) && (widget.state.status === "ready" || widget.state.status === "stale");
4308
+ return isRecord6(widget.state) && (widget.state.status === "ready" || widget.state.status === "stale");
4234
4309
  }
4235
4310
  function recordValue(value, path, errors) {
4236
- if (!isRecord5(value)) {
4311
+ if (!isRecord6(value)) {
4237
4312
  errors.push(`${path} must be an object`);
4238
4313
  return false;
4239
4314
  }
@@ -4298,7 +4373,7 @@ function isIsoTimestamp(value) {
4298
4373
  function isPositiveNumber(value) {
4299
4374
  return typeof value === "number" && value > 0;
4300
4375
  }
4301
- function isRecord5(value) {
4376
+ function isRecord6(value) {
4302
4377
  return typeof value === "object" && value !== null && !Array.isArray(value);
4303
4378
  }
4304
4379
 
@@ -4840,19 +4915,19 @@ var TOPOLO_WIDGET_OUTPUT_SCHEMA = Object.freeze({
4840
4915
  additionalProperties: false
4841
4916
  });
4842
4917
  function isTopoloWidgetOutputSchema(schema) {
4843
- if (!isRecord6(schema) || !isRecord6(schema.properties)) return false;
4918
+ if (!isRecord7(schema) || !isRecord7(schema.properties)) return false;
4844
4919
  const properties = schema.properties;
4845
- const widgetItemSchema = isRecord6(properties.widgets) && isRecord6(properties.widgets.items) ? properties.widgets.items : void 0;
4846
- const widgetProperties = widgetItemSchema && isRecord6(widgetItemSchema.properties) ? widgetItemSchema.properties : void 0;
4847
- const contributionSchema = widgetProperties && isRecord6(widgetProperties.contribution) ? widgetProperties.contribution : void 0;
4848
- const contractVersion = isRecord6(properties.contractVersion) ? properties.contractVersion.const : void 0;
4849
- return schema.type === "object" && contractVersion === "3.0.0" && isRecord6(properties.widgets) && properties.widgets.type === "array" && widgetItemSchema?.type === "object" && hasRequired(schema, "contractVersion") && hasRequired(schema, "widgets") && hasRequired(widgetItemSchema, "contribution") && hasRequired(contributionSchema, "datasets") && hasRequired(contributionSchema, "compatibleVisualizations") && hasRequired(contributionSchema, "defaultVisualization");
4920
+ const widgetItemSchema = isRecord7(properties.widgets) && isRecord7(properties.widgets.items) ? properties.widgets.items : void 0;
4921
+ const widgetProperties = widgetItemSchema && isRecord7(widgetItemSchema.properties) ? widgetItemSchema.properties : void 0;
4922
+ const contributionSchema = widgetProperties && isRecord7(widgetProperties.contribution) ? widgetProperties.contribution : void 0;
4923
+ const contractVersion = isRecord7(properties.contractVersion) ? properties.contractVersion.const : void 0;
4924
+ return schema.type === "object" && contractVersion === "3.0.0" && isRecord7(properties.widgets) && properties.widgets.type === "array" && widgetItemSchema?.type === "object" && hasRequired(schema, "contractVersion") && hasRequired(schema, "widgets") && hasRequired(widgetItemSchema, "contribution") && hasRequired(contributionSchema, "datasets") && hasRequired(contributionSchema, "compatibleVisualizations") && hasRequired(contributionSchema, "defaultVisualization");
4850
4925
  }
4851
- function isRecord6(value) {
4926
+ function isRecord7(value) {
4852
4927
  return typeof value === "object" && value !== null && !Array.isArray(value);
4853
4928
  }
4854
4929
  function hasRequired(schema, field) {
4855
- return isRecord6(schema) && Array.isArray(schema.required) && schema.required.includes(field);
4930
+ return isRecord7(schema) && Array.isArray(schema.required) && schema.required.includes(field);
4856
4931
  }
4857
4932
 
4858
4933
  // src/index.ts