@topolo/sdk 0.21.2 → 0.21.4

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