@topolo/sdk 0.9.3 → 0.9.5

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
@@ -132,7 +132,9 @@ var APPLICATIONS = {
132
132
  "domains": [
133
133
  "admin.topolo.app"
134
134
  ],
135
- "routes": [],
135
+ "routes": [
136
+ "admin.topolo.app"
137
+ ],
136
138
  "appId": null
137
139
  },
138
140
  {
@@ -180,7 +182,7 @@ var APPLICATIONS = {
180
182
  "appDir": "TopoloAuth",
181
183
  "name": "topolo-auth",
182
184
  "packageName": "topolo-auth",
183
- "productionUrl": "https://auth.topolo.app/login",
185
+ "productionUrl": "https://auth.topolo.app",
184
186
  "services": [
185
187
  "auth"
186
188
  ],
@@ -216,7 +218,9 @@ var APPLICATIONS = {
216
218
  "domains": [
217
219
  "backup.topolo.app"
218
220
  ],
219
- "routes": [],
221
+ "routes": [
222
+ "backup.topolo.app"
223
+ ],
220
224
  "appId": "backup"
221
225
  }
222
226
  ]
@@ -268,9 +272,7 @@ var APPLICATIONS = {
268
272
  "domains": [
269
273
  "brand.topolo.app"
270
274
  ],
271
- "routes": [
272
- "brand.topolo.app"
273
- ],
275
+ "routes": [],
274
276
  "appId": "brand"
275
277
  },
276
278
  {
@@ -322,7 +324,9 @@ var APPLICATIONS = {
322
324
  "domains": [
323
325
  "bytes.topolo.app"
324
326
  ],
325
- "routes": [],
327
+ "routes": [
328
+ "bytes.topolo.app"
329
+ ],
326
330
  "appId": "bytes"
327
331
  },
328
332
  {
@@ -352,7 +356,9 @@ var APPLICATIONS = {
352
356
  "domains": [
353
357
  "calendar.topolo.app"
354
358
  ],
355
- "routes": [],
359
+ "routes": [
360
+ "calendar.topolo.app"
361
+ ],
356
362
  "appId": "calendar"
357
363
  },
358
364
  {
@@ -382,9 +388,7 @@ var APPLICATIONS = {
382
388
  "domains": [
383
389
  "campaigns.topolo.app"
384
390
  ],
385
- "routes": [
386
- "campaigns.topolo.app"
387
- ],
391
+ "routes": [],
388
392
  "appId": "campaigns"
389
393
  },
390
394
  {
@@ -474,7 +478,9 @@ var APPLICATIONS = {
474
478
  "domains": [
475
479
  "guest.topolo.app"
476
480
  ],
477
- "routes": [],
481
+ "routes": [
482
+ "guest.topolo.app"
483
+ ],
478
484
  "appId": null
479
485
  },
480
486
  {
@@ -484,7 +490,9 @@ var APPLICATIONS = {
484
490
  "domains": [
485
491
  "commerce.topolo.app"
486
492
  ],
487
- "routes": [],
493
+ "routes": [
494
+ "commerce.topolo.app"
495
+ ],
488
496
  "appId": "commerce"
489
497
  },
490
498
  {
@@ -494,7 +502,9 @@ var APPLICATIONS = {
494
502
  "domains": [
495
503
  "station-commerce.topolo.app"
496
504
  ],
497
- "routes": [],
505
+ "routes": [
506
+ "station-commerce.topolo.app"
507
+ ],
498
508
  "appId": null
499
509
  }
500
510
  ]
@@ -672,7 +682,9 @@ var APPLICATIONS = {
672
682
  "domains": [
673
683
  "docs.topolo.app"
674
684
  ],
675
- "routes": [],
685
+ "routes": [
686
+ "docs.topolo.app"
687
+ ],
676
688
  "appId": null
677
689
  },
678
690
  {
@@ -702,7 +714,9 @@ var APPLICATIONS = {
702
714
  "domains": [
703
715
  "feed-analytics-api.topolo.app"
704
716
  ],
705
- "routes": [],
717
+ "routes": [
718
+ "feed-analytics-api.topolo.app"
719
+ ],
706
720
  "appId": null
707
721
  },
708
722
  {
@@ -712,7 +726,9 @@ var APPLICATIONS = {
712
726
  "domains": [
713
727
  "feed-api.topolo.app"
714
728
  ],
715
- "routes": [],
729
+ "routes": [
730
+ "feed-api.topolo.app"
731
+ ],
716
732
  "appId": null
717
733
  },
718
734
  {
@@ -722,7 +738,9 @@ var APPLICATIONS = {
722
738
  "domains": [
723
739
  "feed.topolo.app"
724
740
  ],
725
- "routes": [],
741
+ "routes": [
742
+ "feed.topolo.app"
743
+ ],
726
744
  "appId": "feed"
727
745
  }
728
746
  ]
@@ -834,7 +852,9 @@ var APPLICATIONS = {
834
852
  "domains": [
835
853
  "home.topolo.app"
836
854
  ],
837
- "routes": [],
855
+ "routes": [
856
+ "home.topolo.app"
857
+ ],
838
858
  "appId": "home"
839
859
  },
840
860
  {
@@ -924,7 +944,9 @@ var APPLICATIONS = {
924
944
  "domains": [
925
945
  "learn.topolo.app"
926
946
  ],
927
- "routes": [],
947
+ "routes": [
948
+ "learn.topolo.app"
949
+ ],
928
950
  "appId": "learn"
929
951
  },
930
952
  {
@@ -1016,7 +1038,9 @@ var APPLICATIONS = {
1016
1038
  "domains": [
1017
1039
  "mdm.topolo.app"
1018
1040
  ],
1019
- "routes": [],
1041
+ "routes": [
1042
+ "mdm.topolo.app"
1043
+ ],
1020
1044
  "appId": "mdm"
1021
1045
  }
1022
1046
  ]
@@ -1038,7 +1062,9 @@ var APPLICATIONS = {
1038
1062
  "domains": [
1039
1063
  "messages.topolo.app"
1040
1064
  ],
1041
- "routes": [],
1065
+ "routes": [
1066
+ "messages.topolo.app"
1067
+ ],
1042
1068
  "appId": "messages"
1043
1069
  },
1044
1070
  {
@@ -1072,11 +1098,13 @@ var APPLICATIONS = {
1072
1098
  {
1073
1099
  "name": "topolo-nexus-dashboard",
1074
1100
  "kind": "worker",
1075
- "apiUrl": null,
1101
+ "apiUrl": "https://nexus.topolo.app",
1076
1102
  "domains": [
1077
1103
  "nexus.topolo.app"
1078
1104
  ],
1079
- "routes": [],
1105
+ "routes": [
1106
+ "nexus.topolo.app"
1107
+ ],
1080
1108
  "appId": null
1081
1109
  },
1082
1110
  {
@@ -1204,7 +1232,9 @@ var APPLICATIONS = {
1204
1232
  "domains": [
1205
1233
  "p2p.topolo.app"
1206
1234
  ],
1207
- "routes": [],
1235
+ "routes": [
1236
+ "p2p.topolo.app"
1237
+ ],
1208
1238
  "appId": "p2p"
1209
1239
  },
1210
1240
  {
@@ -1401,7 +1431,9 @@ var APPLICATIONS = {
1401
1431
  "domains": [
1402
1432
  "studio.topolo.app"
1403
1433
  ],
1404
- "routes": [],
1434
+ "routes": [
1435
+ "studio.topolo.app"
1436
+ ],
1405
1437
  "appId": "social_studio"
1406
1438
  },
1407
1439
  {
@@ -1439,7 +1471,9 @@ var APPLICATIONS = {
1439
1471
  "domains": [
1440
1472
  "socialize.topolo.app"
1441
1473
  ],
1442
- "routes": [],
1474
+ "routes": [
1475
+ "socialize.topolo.app"
1476
+ ],
1443
1477
  "appId": "socialize"
1444
1478
  },
1445
1479
  {
@@ -1449,7 +1483,9 @@ var APPLICATIONS = {
1449
1483
  "domains": [
1450
1484
  "socialize-api.topolo.app"
1451
1485
  ],
1452
- "routes": [],
1486
+ "routes": [
1487
+ "socialize-api.topolo.app"
1488
+ ],
1453
1489
  "appId": null
1454
1490
  },
1455
1491
  {
@@ -1509,7 +1545,9 @@ var APPLICATIONS = {
1509
1545
  "domains": [
1510
1546
  "status.topolo.app"
1511
1547
  ],
1512
- "routes": [],
1548
+ "routes": [
1549
+ "status.topolo.app"
1550
+ ],
1513
1551
  "appId": "status"
1514
1552
  }
1515
1553
  ]
@@ -1625,7 +1663,9 @@ var APPLICATIONS = {
1625
1663
  "domains": [
1626
1664
  "voice.topolo.app"
1627
1665
  ],
1628
- "routes": [],
1666
+ "routes": [
1667
+ "voice.topolo.app"
1668
+ ],
1629
1669
  "appId": "voice"
1630
1670
  },
1631
1671
  {
@@ -1676,7 +1716,9 @@ var APPLICATIONS = {
1676
1716
  "domains": [
1677
1717
  "web.topolo.app"
1678
1718
  ],
1679
- "routes": [],
1719
+ "routes": [
1720
+ "web.topolo.app"
1721
+ ],
1680
1722
  "appId": "web"
1681
1723
  }
1682
1724
  ]
@@ -1755,7 +1797,7 @@ var APPLICATION_REQUIREMENTS = [
1755
1797
  "Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
1756
1798
  "Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
1757
1799
  "Keep mobile and desktop shell behavior aligned with the platform UI kit",
1758
- "Constrain app-owned workspace wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
1800
+ "Constrain the app workspace-view layout wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
1759
1801
  ]
1760
1802
  },
1761
1803
  {
@@ -3386,8 +3428,9 @@ async function digestTopoloActionCatalog(actions, appId) {
3386
3428
  return {
3387
3429
  actionId: action.actionId || `${appId}.${action.name}`,
3388
3430
  authorizationMode,
3389
- ...authorizationMode === "service" ? { allowedServices } : {},
3431
+ allowedServices: authorizationMode === "service" || authorizationMode === "runtime" ? allowedServices : [],
3390
3432
  inputSchema: action.inputSchema,
3433
+ agentContract: action.agentContract ?? {},
3391
3434
  method: action.method,
3392
3435
  outputSchema: action.outputSchema,
3393
3436
  path: action.path,
@@ -3402,6 +3445,285 @@ async function digestTopoloActionCatalog(actions, appId) {
3402
3445
  return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
3403
3446
  }
3404
3447
 
3448
+ // src/action-contract-enrichment.mjs
3449
+ var OPERATION_TOKENS = /* @__PURE__ */ new Set([
3450
+ "add",
3451
+ "archive",
3452
+ "build",
3453
+ "call",
3454
+ "cancel",
3455
+ "claim",
3456
+ "complete",
3457
+ "create",
3458
+ "delete",
3459
+ "disable",
3460
+ "enable",
3461
+ "execute",
3462
+ "export",
3463
+ "get",
3464
+ "import",
3465
+ "ingest",
3466
+ "invite",
3467
+ "list",
3468
+ "move",
3469
+ "preview",
3470
+ "publish",
3471
+ "read",
3472
+ "refresh",
3473
+ "remove",
3474
+ "restore",
3475
+ "retry",
3476
+ "review",
3477
+ "run",
3478
+ "scaffold",
3479
+ "search",
3480
+ "send",
3481
+ "set",
3482
+ "submit",
3483
+ "sync",
3484
+ "transfer",
3485
+ "unpublish",
3486
+ "update",
3487
+ "upload",
3488
+ "validate"
3489
+ ]);
3490
+ var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
3491
+ ["archive", "restore"],
3492
+ ["create", "delete"],
3493
+ ["disable", "enable"],
3494
+ ["enable", "disable"],
3495
+ ["publish", "unpublish"],
3496
+ ["remove", "add"],
3497
+ ["restore", "archive"],
3498
+ ["unpublish", "publish"]
3499
+ ]);
3500
+ function isRecord3(value) {
3501
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
3502
+ }
3503
+ function nonEmptyStrings(value) {
3504
+ return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.trim());
3505
+ }
3506
+ function slugify(value) {
3507
+ return String(value || "").trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3508
+ }
3509
+ function singularize(value) {
3510
+ if (value.endsWith("ies") && value.length > 3) return `${value.slice(0, -3)}y`;
3511
+ if (value.endsWith("sses")) return value.slice(0, -2);
3512
+ if (value.endsWith("s") && !value.endsWith("ss")) return value.slice(0, -1);
3513
+ return value || "resource";
3514
+ }
3515
+ function operationFor(action) {
3516
+ const tokens = String(action.name || "").split(/[._:-]+/).filter(Boolean);
3517
+ return [...tokens].reverse().find((token) => OPERATION_TOKENS.has(token)) || String(action.method || "GET").toLowerCase();
3518
+ }
3519
+ function resourceFor(action) {
3520
+ const nameTokens = String(action.name || "").split(/[._:-]+/).filter(Boolean);
3521
+ const named = nameTokens.find((token) => !OPERATION_TOKENS.has(token));
3522
+ if (named) return singularize(slugify(named).replaceAll("-", "_"));
3523
+ const pathToken = String(action.path || "").split("/").filter((token) => token && token !== "api" && !token.startsWith("{")).find((token) => !OPERATION_TOKENS.has(slugify(token)));
3524
+ return singularize(slugify(pathToken || "resource").replaceAll("-", "_"));
3525
+ }
3526
+ function isConcreteOutputSchema(schema) {
3527
+ if (!isRecord3(schema)) return false;
3528
+ if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
3529
+ if (schema.type === "array" && isRecord3(schema.items) && Object.keys(schema.items).length > 0) return true;
3530
+ if (isRecord3(schema.properties) && Object.keys(schema.properties).length > 0) return true;
3531
+ return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
3532
+ }
3533
+ function resourceSchema(resource) {
3534
+ return {
3535
+ type: "object",
3536
+ title: `${resource.replaceAll("_", " ")} record`,
3537
+ properties: {
3538
+ id: {
3539
+ type: "string",
3540
+ description: `Stable identifier when the ${resource.replaceAll("_", " ")} exposes one.`
3541
+ }
3542
+ },
3543
+ additionalProperties: true
3544
+ };
3545
+ }
3546
+ function derivedOutputSchema(action) {
3547
+ const resource = resourceFor(action);
3548
+ const operation = operationFor(action);
3549
+ const record = resourceSchema(resource);
3550
+ const records = { type: "array", items: record };
3551
+ const provenance = {
3552
+ "x-topolo-contract-source": "derived-action-metadata",
3553
+ "x-topolo-contract-precision": "structural"
3554
+ };
3555
+ if (operation === "list" || operation === "search") {
3556
+ return {
3557
+ title: `${action.title || action.name} response`,
3558
+ description: `Collection response returned by ${action.name}. The service may return the collection directly or in an envelope.`,
3559
+ oneOf: [
3560
+ records,
3561
+ {
3562
+ type: "object",
3563
+ properties: {
3564
+ data: { oneOf: [records, { type: "object", additionalProperties: true }] },
3565
+ items: records,
3566
+ [resource.endsWith("s") ? resource : `${resource}s`]: records,
3567
+ count: { type: "integer", minimum: 0 },
3568
+ nextCursor: { type: ["string", "null"] }
3569
+ },
3570
+ additionalProperties: true
3571
+ }
3572
+ ],
3573
+ ...provenance
3574
+ };
3575
+ }
3576
+ if (operation === "get" || operation === "read") {
3577
+ return {
3578
+ title: `${action.title || action.name} response`,
3579
+ description: `Resource response returned by ${action.name}. The service may return the resource directly or in an envelope.`,
3580
+ oneOf: [
3581
+ record,
3582
+ {
3583
+ type: "object",
3584
+ properties: {
3585
+ data: record,
3586
+ [resource]: record
3587
+ },
3588
+ additionalProperties: true
3589
+ }
3590
+ ],
3591
+ ...provenance
3592
+ };
3593
+ }
3594
+ return {
3595
+ type: "object",
3596
+ title: `${action.title || action.name} response`,
3597
+ description: `Operation result returned by ${action.name}.`,
3598
+ properties: {
3599
+ success: { type: "boolean" },
3600
+ id: { type: "string" },
3601
+ data: { oneOf: [record, { type: "object", additionalProperties: true }] },
3602
+ result: { type: "object", additionalProperties: true },
3603
+ message: { type: "string" }
3604
+ },
3605
+ additionalProperties: true,
3606
+ ...provenance
3607
+ };
3608
+ }
3609
+ function sampleForSchema(schema, depth = 0) {
3610
+ if (!isRecord3(schema) || depth > 5) return null;
3611
+ if (schema.const !== void 0) return schema.const;
3612
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
3613
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1);
3614
+ if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return sampleForSchema(schema.anyOf[0], depth + 1);
3615
+ if (schema.type === "array") return [];
3616
+ if (schema.type === "boolean") return false;
3617
+ if (schema.type === "integer" || schema.type === "number") return schema.minimum ?? 0;
3618
+ if (schema.type === "string") {
3619
+ if (schema.format === "email") return "agent@example.com";
3620
+ if (schema.format === "uri" || schema.format === "url") return "https://example.com";
3621
+ return schema.minLength > 1 ? "example".padEnd(schema.minLength, "x") : "example";
3622
+ }
3623
+ if (schema.type === "null") return null;
3624
+ if (schema.type === "object" || isRecord3(schema.properties)) {
3625
+ const properties = isRecord3(schema.properties) ? schema.properties : {};
3626
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
3627
+ const entries = Object.entries(properties).filter(([key]) => required.has(key)).map(([key, child]) => [key, sampleForSchema(child, depth + 1)]);
3628
+ return Object.fromEntries(entries);
3629
+ }
3630
+ return null;
3631
+ }
3632
+ function actionPrefix(name, operation) {
3633
+ const tokens = String(name || "").split(/[._:-]+/).filter(Boolean);
3634
+ const index = tokens.lastIndexOf(operation);
3635
+ if (index === -1) return tokens.join(".");
3636
+ return tokens.filter((_, tokenIndex) => tokenIndex !== index).join(".");
3637
+ }
3638
+ function findInverseActionId(action, allActions, appId) {
3639
+ const operation = operationFor(action);
3640
+ if (operation === "update" || operation === "set" || operation === "move") {
3641
+ return `${appId}.${action.name}`;
3642
+ }
3643
+ const inverse = INVERSE_OPERATIONS.get(operation);
3644
+ if (!inverse) return null;
3645
+ const prefix = actionPrefix(action.name, operation);
3646
+ const match = allActions.find((candidate) => operationFor(candidate) === inverse && actionPrefix(candidate.name, inverse) === prefix);
3647
+ return match ? `${appId}.${match.name}` : null;
3648
+ }
3649
+ function recoveryFor(action, allActions, appId) {
3650
+ const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
3651
+ if (readOnly) {
3652
+ return {
3653
+ mode: "not_applicable",
3654
+ instructions: "This action does not change service state."
3655
+ };
3656
+ }
3657
+ const actionId = findInverseActionId(action, allActions, appId);
3658
+ if (actionId) {
3659
+ return {
3660
+ mode: "compensating_action",
3661
+ actionId,
3662
+ requiresSnapshot: actionId === `${appId}.${action.name}`,
3663
+ instructions: actionId === `${appId}.${action.name}` ? "Capture the current resource before the call, then invoke this action with the captured values to restore it." : `Invoke ${actionId} with the affected resource identifier after confirming the rollback.`
3664
+ };
3665
+ }
3666
+ return {
3667
+ mode: "irreversible",
3668
+ instructions: "No safe automatic inverse is published. Confirm the change and retain any required export or snapshot before calling."
3669
+ };
3670
+ }
3671
+ function defaultErrors(action) {
3672
+ const errors = [
3673
+ { status: 401, code: "authentication_required", recovery: "Authenticate again or provide a valid Topolo API key." },
3674
+ { status: 403, code: "permission_denied", recovery: `Grant the credential permission ${action.permission || action.permissionName || "required by this action"} and the required resource binding.` },
3675
+ { status: 422, code: "invalid_input", recovery: "Correct the payload using inputSchema, then validate before retrying." }
3676
+ ];
3677
+ if (action.method !== "GET") {
3678
+ errors.push({ status: 409, code: "state_conflict", recovery: "Read the current resource state, reconcile the intended change, and retry only if still valid." });
3679
+ }
3680
+ return errors;
3681
+ }
3682
+ function enrichActionContract(manifest, action, allActions = manifest.actions || []) {
3683
+ const appId = String(manifest.appId || manifest.app_id || "").trim();
3684
+ const service = slugify(manifest.slug || appId.replace(/^app_/, "").replaceAll("_", "-") || manifest.name);
3685
+ const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
3686
+ const resource = resourceFor(action);
3687
+ const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
3688
+ const existing = isRecord3(action.agentContract) ? structuredClone(action.agentContract) : {};
3689
+ const recovery = isRecord3(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
3690
+ const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
3691
+ const inputExample = sampleForSchema(action.inputSchema);
3692
+ const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples : [{ input: inputExample ?? {}, expected: `A response matching the published outputSchema for ${action.name}.` }];
3693
+ const docsUrl = `https://docs.topolo.app/reference/actions/?service=${encodeURIComponent(service)}&action=${encodeURIComponent(action.name)}`;
3694
+ const existingDocsUrl = typeof existing.docsUrl === "string" && existing.docsUrl.trim() ? existing.docsUrl.trim() : void 0;
3695
+ return {
3696
+ outputSchema,
3697
+ agentContract: {
3698
+ ...existing,
3699
+ docsUrl,
3700
+ ...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
3701
+ resource: isRecord3(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
3702
+ 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.`],
3703
+ errors: Array.isArray(existing.errors) && existing.errors.length > 0 ? existing.errors : defaultErrors(action),
3704
+ 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"}.`],
3705
+ examples,
3706
+ recovery,
3707
+ ...rollbackActionId ? { rollbackActionId } : {},
3708
+ contractSource: Object.hasOwn(outputSchema, "x-topolo-contract-source") ? "derived-action-metadata" : "application-declared"
3709
+ }
3710
+ };
3711
+ }
3712
+ function actionContractCompleteness(action) {
3713
+ const contract = action?.agentContract;
3714
+ return {
3715
+ inputSchema: isRecord3(action?.inputSchema),
3716
+ outputSchema: isConcreteOutputSchema(action?.outputSchema),
3717
+ effects: nonEmptyStrings(contract?.effects),
3718
+ errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
3719
+ docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
3720
+ examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
3721
+ verification: nonEmptyStrings(contract?.verify),
3722
+ recovery: isRecord3(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
3723
+ agentContract: isRecord3(contract) && isRecord3(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
3724
+ };
3725
+ }
3726
+
3405
3727
  // src/modules/crm.ts
3406
3728
  var CrmModule = class {
3407
3729
  constructor(client) {
@@ -3803,7 +4125,7 @@ function createTopoloWidgetError(error) {
3803
4125
  }
3804
4126
  function validateTopoloWidgetResponse(response) {
3805
4127
  const errors = [];
3806
- if (!isRecord3(response)) {
4128
+ if (!isRecord4(response)) {
3807
4129
  return { ok: false, errors: ["response must be an object"] };
3808
4130
  }
3809
4131
  if (typeof response.success !== "boolean") {
@@ -3822,7 +4144,7 @@ function validateTopoloWidgetResponse(response) {
3822
4144
  return { ok: errors.length === 0, errors };
3823
4145
  }
3824
4146
  function validateWidget(widget, path, errors) {
3825
- if (!isRecord3(widget)) {
4147
+ if (!isRecord4(widget)) {
3826
4148
  errors.push(`${path} must be an object`);
3827
4149
  return;
3828
4150
  }
@@ -3849,7 +4171,7 @@ function validateWidget(widget, path, errors) {
3849
4171
  case "chart":
3850
4172
  validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
3851
4173
  validateNonEmptyString(widget.title, `${path}.title`, errors);
3852
- if (!isRecord3(widget.data)) {
4174
+ if (!isRecord4(widget.data)) {
3853
4175
  errors.push(`${path}.data must be an object`);
3854
4176
  } else {
3855
4177
  validateArray2(widget.data.labels, `${path}.data.labels`, errors);
@@ -3865,7 +4187,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
3865
4187
  errors.push(`${path} must be an object`);
3866
4188
  return;
3867
4189
  }
3868
- if (!isRecord3(snapshot)) {
4190
+ if (!isRecord4(snapshot)) {
3869
4191
  errors.push(`${path} must be an object`);
3870
4192
  return;
3871
4193
  }
@@ -3885,7 +4207,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
3885
4207
  }
3886
4208
  snapshot.availableMetrics.forEach((metric, index) => {
3887
4209
  const metricPath = `${path}.availableMetrics[${index}]`;
3888
- if (!isRecord3(metric)) {
4210
+ if (!isRecord4(metric)) {
3889
4211
  errors.push(`${metricPath} must be an object`);
3890
4212
  return;
3891
4213
  }
@@ -3906,7 +4228,7 @@ function validateArray2(value, path, errors) {
3906
4228
  errors.push(`${path} must be an array`);
3907
4229
  }
3908
4230
  }
3909
- function isRecord3(value) {
4231
+ function isRecord4(value) {
3910
4232
  return typeof value === "object" && value !== null && !Array.isArray(value);
3911
4233
  }
3912
4234
  function toIsoTimestamp(input) {
@@ -3943,6 +4265,7 @@ export {
3943
4265
  TopoloOAuth,
3944
4266
  TopoloPermissionError,
3945
4267
  TopoloSdkError,
4268
+ actionContractCompleteness,
3946
4269
  applicationRequirementScopes,
3947
4270
  auditAllApplicationRequirements,
3948
4271
  auditApplicationRequirements,
@@ -3955,6 +4278,7 @@ export {
3955
4278
  defineTopoloRecentActivityWidget,
3956
4279
  defineTopoloStatsWidget,
3957
4280
  digestTopoloActionCatalog,
4281
+ enrichActionContract,
3958
4282
  requirementsForApplication,
3959
4283
  resolveCatalogServiceUrl,
3960
4284
  resolveServiceUrl,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@topolo/sdk",
3
- "version": "0.9.3",
4
- "topoloPublishedVersion": "0.9.3",
3
+ "version": "0.9.5",
4
+ "topoloPublishedVersion": "0.9.5",
5
5
  "description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -23,13 +23,16 @@
23
23
  "dist"
24
24
  ],
25
25
  "scripts": {
26
- "check:generated": "git diff --exit-code -- src/services.generated.ts src/applications.generated.ts",
26
+ "generate:services": "node scripts/generate-services.mjs",
27
+ "check:generated": "node scripts/check-generated.mjs",
28
+ "prebuild": "node scripts/generate-services.mjs",
27
29
  "build": "tsup src/index.ts --format esm,cjs --dts --clean && tsup src/node/credential-store.ts --format esm,cjs --dts --out-dir dist/node",
28
30
  "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
29
- "typecheck": "tsc --noEmit",
31
+ "typecheck": "node scripts/generate-services.mjs && tsc --noEmit",
30
32
  "test": "vitest run",
31
33
  "test:watch": "vitest",
32
- "prepublishOnly": "npm run build",
34
+ "prepublishOnly": "npm run build && npm run verify:package",
35
+ "verify:package": "node ../../tooling/verify-npm-package.mjs .",
33
36
  "coverage": "vitest run --coverage"
34
37
  },
35
38
  "devDependencies": {