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