@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.cjs CHANGED
@@ -117,60 +117,6 @@ function generateRequestId() {
117
117
  return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
118
118
  }
119
119
 
120
- // src/dpop.ts
121
- var encoder = new TextEncoder();
122
- function encodeBase64Url(value) {
123
- const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
124
- let binary = "";
125
- for (const byte of bytes) binary += String.fromCharCode(byte);
126
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
127
- }
128
- function encodeJson(value) {
129
- return encodeBase64Url(encoder.encode(JSON.stringify(value)));
130
- }
131
- async function sha256Base64Url(value) {
132
- return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
133
- }
134
- function publicJwk(privateJwk) {
135
- const { crv, kty, x, y } = privateJwk;
136
- if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
137
- throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
138
- }
139
- return { crv, kty, x, y };
140
- }
141
- function canonicalTargetUrl(value) {
142
- const url = new URL(value);
143
- return `${url.origin}${url.pathname}`;
144
- }
145
- async function calculateDpopJkt(privateJwk) {
146
- const jwk = publicJwk(privateJwk);
147
- return sha256Base64Url(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }));
148
- }
149
- async function createDpopProof(input) {
150
- const jwk = publicJwk(input.privateJwk);
151
- const key = await crypto.subtle.importKey(
152
- "jwk",
153
- input.privateJwk,
154
- { name: "ECDSA", namedCurve: "P-256" },
155
- false,
156
- ["sign"]
157
- );
158
- const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
159
- const claims = encodeJson({
160
- jti: crypto.randomUUID(),
161
- iat: Math.floor(Date.now() / 1e3),
162
- htm: input.method.toUpperCase(),
163
- htu: canonicalTargetUrl(input.targetUrl),
164
- ath: await sha256Base64Url(input.accessToken)
165
- });
166
- const signature = await crypto.subtle.sign(
167
- { name: "ECDSA", hash: "SHA-256" },
168
- key,
169
- encoder.encode(`${header}.${claims}`)
170
- );
171
- return `${header}.${claims}.${encodeBase64Url(signature)}`;
172
- }
173
-
174
120
  // src/errors.ts
175
121
  var TopoloSdkError = class extends Error {
176
122
  code;
@@ -494,6 +440,139 @@ var TopoloHttpError = class extends TopoloSdkError {
494
440
  }
495
441
  };
496
442
 
443
+ // src/action-secrets.ts
444
+ var ACTION_SECRET_PREFIX = "topolo-secret:";
445
+ function isActionSecretReference(value) {
446
+ return typeof value === "string" && value.startsWith(ACTION_SECRET_PREFIX);
447
+ }
448
+ function isRecord(value) {
449
+ return typeof value === "object" && value !== null && !Array.isArray(value);
450
+ }
451
+ function sensitive(key, value) {
452
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
453
+ if (normalized === "capability") {
454
+ if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
455
+ const text2 = value.trim();
456
+ return Boolean(text2) && !/^[a-z][a-z0-9_-]{0,31}$/.test(text2);
457
+ }
458
+ return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
459
+ }
460
+ function maskActionSecrets(value, secrets) {
461
+ let masked = value;
462
+ for (const [secret, reference] of secrets) {
463
+ const encoded = encodeURIComponent(secret);
464
+ const variants = [.../* @__PURE__ */ new Set([secret, encoded])].map((item) => item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
465
+ masked = masked.replace(new RegExp(variants.join("|"), "g"), (match) => match === secret ? reference : encodeURIComponent(reference));
466
+ }
467
+ return masked;
468
+ }
469
+ async function resolveActionSecrets(value, store, secrets) {
470
+ if (isActionSecretReference(value)) {
471
+ if (!store) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
472
+ const resolved = await store.load(value);
473
+ if (typeof resolved === "string" && resolved) secrets.set(resolved, value);
474
+ return resolved;
475
+ }
476
+ if (Array.isArray(value)) return Promise.all(value.map((item) => resolveActionSecrets(item, store, secrets)));
477
+ if (!isRecord(value)) return value;
478
+ return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await resolveActionSecrets(item, store, secrets)])));
479
+ }
480
+ async function protectActionResult(value, store) {
481
+ const secrets = /* @__PURE__ */ new Map();
482
+ async function protect(item, key = "") {
483
+ if (isActionSecretReference(item)) return item;
484
+ if (sensitive(key, item)) {
485
+ const reference = store ? await store.save(item) : "[REDACTED]";
486
+ if (typeof item === "string" && item) secrets.set(item, reference);
487
+ return reference;
488
+ }
489
+ if (Array.isArray(item)) return Promise.all(item.map((child) => protect(child)));
490
+ if (!isRecord(item)) return item;
491
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([name, child]) => [name, await protect(child, name)])));
492
+ }
493
+ const protectedValue = await protect(value);
494
+ async function protectEmbedded(item) {
495
+ if (typeof item === "string" && !isActionSecretReference(item) && maskActionSecrets(item, secrets) !== item) {
496
+ return store ? store.save(item) : maskActionSecrets(item, secrets);
497
+ }
498
+ if (Array.isArray(item)) return Promise.all(item.map(protectEmbedded));
499
+ if (!isRecord(item)) return item;
500
+ return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([key, child]) => [key, await protectEmbedded(child)])));
501
+ }
502
+ return protectEmbedded(protectedValue);
503
+ }
504
+ function maskActionSecretValue(value, secrets) {
505
+ if (value instanceof Error) return maskActionSecretError(value, secrets);
506
+ if (typeof value === "string") return maskActionSecrets(value, secrets);
507
+ if (Array.isArray(value)) return value.map((item) => maskActionSecretValue(item, secrets));
508
+ if (!isRecord(value)) return value;
509
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, maskActionSecretValue(item, secrets)]));
510
+ }
511
+ function maskActionSecretError(error, secrets) {
512
+ if (!(error instanceof Error)) return maskActionSecretValue(error, secrets);
513
+ const masked = Object.create(Object.getPrototypeOf(error));
514
+ for (const key of Object.getOwnPropertyNames(error)) {
515
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
516
+ if ("value" in descriptor) descriptor.value = maskActionSecretValue(descriptor.value, secrets);
517
+ Object.defineProperty(masked, key, descriptor);
518
+ }
519
+ return masked;
520
+ }
521
+
522
+ // src/dpop.ts
523
+ var encoder = new TextEncoder();
524
+ function encodeBase64Url(value) {
525
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
526
+ let binary = "";
527
+ for (const byte of bytes) binary += String.fromCharCode(byte);
528
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
529
+ }
530
+ function encodeJson(value) {
531
+ return encodeBase64Url(encoder.encode(JSON.stringify(value)));
532
+ }
533
+ async function sha256Base64Url(value) {
534
+ return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
535
+ }
536
+ function publicJwk(privateJwk) {
537
+ const { crv, kty, x, y } = privateJwk;
538
+ if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
539
+ throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
540
+ }
541
+ return { crv, kty, x, y };
542
+ }
543
+ function canonicalTargetUrl(value) {
544
+ const url = new URL(value);
545
+ return `${url.origin}${url.pathname}`;
546
+ }
547
+ async function calculateDpopJkt(privateJwk) {
548
+ const jwk = publicJwk(privateJwk);
549
+ return sha256Base64Url(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }));
550
+ }
551
+ async function createDpopProof(input) {
552
+ const jwk = publicJwk(input.privateJwk);
553
+ const key = await crypto.subtle.importKey(
554
+ "jwk",
555
+ input.privateJwk,
556
+ { name: "ECDSA", namedCurve: "P-256" },
557
+ false,
558
+ ["sign"]
559
+ );
560
+ const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
561
+ const claims = encodeJson({
562
+ jti: crypto.randomUUID(),
563
+ iat: Math.floor(Date.now() / 1e3),
564
+ htm: input.method.toUpperCase(),
565
+ htu: canonicalTargetUrl(input.targetUrl),
566
+ ath: await sha256Base64Url(input.accessToken)
567
+ });
568
+ const signature = await crypto.subtle.sign(
569
+ { name: "ECDSA", hash: "SHA-256" },
570
+ key,
571
+ encoder.encode(`${header}.${claims}`)
572
+ );
573
+ return `${header}.${claims}.${encodeBase64Url(signature)}`;
574
+ }
575
+
497
576
  // src/environment.ts
498
577
  var TOPOLO_RUNTIME_ENVIRONMENTS = ["production", "staging", "development", "local"];
499
578
  var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
@@ -799,9 +878,9 @@ function inspectSchema(schema, path, root, depth) {
799
878
  return { path, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
800
879
  }
801
880
  const children = [];
802
- if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
803
- if (isRecord(schema.$defs)) children.push(...Object.entries(schema.$defs));
804
- if (isRecord(schema.definitions)) children.push(...Object.entries(schema.definitions));
881
+ if (isRecord2(schema.properties)) children.push(...Object.entries(schema.properties));
882
+ if (isRecord2(schema.$defs)) children.push(...Object.entries(schema.$defs));
883
+ if (isRecord2(schema.definitions)) children.push(...Object.entries(schema.definitions));
805
884
  children.push(
806
885
  ["additionalProperties", schema.additionalProperties],
807
886
  ["propertyNames", schema.propertyNames],
@@ -811,7 +890,7 @@ function inspectSchema(schema, path, root, depth) {
811
890
  ...arraySchemaEntries("oneOf", schema.oneOf)
812
891
  );
813
892
  for (const [key, child] of children) {
814
- if (!isRecord(child)) continue;
893
+ if (!isRecord2(child)) continue;
815
894
  const issue = inspectSchema(child, `${path}.${key}`, root, depth + 1);
816
895
  if (issue) return issue;
817
896
  }
@@ -835,7 +914,7 @@ function validateNode(schema, value, path, root, errors, depth) {
835
914
  }
836
915
  if (Array.isArray(schema.allOf)) {
837
916
  for (const candidate of schema.allOf) {
838
- if (isRecord(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
917
+ if (isRecord2(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
839
918
  }
840
919
  }
841
920
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
@@ -865,7 +944,7 @@ function validateNode(schema, value, path, root, errors, depth) {
865
944
  if (typeof value === "string") validateString(schema, value, path, errors);
866
945
  if (typeof value === "number") validateNumber(schema, value, path, errors);
867
946
  if (Array.isArray(value)) validateArray(schema, value, path, root, errors, depth);
868
- if (isRecord(value)) validateObject(schema, value, path, root, errors, depth);
947
+ if (isRecord2(value)) validateObject(schema, value, path, root, errors, depth);
869
948
  }
870
949
  function validateObject(schema, value, path, root, errors, depth) {
871
950
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
@@ -874,25 +953,25 @@ function validateObject(schema, value, path, root, errors, depth) {
874
953
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
875
954
  errors.push({ path, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
876
955
  }
877
- const properties = isRecord(schema.properties) ? schema.properties : {};
956
+ const properties = isRecord2(schema.properties) ? schema.properties : {};
878
957
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
879
958
  for (const key of required) {
880
959
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
881
960
  errors.push({ path: `${path}.${key}`, code: "required", message: "Required property is missing." });
882
961
  }
883
962
  }
884
- if (isRecord(schema.propertyNames)) {
963
+ if (isRecord2(schema.propertyNames)) {
885
964
  for (const key of Object.keys(value)) {
886
965
  validateNode(schema.propertyNames, key, `${path}.${key}`, root, errors, depth + 1);
887
966
  }
888
967
  }
889
968
  for (const [key, child] of Object.entries(value)) {
890
969
  const childSchema = properties[key];
891
- if (isRecord(childSchema)) {
970
+ if (isRecord2(childSchema)) {
892
971
  validateNode(childSchema, child, `${path}.${key}`, root, errors, depth + 1);
893
972
  } else if (schema.additionalProperties === false) {
894
973
  errors.push({ path: `${path}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
895
- } else if (isRecord(schema.additionalProperties)) {
974
+ } else if (isRecord2(schema.additionalProperties)) {
896
975
  validateNode(schema.additionalProperties, child, `${path}.${key}`, root, errors, depth + 1);
897
976
  }
898
977
  }
@@ -908,7 +987,7 @@ function validateArray(schema, value, path, root, errors, depth) {
908
987
  const unique = new Set(value.map((item) => JSON.stringify(item)));
909
988
  if (unique.size !== value.length) errors.push({ path, code: "unique_items", message: "Expected every item to be unique." });
910
989
  }
911
- if (isRecord(schema.items)) {
990
+ if (isRecord2(schema.items)) {
912
991
  value.forEach((item, index) => validateNode(schema.items, item, `${path}[${index}]`, root, errors, depth + 1));
913
992
  }
914
993
  }
@@ -946,7 +1025,7 @@ function validateNumber(schema, value, path, errors) {
946
1025
  }
947
1026
  }
948
1027
  function candidateMatches(candidate, value, root, depth) {
949
- if (!isRecord(candidate)) return false;
1028
+ if (!isRecord2(candidate)) return false;
950
1029
  const issues = [];
951
1030
  validateNode(candidate, value, "$", root, issues, depth + 1);
952
1031
  return issues.length === 0;
@@ -960,24 +1039,24 @@ function resolveLocalReference(root, reference) {
960
1039
  current = current[Number(token)];
961
1040
  continue;
962
1041
  }
963
- if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
1042
+ if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
964
1043
  current = current[token];
965
1044
  }
966
- return isRecord(current) ? current : null;
1045
+ return isRecord2(current) ? current : null;
967
1046
  }
968
1047
  function matchesType(type, value) {
969
1048
  if (type === void 0) return true;
970
1049
  if (Array.isArray(type)) return type.some((candidate) => matchesType(candidate, value));
971
1050
  if (type === "null") return value === null;
972
1051
  if (type === "array") return Array.isArray(value);
973
- if (type === "object") return isRecord(value);
1052
+ if (type === "object") return isRecord2(value);
974
1053
  if (type === "integer") return typeof value === "number" && Number.isInteger(value);
975
1054
  return typeof value === type;
976
1055
  }
977
1056
  function sameJsonValue(left, right) {
978
1057
  return JSON.stringify(left) === JSON.stringify(right);
979
1058
  }
980
- function isRecord(value) {
1059
+ function isRecord2(value) {
981
1060
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
982
1061
  }
983
1062
  function isUrl(value) {
@@ -1234,7 +1313,7 @@ function fail(path, message) {
1234
1313
 
1235
1314
  // src/action-upload.ts
1236
1315
  function parseTopoloActionUploadContract(value) {
1237
- if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
1316
+ if (!isRecord3(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
1238
1317
  throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
1239
1318
  }
1240
1319
  const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
@@ -1313,17 +1392,17 @@ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, op
1313
1392
  };
1314
1393
  }
1315
1394
  function topoloUploadResponseValue(response, path) {
1316
- return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
1395
+ return path.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, response);
1317
1396
  }
1318
1397
  function redactTopoloUrlSessionResult(response, uploadUrlPath) {
1319
1398
  const redacted = cloneUploadValue(response);
1320
1399
  let target = redacted;
1321
1400
  const segments = uploadUrlPath.split(".");
1322
1401
  for (const segment of segments.slice(0, -1)) {
1323
- target = isRecord2(target) ? target[segment] : void 0;
1402
+ target = isRecord3(target) ? target[segment] : void 0;
1324
1403
  }
1325
1404
  const finalSegment = segments.at(-1);
1326
- if (finalSegment && isRecord2(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1405
+ if (finalSegment && isRecord3(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
1327
1406
  redactUploadHeaders(redacted);
1328
1407
  return redacted;
1329
1408
  }
@@ -1347,7 +1426,7 @@ function mapUploadInput(contract, contentBase64, input, options) {
1347
1426
  }
1348
1427
  function cloneUploadValue(value) {
1349
1428
  if (Array.isArray(value)) return value.map(cloneUploadValue);
1350
- if (!isRecord2(value)) return value;
1429
+ if (!isRecord3(value)) return value;
1351
1430
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneUploadValue(item)]));
1352
1431
  }
1353
1432
  function redactUploadHeaders(value) {
@@ -1355,9 +1434,9 @@ function redactUploadHeaders(value) {
1355
1434
  value.forEach(redactUploadHeaders);
1356
1435
  return;
1357
1436
  }
1358
- if (!isRecord2(value)) return;
1437
+ if (!isRecord3(value)) return;
1359
1438
  for (const [key, item] of Object.entries(value)) {
1360
- if (key.toLowerCase() === "headers" && isRecord2(item)) {
1439
+ if (key.toLowerCase() === "headers" && isRecord3(item)) {
1361
1440
  for (const header of Object.keys(item)) {
1362
1441
  if (["authorization", "cookie", "set-cookie", "proxy-authorization", "x-api-key"].includes(header.toLowerCase())) {
1363
1442
  item[header] = "[REDACTED]";
@@ -1386,7 +1465,7 @@ function decodedBase64Size(contentBase64) {
1386
1465
  return contentBase64.length / 4 * 3 - padding;
1387
1466
  }
1388
1467
  function parseCompleteContract(value) {
1389
- if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1468
+ if (!isRecord3(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
1390
1469
  return {
1391
1470
  actionId: requiredPath(value.actionId, "complete.actionId"),
1392
1471
  inputField: requiredField(value.inputField, "complete.inputField"),
@@ -1424,7 +1503,7 @@ function encodeTopoloActionBase64(bytes) {
1424
1503
  }
1425
1504
  return btoa(binary);
1426
1505
  }
1427
- function isRecord2(value) {
1506
+ function isRecord3(value) {
1428
1507
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1429
1508
  }
1430
1509
 
@@ -1437,6 +1516,8 @@ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
1437
1516
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
1438
1517
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
1439
1518
  var TopoloClient = class _TopoloClient {
1519
+ actionSecretStore;
1520
+ actionSecrets = /* @__PURE__ */ new Map();
1440
1521
  credential;
1441
1522
  agent;
1442
1523
  actorContext;
@@ -1466,6 +1547,7 @@ var TopoloClient = class _TopoloClient {
1466
1547
  _TopoloClient.appIdCache.clear();
1467
1548
  }
1468
1549
  constructor(options) {
1550
+ this.actionSecretStore = options.actionSecretStore;
1469
1551
  if (!options.credential) throw new TopoloAuthError("credential is required");
1470
1552
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
1471
1553
  this.credential = options.credential;
@@ -1487,6 +1569,7 @@ var TopoloClient = class _TopoloClient {
1487
1569
  }
1488
1570
  emit(event) {
1489
1571
  if (!this.debug) return;
1572
+ event = maskActionSecretValue(event, this.actionSecrets);
1490
1573
  try {
1491
1574
  this.debug(event);
1492
1575
  } catch {
@@ -1872,7 +1955,7 @@ var TopoloClient = class _TopoloClient {
1872
1955
  }
1873
1956
  const entries = [];
1874
1957
  for (const item of raw) {
1875
- if (!isRecord3(item)) continue;
1958
+ if (!isRecord4(item)) continue;
1876
1959
  const s = item;
1877
1960
  const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
1878
1961
  if (!appId) continue;
@@ -1947,8 +2030,8 @@ var TopoloClient = class _TopoloClient {
1947
2030
  "app_topolo_auth.service_api_key_resources.list",
1948
2031
  { appId: service.appId }
1949
2032
  );
1950
- const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
1951
- if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
2033
+ const payload = isRecord4(response) && isRecord4(response.data) ? response.data : response;
2034
+ if (!isRecord4(payload) || !Array.isArray(payload.resources)) {
1952
2035
  throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
1953
2036
  }
1954
2037
  return {
@@ -2129,17 +2212,20 @@ var TopoloClient = class _TopoloClient {
2129
2212
  }
2130
2213
  async validateActionInput(action, input = {}) {
2131
2214
  const entry = await this.getAction(action);
2132
- return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input) };
2215
+ const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
2216
+ return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, resolved) };
2133
2217
  }
2134
2218
  async planAction(action, input = {}, options = {}) {
2135
2219
  const entry = await this.getAction(action);
2136
- return this.planResolvedAction(entry, input, options);
2220
+ const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
2221
+ return this.planResolvedAction(entry, resolved, options);
2137
2222
  }
2138
2223
  async prepareAction(action, input, options = {}) {
2139
2224
  const entry = await this.getAction(action);
2140
2225
  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;
2141
2226
  const selectedInput = input ?? example?.input ?? {};
2142
- const plan = this.planResolvedAction(entry, selectedInput, options);
2227
+ const resolved = await resolveActionSecrets(selectedInput, this.actionSecretStore, this.actionSecrets);
2228
+ const plan = this.planResolvedAction(entry, resolved, options);
2143
2229
  return {
2144
2230
  action: entry,
2145
2231
  example,
@@ -2176,7 +2262,7 @@ var TopoloClient = class _TopoloClient {
2176
2262
  confirmationRequired,
2177
2263
  confirmationProvided: options.confirm === true,
2178
2264
  executable: validation.valid && agentAccess !== "off" && (!confirmationRequired || options.confirm === true),
2179
- request,
2265
+ request: maskActionSecretValue(request, this.actionSecrets),
2180
2266
  verification: stringArray(entry.agentContract?.verify),
2181
2267
  rollbackActionId: pickActionString(entry.agentContract?.rollbackActionId),
2182
2268
  nextActionIds: stringArray(entry.agentContract?.nextActionIds)
@@ -2206,6 +2292,7 @@ var TopoloClient = class _TopoloClient {
2206
2292
  `Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
2207
2293
  );
2208
2294
  }
2295
+ input = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
2209
2296
  const validation = validateTopoloActionInput(entry.inputSchema, input);
2210
2297
  if (!validation.valid) {
2211
2298
  throw new TopoloSdkError(
@@ -2236,22 +2323,31 @@ var TopoloClient = class _TopoloClient {
2236
2323
  }
2237
2324
  }
2238
2325
  const service = entry.appSlug || entry.appId;
2239
- const result = await this.request({
2240
- service,
2241
- ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
2242
- method: entry.method,
2243
- path: mapped.path,
2244
- ...mapped.query ? { query: mapped.query } : {},
2245
- ...mapped.body !== void 0 ? { body: mapped.body } : {},
2246
- ...options.resource ? { resource: options.resource } : {},
2247
- ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
2248
- headers: {
2249
- [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
2250
- [TOPOLO_ACTION_NAME_HEADER]: entry.name
2251
- },
2252
- confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
2253
- });
2254
- return preserveSensitiveResult ? result : redactActionResult(result);
2326
+ let result;
2327
+ try {
2328
+ result = await this.request({
2329
+ service,
2330
+ ...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
2331
+ method: entry.method,
2332
+ path: mapped.path,
2333
+ ...mapped.query ? { query: mapped.query } : {},
2334
+ ...mapped.body !== void 0 ? { body: mapped.body } : {},
2335
+ ...options.resource ? { resource: options.resource } : {},
2336
+ ...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
2337
+ headers: {
2338
+ [TOPOLO_ACTION_ID_HEADER]: entry.actionId,
2339
+ [TOPOLO_ACTION_NAME_HEADER]: entry.name
2340
+ },
2341
+ confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
2342
+ });
2343
+ } catch (error) {
2344
+ throw maskActionSecretError(error, this.actionSecrets);
2345
+ }
2346
+ return preserveSensitiveResult ? result : await protectActionResult(result, this.actionSecretStore);
2347
+ }
2348
+ async releaseActionSecret(reference) {
2349
+ if (!this.actionSecretStore) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
2350
+ await this.actionSecretStore.delete(reference);
2255
2351
  }
2256
2352
  async buildVerifiedAgentActorEnvelope(actionId, input, resource) {
2257
2353
  if (!this.actorContext) return void 0;
@@ -2398,7 +2494,7 @@ function resolveConfiguredAppId(slug, appIds) {
2398
2494
  return null;
2399
2495
  }
2400
2496
  function normalizeApiKeyResourceMetadata(input) {
2401
- if (!isRecord3(input)) return null;
2497
+ if (!isRecord4(input)) return null;
2402
2498
  const resourceType = readString(input.resourceType ?? input.resource_type);
2403
2499
  const label = readString(input.label);
2404
2500
  const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
@@ -2432,7 +2528,7 @@ function normalizeApiKeyResourceMetadataList(input) {
2432
2528
  return resources;
2433
2529
  }
2434
2530
  function normalizeBindableResource(input) {
2435
- if (!isRecord3(input)) return null;
2531
+ if (!isRecord4(input)) return null;
2436
2532
  const resourceType = readString(input.resourceType ?? input.resource_type);
2437
2533
  const resourceId = readString(input.resourceId ?? input.resource_id);
2438
2534
  if (!resourceType || !resourceId) return null;
@@ -2443,13 +2539,13 @@ function normalizeBindableResource(input) {
2443
2539
  slug: readOptionalString(input.slug),
2444
2540
  status: readOptionalString(input.status),
2445
2541
  organizationId: readOptionalString(input.organizationId ?? input.organization_id),
2446
- metadata: isRecord3(input.metadata) ? input.metadata : {}
2542
+ metadata: isRecord4(input.metadata) ? input.metadata : {}
2447
2543
  };
2448
2544
  }
2449
2545
  function normalizeResourceBindings(input) {
2450
2546
  if (!Array.isArray(input)) return [];
2451
2547
  return input.flatMap((binding) => {
2452
- if (!isRecord3(binding)) return [];
2548
+ if (!isRecord4(binding)) return [];
2453
2549
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
2454
2550
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
2455
2551
  return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
@@ -2462,30 +2558,9 @@ function readOptionalString(value) {
2462
2558
  const normalized = readString(value);
2463
2559
  return normalized || null;
2464
2560
  }
2465
- function isRecord3(value) {
2561
+ function isRecord4(value) {
2466
2562
  return typeof value === "object" && value !== null && !Array.isArray(value);
2467
2563
  }
2468
- function redactActionResult(value) {
2469
- if (Array.isArray(value)) return value.map(redactActionResult);
2470
- if (!isRecord3(value)) return value;
2471
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
2472
- key,
2473
- isSensitiveActionResultKey(key, item) ? "[REDACTED]" : redactActionResult(item)
2474
- ]));
2475
- }
2476
- function isSensitiveActionResultKey(key, value) {
2477
- const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
2478
- if (normalized === "capability") return isCredentialShaped(value);
2479
- return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
2480
- }
2481
- var PLAIN_ENUM_VALUE = /^[a-z][a-z0-9_-]{0,31}$/;
2482
- function isCredentialShaped(value) {
2483
- if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
2484
- const trimmed = value.trim();
2485
- if (!trimmed) return false;
2486
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return true;
2487
- return !PLAIN_ENUM_VALUE.test(trimmed);
2488
- }
2489
2564
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2490
2565
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2491
2566
  const configured = serviceUrls?.[serviceKey];
@@ -2555,14 +2630,14 @@ function catalogSearchQuery(options) {
2555
2630
  return query;
2556
2631
  }
2557
2632
  function normalizeCatalogPage(value) {
2558
- if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
2633
+ if (!isRecord4(value)) return { nextCursor: null, hasMore: false };
2559
2634
  return {
2560
2635
  nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
2561
2636
  hasMore: value.hasMore === true
2562
2637
  };
2563
2638
  }
2564
2639
  function normalizeServiceCatalogEntry(item) {
2565
- if (!isRecord3(item)) return null;
2640
+ if (!isRecord4(item)) return null;
2566
2641
  const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
2567
2642
  if (!appId) return null;
2568
2643
  const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
@@ -3027,7 +3102,7 @@ var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
3027
3102
  ["restore", "archive"],
3028
3103
  ["unpublish", "publish"]
3029
3104
  ]);
3030
- function isRecord4(value) {
3105
+ function isRecord5(value) {
3031
3106
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
3032
3107
  }
3033
3108
  function nonEmptyStrings(value) {
@@ -3065,10 +3140,10 @@ function collectionFor(action, resource) {
3065
3140
  return resource.endsWith("s") ? resource : `${resource}s`;
3066
3141
  }
3067
3142
  function isConcreteOutputSchema(schema) {
3068
- if (!isRecord4(schema)) return false;
3143
+ if (!isRecord5(schema)) return false;
3069
3144
  if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
3070
- if (schema.type === "array" && isRecord4(schema.items) && Object.keys(schema.items).length > 0) return true;
3071
- if (isRecord4(schema.properties) && Object.keys(schema.properties).length > 0) return true;
3145
+ if (schema.type === "array" && isRecord5(schema.items) && Object.keys(schema.items).length > 0) return true;
3146
+ if (isRecord5(schema.properties) && Object.keys(schema.properties).length > 0) return true;
3072
3147
  return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
3073
3148
  }
3074
3149
  function resourceSchema(resource) {
@@ -3195,10 +3270,10 @@ function isDynamicIdentifier(hint) {
3195
3270
  return words === "id" || words.endsWith(" id");
3196
3271
  }
3197
3272
  function localReference(rootSchema, reference) {
3198
- if (!isRecord4(rootSchema) || typeof reference !== "string" || !reference.startsWith("#/")) return null;
3273
+ if (!isRecord5(rootSchema) || typeof reference !== "string" || !reference.startsWith("#/")) return null;
3199
3274
  return reference.slice(2).split("/").reduce((value, segment) => {
3200
3275
  const key = segment.replaceAll("~1", "/").replaceAll("~0", "~");
3201
- return isRecord4(value) ? value[key] : void 0;
3276
+ return isRecord5(value) ? value[key] : void 0;
3202
3277
  }, rootSchema);
3203
3278
  }
3204
3279
  function representativeNumber(schema, hint) {
@@ -3212,9 +3287,9 @@ function representativeNumber(schema, hint) {
3212
3287
  return integer ? Math.round(value) : value;
3213
3288
  }
3214
3289
  function sampleForSchema(schema, depth = 0, hint = "value", rootSchema = schema) {
3215
- if (!isRecord4(schema) || depth > 20) return null;
3290
+ if (!isRecord5(schema) || depth > 20) return null;
3216
3291
  const referenced = localReference(rootSchema, schema.$ref);
3217
- if (isRecord4(referenced)) return sampleForSchema(referenced, depth + 1, hint, rootSchema);
3292
+ if (isRecord5(referenced)) return sampleForSchema(referenced, depth + 1, hint, rootSchema);
3218
3293
  if (schema.const !== void 0) return schema.const;
3219
3294
  if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
3220
3295
  if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1, hint, rootSchema);
@@ -3234,8 +3309,8 @@ function sampleForSchema(schema, depth = 0, hint = "value", rootSchema = schema)
3234
3309
  }
3235
3310
  if (schema.type === "string") return isDynamicIdentifier(hint) ? void 0 : sampleString(schema, hint);
3236
3311
  if (schema.type === "null") return null;
3237
- if (schema.type === "object" || isRecord4(schema.properties)) {
3238
- const properties = isRecord4(schema.properties) ? schema.properties : {};
3312
+ if (schema.type === "object" || isRecord5(schema.properties)) {
3313
+ const properties = isRecord5(schema.properties) ? schema.properties : {};
3239
3314
  const required = new Set(Array.isArray(schema.required) ? schema.required : []);
3240
3315
  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);
3241
3316
  return Object.fromEntries(entries);
@@ -3243,9 +3318,9 @@ function sampleForSchema(schema, depth = 0, hint = "value", rootSchema = schema)
3243
3318
  return null;
3244
3319
  }
3245
3320
  function hasRequiredDynamicIdentifier(schema, depth = 0, rootSchema = schema) {
3246
- if (!isRecord4(schema) || depth > 20) return false;
3321
+ if (!isRecord5(schema) || depth > 20) return false;
3247
3322
  const referenced = localReference(rootSchema, schema.$ref);
3248
- if (isRecord4(referenced)) return hasRequiredDynamicIdentifier(referenced, depth + 1, rootSchema);
3323
+ if (isRecord5(referenced)) return hasRequiredDynamicIdentifier(referenced, depth + 1, rootSchema);
3249
3324
  if (Array.isArray(schema.oneOf)) {
3250
3325
  return schema.oneOf.some((candidate) => hasRequiredDynamicIdentifier(candidate, depth + 1, rootSchema));
3251
3326
  }
@@ -3255,10 +3330,10 @@ function hasRequiredDynamicIdentifier(schema, depth = 0, rootSchema = schema) {
3255
3330
  if (schema.type === "array") {
3256
3331
  return hasRequiredDynamicIdentifier(schema.items, depth + 1, rootSchema);
3257
3332
  }
3258
- if (schema.type !== "object" && !isRecord4(schema.properties)) return false;
3259
- const properties = isRecord4(schema.properties) ? schema.properties : {};
3333
+ if (schema.type !== "object" && !isRecord5(schema.properties)) return false;
3334
+ const properties = isRecord5(schema.properties) ? schema.properties : {};
3260
3335
  const required = new Set(Array.isArray(schema.required) ? schema.required : []);
3261
- return Object.entries(properties).some(([key, child]) => required.has(key) && (isRecord4(child) && child.type === "string" && isDynamicIdentifier(key) || hasRequiredDynamicIdentifier(child, depth + 1, rootSchema)));
3336
+ return Object.entries(properties).some(([key, child]) => required.has(key) && (isRecord5(child) && child.type === "string" && isDynamicIdentifier(key) || hasRequiredDynamicIdentifier(child, depth + 1, rootSchema)));
3262
3337
  }
3263
3338
  function actionPrefix(name, operation) {
3264
3339
  const tokens = String(name || "").split(/[._:-]+/).filter(Boolean);
@@ -3331,13 +3406,13 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
3331
3406
  const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
3332
3407
  const resource = resourceFor(action);
3333
3408
  const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
3334
- const existing = isRecord4(action.agentContract) ? structuredClone(action.agentContract) : {};
3409
+ const existing = isRecord5(action.agentContract) ? structuredClone(action.agentContract) : {};
3335
3410
  const declaredAgentContract = Object.keys(existing).length > 0;
3336
- const recovery = isRecord4(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
3411
+ const recovery = isRecord5(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
3337
3412
  const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
3338
3413
  const inputExample = sampleForSchema(action.inputSchema);
3339
3414
  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.`;
3340
- 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 }) : [{
3415
+ 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 }) : [{
3341
3416
  input: inputExample ?? {},
3342
3417
  expected: expectedExample
3343
3418
  }];
@@ -3350,7 +3425,7 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
3350
3425
  ...existing,
3351
3426
  docsUrl,
3352
3427
  ...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
3353
- resource: isRecord4(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
3428
+ resource: isRecord5(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
3354
3429
  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.`],
3355
3430
  errors: mergeErrors(defaultErrors(action), existing.errors),
3356
3431
  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"}.`],
@@ -3365,15 +3440,15 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
3365
3440
  function actionContractCompleteness(action) {
3366
3441
  const contract = action?.agentContract;
3367
3442
  return {
3368
- inputSchema: isRecord4(action?.inputSchema),
3443
+ inputSchema: isRecord5(action?.inputSchema),
3369
3444
  outputSchema: isConcreteOutputSchema(action?.outputSchema),
3370
3445
  effects: nonEmptyStrings(contract?.effects),
3371
3446
  errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
3372
3447
  docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
3373
3448
  examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
3374
3449
  verification: nonEmptyStrings(contract?.verify),
3375
- recovery: isRecord4(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
3376
- agentContract: isRecord4(contract) && isRecord4(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
3450
+ recovery: isRecord5(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
3451
+ agentContract: isRecord5(contract) && isRecord5(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
3377
3452
  };
3378
3453
  }
3379
3454
 
@@ -3850,7 +3925,7 @@ var DIMENSION_VALUE_TYPES = ["string", "number", "boolean", "date", "datetime"];
3850
3925
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
3851
3926
  function validateTopoloWidgetResponse(response) {
3852
3927
  const errors = [];
3853
- if (!isRecord5(response)) return { ok: false, errors: ["response must be an object"] };
3928
+ if (!isRecord6(response)) return { ok: false, errors: ["response must be an object"] };
3854
3929
  exactValue(response.contractVersion, CONTRACT_VERSION, "contractVersion", errors);
3855
3930
  nonEmptyString(response.appId, "appId", errors);
3856
3931
  nonEmptyString(response.serviceName, "serviceName", errors);
@@ -4046,7 +4121,7 @@ function validateContribution(value, path, widget, errors) {
4046
4121
  nonEmptyString(value.workspaceSelector.resourceType, `${path}.workspaceSelector.resourceType`, errors);
4047
4122
  nonEmptyString(value.workspaceSelector.label, `${path}.workspaceSelector.label`, errors);
4048
4123
  booleanValue(value.workspaceSelector.supportsMultiple, `${path}.workspaceSelector.supportsMultiple`, errors);
4049
- if (value.workspaceSelector.mode === "required" && requiresContent(widget) && isRecord5(widget.resource) && typeof widget.resource.resourceId !== "string") {
4124
+ if (value.workspaceSelector.mode === "required" && requiresContent(widget) && isRecord6(widget.resource) && typeof widget.resource.resourceId !== "string") {
4050
4125
  errors.push(`${path}.workspaceSelector requires a bound resourceId for ready or stale data`);
4051
4126
  }
4052
4127
  }
@@ -4198,36 +4273,36 @@ function widgetActionKeys(widget) {
4198
4273
  const keys = /* @__PURE__ */ new Set();
4199
4274
  for (const collection of [widget.controls, widget.actions]) {
4200
4275
  if (!Array.isArray(collection)) continue;
4201
- for (const action of collection) if (isRecord5(action) && typeof action.id === "string") keys.add(action.id);
4276
+ for (const action of collection) if (isRecord6(action) && typeof action.id === "string") keys.add(action.id);
4202
4277
  }
4203
4278
  return keys;
4204
4279
  }
4205
4280
  function validateRuntimeSemantics(widget, contributionPath, declared, errors) {
4206
4281
  const metrics = Array.isArray(widget.metrics) ? widget.metrics : [];
4207
4282
  for (const metric of metrics) {
4208
- if (!isRecord5(metric) || typeof metric.key !== "string") continue;
4283
+ if (!isRecord6(metric) || typeof metric.key !== "string") continue;
4209
4284
  if (!declared.metricKeys.has(metric.key)) errors.push(`${contributionPath}.metricDefinitions is missing runtime metric ${metric.key}`);
4210
4285
  validateRuntimeDimensions(metric.dimensions, declared.dimensionKeys, contributionPath, errors);
4211
- if (isRecord5(metric.series)) {
4286
+ if (isRecord6(metric.series)) {
4212
4287
  if (typeof metric.series.id === "string" && !declared.seriesKeys.has(metric.series.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${metric.series.id}`);
4213
4288
  validateSeriesDimensions(metric.series, declared.dimensionKeys, contributionPath, errors);
4214
4289
  }
4215
4290
  }
4216
4291
  const series2 = Array.isArray(widget.series) ? widget.series : [];
4217
4292
  for (const item of series2) {
4218
- if (!isRecord5(item)) continue;
4293
+ if (!isRecord6(item)) continue;
4219
4294
  if (typeof item.id === "string" && !declared.seriesKeys.has(item.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${item.id}`);
4220
4295
  validateSeriesDimensions(item, declared.dimensionKeys, contributionPath, errors);
4221
4296
  }
4222
4297
  }
4223
4298
  function validateSeriesDimensions(series2, known, path, errors) {
4224
4299
  if (!Array.isArray(series2.points)) return;
4225
- for (const point of series2.points) if (isRecord5(point)) validateRuntimeDimensions(point.dimensions, known, path, errors);
4300
+ for (const point of series2.points) if (isRecord6(point)) validateRuntimeDimensions(point.dimensions, known, path, errors);
4226
4301
  }
4227
4302
  function validateRuntimeDimensions(value, known, path, errors) {
4228
4303
  if (!Array.isArray(value)) return;
4229
4304
  for (const dimension2 of value) {
4230
- if (isRecord5(dimension2) && typeof dimension2.key === "string" && !known.has(dimension2.key)) {
4305
+ if (isRecord6(dimension2) && typeof dimension2.key === "string" && !known.has(dimension2.key)) {
4231
4306
  errors.push(`${path}.dimensions is missing runtime dimension ${dimension2.key}`);
4232
4307
  }
4233
4308
  }
@@ -4329,10 +4404,10 @@ function validateNotifications(value, path, errors, requireOne) {
4329
4404
  });
4330
4405
  }
4331
4406
  function requiresContent(widget) {
4332
- return isRecord5(widget.state) && (widget.state.status === "ready" || widget.state.status === "stale");
4407
+ return isRecord6(widget.state) && (widget.state.status === "ready" || widget.state.status === "stale");
4333
4408
  }
4334
4409
  function recordValue(value, path, errors) {
4335
- if (!isRecord5(value)) {
4410
+ if (!isRecord6(value)) {
4336
4411
  errors.push(`${path} must be an object`);
4337
4412
  return false;
4338
4413
  }
@@ -4397,7 +4472,7 @@ function isIsoTimestamp(value) {
4397
4472
  function isPositiveNumber(value) {
4398
4473
  return typeof value === "number" && value > 0;
4399
4474
  }
4400
- function isRecord5(value) {
4475
+ function isRecord6(value) {
4401
4476
  return typeof value === "object" && value !== null && !Array.isArray(value);
4402
4477
  }
4403
4478
 
@@ -4939,19 +5014,19 @@ var TOPOLO_WIDGET_OUTPUT_SCHEMA = Object.freeze({
4939
5014
  additionalProperties: false
4940
5015
  });
4941
5016
  function isTopoloWidgetOutputSchema(schema) {
4942
- if (!isRecord6(schema) || !isRecord6(schema.properties)) return false;
5017
+ if (!isRecord7(schema) || !isRecord7(schema.properties)) return false;
4943
5018
  const properties = schema.properties;
4944
- const widgetItemSchema = isRecord6(properties.widgets) && isRecord6(properties.widgets.items) ? properties.widgets.items : void 0;
4945
- const widgetProperties = widgetItemSchema && isRecord6(widgetItemSchema.properties) ? widgetItemSchema.properties : void 0;
4946
- const contributionSchema = widgetProperties && isRecord6(widgetProperties.contribution) ? widgetProperties.contribution : void 0;
4947
- const contractVersion = isRecord6(properties.contractVersion) ? properties.contractVersion.const : void 0;
4948
- 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");
5019
+ const widgetItemSchema = isRecord7(properties.widgets) && isRecord7(properties.widgets.items) ? properties.widgets.items : void 0;
5020
+ const widgetProperties = widgetItemSchema && isRecord7(widgetItemSchema.properties) ? widgetItemSchema.properties : void 0;
5021
+ const contributionSchema = widgetProperties && isRecord7(widgetProperties.contribution) ? widgetProperties.contribution : void 0;
5022
+ const contractVersion = isRecord7(properties.contractVersion) ? properties.contractVersion.const : void 0;
5023
+ 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");
4949
5024
  }
4950
- function isRecord6(value) {
5025
+ function isRecord7(value) {
4951
5026
  return typeof value === "object" && value !== null && !Array.isArray(value);
4952
5027
  }
4953
5028
  function hasRequired(schema, field) {
4954
- return isRecord6(schema) && Array.isArray(schema.required) && schema.required.includes(field);
5029
+ return isRecord7(schema) && Array.isArray(schema.required) && schema.required.includes(field);
4955
5030
  }
4956
5031
 
4957
5032
  // src/index.ts