@vess-id/vess 0.2.0-alpha.3 → 0.2.0-alpha.31

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.
Files changed (49) hide show
  1. package/dist/adapter/mcp/http-transport.d.ts +9 -3
  2. package/dist/adapter/mcp/http-transport.d.ts.map +1 -1
  3. package/dist/adapter/mcp/http-transport.js +85 -15
  4. package/dist/adapter/mcp/http-transport.js.map +1 -1
  5. package/dist/adapter/mcp/mcp-adapter.d.ts +2 -0
  6. package/dist/adapter/mcp/mcp-adapter.d.ts.map +1 -1
  7. package/dist/adapter/mcp/mcp-adapter.js +9 -0
  8. package/dist/adapter/mcp/mcp-adapter.js.map +1 -1
  9. package/dist/adapter/mcp/mcp-server.factory.d.ts.map +1 -1
  10. package/dist/adapter/mcp/mcp-server.factory.js +59 -20
  11. package/dist/adapter/mcp/mcp-server.factory.js.map +1 -1
  12. package/dist/audit/audit-logger.d.ts +1 -1
  13. package/dist/audit/audit-logger.d.ts.map +1 -1
  14. package/dist/cli/index.js +9 -1
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/cli/version.d.ts +2 -0
  17. package/dist/cli/version.d.ts.map +1 -0
  18. package/dist/cli/version.js +10 -0
  19. package/dist/cli/version.js.map +1 -0
  20. package/dist/config/constants.d.ts +8 -0
  21. package/dist/config/constants.d.ts.map +1 -1
  22. package/dist/config/constants.js +9 -1
  23. package/dist/config/constants.js.map +1 -1
  24. package/dist/config/version.d.ts +3 -0
  25. package/dist/config/version.d.ts.map +1 -0
  26. package/dist/config/version.js +80 -0
  27. package/dist/config/version.js.map +1 -0
  28. package/dist/core/execution-engine.d.ts +15 -1
  29. package/dist/core/execution-engine.d.ts.map +1 -1
  30. package/dist/core/execution-engine.js +207 -36
  31. package/dist/core/execution-engine.js.map +1 -1
  32. package/dist/core/runtime.d.ts.map +1 -1
  33. package/dist/core/runtime.js +4 -0
  34. package/dist/core/runtime.js.map +1 -1
  35. package/dist/core/types.d.ts +8 -0
  36. package/dist/core/types.d.ts.map +1 -1
  37. package/dist/gateway/gateway-client.d.ts +31 -9
  38. package/dist/gateway/gateway-client.d.ts.map +1 -1
  39. package/dist/gateway/gateway-client.js +74 -30
  40. package/dist/gateway/gateway-client.js.map +1 -1
  41. package/dist/utils/credential-errors.d.ts +41 -0
  42. package/dist/utils/credential-errors.d.ts.map +1 -1
  43. package/dist/utils/credential-errors.js +39 -0
  44. package/dist/utils/credential-errors.js.map +1 -1
  45. package/dist/wallet/wallet.d.ts +6 -0
  46. package/dist/wallet/wallet.d.ts.map +1 -1
  47. package/dist/wallet/wallet.js +9 -0
  48. package/dist/wallet/wallet.js.map +1 -1
  49. package/package.json +14 -14
@@ -0,0 +1,3 @@
1
+ export declare const CLI_VERSION: string;
2
+ export declare const SDK_VERSION: string;
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/config/version.ts"],"names":[],"mappings":"AAyCA,eAAO,MAAM,WAAW,QAAmB,CAAA;AAC3C,eAAO,MAAM,WAAW,QAAmB,CAAA"}
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SDK_VERSION = exports.CLI_VERSION = void 0;
37
+ const path = __importStar(require("node:path"));
38
+ const fs = __importStar(require("node:fs"));
39
+ const node_module_1 = require("node:module");
40
+ /**
41
+ * Read the agentd CLI version from package.json at runtime.
42
+ * Works in both dev (src/config) and production (dist/config).
43
+ */
44
+ function readCliVersion() {
45
+ try {
46
+ const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');
47
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
48
+ return pkg.version ?? '0.0.0-unknown';
49
+ }
50
+ catch {
51
+ return '0.0.0-unknown';
52
+ }
53
+ }
54
+ /**
55
+ * Read the SDK version from @vess-id/ai-identity package.json.
56
+ * Tries multiple resolution strategies for compatibility with
57
+ * jest (moduleNameMapper), workspace links, and production installs.
58
+ */
59
+ function readSdkVersion() {
60
+ // Strategy 1: createRequire (works in production npm installs)
61
+ try {
62
+ const req = (0, node_module_1.createRequire)(__filename);
63
+ const pkg = req('@vess-id/ai-identity/package.json');
64
+ if (pkg.version)
65
+ return pkg.version;
66
+ }
67
+ catch { /* fall through */ }
68
+ // Strategy 2: relative path from monorepo (works in dev and jest)
69
+ try {
70
+ const pkgPath = path.resolve(__dirname, '..', '..', '..', 'sdk', 'package.json');
71
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
72
+ if (pkg.version)
73
+ return pkg.version;
74
+ }
75
+ catch { /* fall through */ }
76
+ return '0.0.0-unknown';
77
+ }
78
+ exports.CLI_VERSION = readCliVersion();
79
+ exports.SDK_VERSION = readSdkVersion();
80
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/config/version.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAiC;AACjC,4CAA6B;AAC7B,6CAA2C;AAE3C;;;GAGG;AACH,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAA;QACnE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;QACzD,OAAO,GAAG,CAAC,OAAO,IAAI,eAAe,CAAA;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,CAAA;IACxB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc;IACrB,+DAA+D;IAC/D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,2BAAa,EAAC,UAAU,CAAC,CAAA;QACrC,MAAM,GAAG,GAAG,GAAG,CAAC,mCAAmC,CAAwB,CAAA;QAC3E,IAAI,GAAG,CAAC,OAAO;YAAE,OAAO,GAAG,CAAC,OAAO,CAAA;IACrC,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAE9B,kEAAkE;IAClE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,CAAA;QAChF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;QACzD,IAAI,GAAG,CAAC,OAAO;YAAE,OAAO,GAAG,CAAC,OAAO,CAAA;IACrC,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAE9B,OAAO,eAAe,CAAA;AACxB,CAAC;AAEY,QAAA,WAAW,GAAG,cAAc,EAAE,CAAA;AAC9B,QAAA,WAAW,GAAG,cAAc,EAAE,CAAA"}
@@ -77,10 +77,24 @@ export declare class ExecutionEngine {
77
77
  */
78
78
  private handlePendingApproval;
79
79
  /**
80
- * Create an OOB approval request for high/critical risk actions.
80
+ * Create an OOB (out-of-band) approval request.
81
81
  * Returns a URL for the user to approve in their browser.
82
+ *
83
+ * Called in two contexts:
84
+ * - ensureVC Step 4: for high/critical risk actions that require browser approval.
85
+ * - tryOOBFallbackAfterRetryExhaustion: for ANY risk level when a VC expires
86
+ * mid-execution and retry is exhausted. In this case OOB is used as a fallback
87
+ * because the normal in-band flow cannot recover an expired credential — the
88
+ * user must re-authorize via browser regardless of risk level.
82
89
  */
83
90
  private createOOBApprovalRequest;
91
+ /**
92
+ * Revoke the stale credential and attempt to create an OOB approval request
93
+ * as a fallback after retry exhaustion. Returns an ExecuteResult with
94
+ * waitingForApproval on success, or null if OOB creation fails (caller falls
95
+ * through to the original error).
96
+ */
97
+ private tryOOBFallbackAfterRetryExhaustion;
84
98
  /**
85
99
  * Create an in-band approval response for low/medium risk actions.
86
100
  * Generates an approval token for the MCP client to present back.
@@ -1 +1 @@
1
- {"version":3,"file":"execution-engine.d.ts","sourceRoot":"","sources":["../../src/core/execution-engine.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAuC,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAuB,MAAM,2BAA2B,CAAA;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAKjE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAA0E,MAAM,SAAS,CAAA;AAElK,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAmCrD,qBAAa,eAAe;IASxB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAfjC,OAAO,CAAC,kBAAkB,CAAsB;IAChD,OAAO,CAAC,UAAU,CAAC,CAAmC;IACtD,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAS;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAO;gBAG5B,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,aAAa,EAAE,mBAAmB,EAClC,OAAO,EAAE,gBAAgB,EACzB,YAAY,EAAE,oBAAoB,EAClC,aAAa,CAAC,EAAE,aAAa,YAAA;IAGhD;;OAEG;IACH,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIzC;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAI5D;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA6B7B;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAkF9D;;;;;;;;OAQG;YACW,QAAQ;YA8GR,YAAY;IAuC1B;;;OAGG;YACW,kBAAkB;IA4IhC;;;;;;;OAOG;YACW,mBAAmB;YA2GnB,iBAAiB;YAiDjB,uBAAuB;IAwFrC;;OAEG;YACW,qBAAqB;IA8HnC;;;OAGG;YACW,wBAAwB;IAyCtC;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAgDpC;;;;OAIG;IACG,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAmH7D,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC;IAWpD;;;OAGG;YACW,iBAAiB;IA8B/B,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,eAAe;YAUT,sBAAsB;IA0JpC,OAAO,CAAC,eAAe;IASvB;;;;;OAKG;IACH,OAAO,CAAC,0BAA0B;IAiBlC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,eAAe;IAavB,iHAAiH;YACnG,cAAc;YA4Bd,sBAAsB;IAsCpC,OAAO,CAAC,QAAQ;CAqCjB"}
1
+ {"version":3,"file":"execution-engine.d.ts","sourceRoot":"","sources":["../../src/core/execution-engine.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAuC,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAuB,MAAM,2BAA2B,CAAA;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAKjE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAA0E,MAAM,SAAS,CAAA;AAElK,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAoCrD,qBAAa,eAAe;IASxB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAfjC,OAAO,CAAC,kBAAkB,CAAsB;IAChD,OAAO,CAAC,UAAU,CAAC,CAAmC;IACtD,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAS;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAO;gBAG5B,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,aAAa,EAAE,mBAAmB,EAClC,OAAO,EAAE,gBAAgB,EACzB,YAAY,EAAE,oBAAoB,EAClC,aAAa,CAAC,EAAE,aAAa,YAAA;IAGhD;;OAEG;IACH,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIzC;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAI5D;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA6B7B;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAkF9D;;;;;;;;OAQG;YACW,QAAQ;YA2HR,YAAY;IAuC1B;;;OAGG;YACW,kBAAkB;IAwJhC;;;;;;;OAOG;YACW,mBAAmB;YA2GnB,iBAAiB;YAiDjB,uBAAuB;IAqIrC;;OAEG;YACW,qBAAqB;IAmLnC;;;;;;;;;;OAUG;YACW,wBAAwB;IA+CtC;;;;;OAKG;YACW,kCAAkC;IAqChD;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAgDpC;;;;OAIG;IACG,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAmH7D,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC;IAWpD;;;OAGG;YACW,iBAAiB;IA8B/B,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,eAAe;YAUT,sBAAsB;IA0JpC,OAAO,CAAC,eAAe;IASvB;;;;;OAKG;IACH,OAAO,CAAC,0BAA0B;IA0BlC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,eAAe;IAavB,iHAAiH;YACnG,cAAc;YA4Bd,sBAAsB;IAsCpC,OAAO,CAAC,QAAQ;CAqCjB"}
@@ -63,8 +63,9 @@ const executor_registry_1 = require("../executor/executor-registry");
63
63
  const env_resolver_1 = require("../env/env-resolver");
64
64
  /** Policy cache staleness TTL (spec §7.6: 30 minutes) */
65
65
  const POLICY_SYNC_TTL_MS = 30 * 60 * 1000;
66
- /** OOB approval request expiration (seconds) */
67
- const OOB_APPROVAL_EXPIRES_IN_SECONDS = 300;
66
+ /** Fallback OOB approval expiration (seconds) when the API doesn't return a value.
67
+ * Kept short (5 min) so the agent doesn't wait on a possibly-expired request. */
68
+ const OOB_APPROVAL_DEFAULT_EXPIRES_IN_SECONDS = 300;
68
69
  /** Default TTL for OOB-approved credentials (10 minutes) */
69
70
  const OOB_CREDENTIAL_DEFAULT_TTL_MS = 10 * 60 * 1000;
70
71
  class ExecutionEngine {
@@ -243,23 +244,39 @@ class ExecutionEngine {
243
244
  return { credential: verified, decisionSource: 'cached_vc' };
244
245
  }
245
246
  }
246
- // Step 2: Check Gateway for approved requests
247
+ // Step 2: Check Gateway for approved requests, then claim VC
247
248
  try {
248
249
  approvedRequests = await this.gatewayClient.findApprovedRequests(this.context.agentDid);
249
- const matching = approvedRequests.find(r => r.actions?.includes(action) && r.credential);
250
- if (matching && matching.credential) {
251
- const stored = this.wallet.storeCredential({
252
- holderDid: this.context.agentDid,
253
- projectId: this.context.projectId,
254
- credentialJwt: matching.credential.jwt,
255
- actions: matching.actions,
256
- provider,
257
- expiresAt: matching.credential.expiresAt,
258
- normalizedResourceKey: resourceInfo?.normalizedResource,
259
- resourceFingerprint: resourceInfo?.fingerprint,
260
- resources: (0, wallet_1.validateResources)(matching.resources),
261
- });
262
- return { credential: stored, decisionSource: 'cached_vc' };
250
+ const matching = approvedRequests.find(r => r.actions?.includes(action));
251
+ if (matching) {
252
+ try {
253
+ const claimed = await this.gatewayClient.claimVC(matching.id, this.context.agentDid);
254
+ const stored = this.wallet.storeCredential({
255
+ holderDid: this.context.agentDid,
256
+ projectId: this.context.projectId,
257
+ credentialJwt: claimed.credential.jwt,
258
+ actions: claimed.actions,
259
+ provider,
260
+ expiresAt: claimed.credential.expiresAt,
261
+ normalizedResourceKey: resourceInfo?.normalizedResource,
262
+ resourceFingerprint: resourceInfo?.fingerprint,
263
+ resources: (0, wallet_1.validateResources)(claimed.resources),
264
+ });
265
+ return { credential: stored, decisionSource: 'claimed_from_approval' };
266
+ }
267
+ catch (claimErr) {
268
+ const msg = claimErr instanceof Error ? claimErr.message : String(claimErr);
269
+ if (msg === 'CLAIM_CONSUMED') {
270
+ process.stderr.write('[vess] claimVC: request already consumed, falling through to step 3\n');
271
+ }
272
+ else if (msg === 'CLAIM_EXPIRED') {
273
+ process.stderr.write('[vess] claimVC: request expired, falling through to step 3\n');
274
+ }
275
+ else {
276
+ process.stderr.write(`[vess] claimVC failed: ${msg}, falling through to step 3\n`);
277
+ }
278
+ // Fall through to Step 3
279
+ }
263
280
  }
264
281
  }
265
282
  catch {
@@ -384,6 +401,16 @@ class ExecutionEngine {
384
401
  return { success: false, error: `VC reissuance denied: ${newVCResult.reason}` };
385
402
  }
386
403
  }
404
+ // After retry exhaustion, if still credential-invalid, fall back to OOB approval
405
+ // so the user gets an actionable re-authorization URL instead of a raw error.
406
+ if ((0, credential_errors_1.isCredentialInvalidError)(authResult.reason) && retryCount >= 1) {
407
+ const resourceInfoForOOB = normalizedResource && resourceFingerprint
408
+ ? { normalizedResource, fingerprint: resourceFingerprint }
409
+ : undefined;
410
+ const oobFallback = await this.tryOOBFallbackAfterRetryExhaustion(action, provider, vcResult.credential.id, params, resourceInfoForOOB, 'local');
411
+ if (oobFallback)
412
+ return oobFallback;
413
+ }
387
414
  this.logAudit({
388
415
  action, provider,
389
416
  decision: 'deny',
@@ -615,12 +642,45 @@ class ExecutionEngine {
615
642
  vpChallenge: nonce,
616
643
  vpDomain: this.context.gatewayUrl,
617
644
  });
645
+ // Phase 2c-B — the api signaled a revoked/expired OAuth token. Do not
646
+ // retry: the VC is still valid locally but the upstream SaaS token is
647
+ // gone and won't return until the user clicks the authUrl. Surface the
648
+ // reauth payload so the CLI can render it.
649
+ //
650
+ // Scope note: reauth only originates here (the gateway path) because
651
+ // local actions (`os.*`) don't touch SaaS OAuth tokens. If a future
652
+ // local executor ever starts proxying SaaS calls, mirror this branch
653
+ // in `executeLocalWithVC` with `enforcementType: 'local'`.
654
+ if (!result.success && result.errorCode === ai_identity_1.GATEWAY_ERROR_CODE.REAUTH_REQUIRED && result.reauthRequired) {
655
+ this.logAudit({
656
+ action, provider,
657
+ decision: 'deny',
658
+ enforcementType: 'gateway',
659
+ decisionSource: 'gateway_execution',
660
+ decisionReason: 'oauth_token_revoked',
661
+ executionType: 'gateway',
662
+ credentialId: vcResult.credential.id,
663
+ grantId: vcResult.grantId,
664
+ approvalMode: vcResult.approvalMode,
665
+ });
666
+ return {
667
+ success: false,
668
+ error: result.error,
669
+ reauthRequired: {
670
+ provider: result.reauthRequired.provider,
671
+ reason: result.reauthRequired.reason,
672
+ message: result.reauthRequired.message,
673
+ authUrl: result.reauthRequired.authUrl,
674
+ projectId: result.reauthRequired.projectId,
675
+ },
676
+ };
677
+ }
618
678
  // Detect resource mismatch from structured error code
619
- if (!result.success && result.errorCode === 'RESOURCE_MISMATCH') {
679
+ if (!result.success && result.errorCode === ai_identity_1.GATEWAY_ERROR_CODE.RESOURCE_MISMATCH) {
620
680
  return this.handleResourceMismatch(action, provider, params, vcResult);
621
681
  }
622
682
  // Detect credential-invalid errors and attempt VC reissuance (max 1 retry)
623
- if (!result.success && result.errorCode === 'CREDENTIAL_INVALID' && retryCount < 1) {
683
+ if (!result.success && result.errorCode === ai_identity_1.GATEWAY_ERROR_CODE.CREDENTIAL_INVALID && retryCount < 1) {
624
684
  this.wallet.revokeCredential(vcResult.credential.id);
625
685
  const newVCResult = await this.ensureVC(action, provider, undefined, params, canonicalResourceId);
626
686
  if ('credential' in newVCResult) {
@@ -640,6 +700,15 @@ class ExecutionEngine {
640
700
  // Could not re-acquire VC — return the original error
641
701
  return { success: false, error: result.error };
642
702
  }
703
+ // After retry exhaustion, if still CREDENTIAL_INVALID, fall back to OOB approval
704
+ // so the user gets an actionable re-authorization URL instead of a raw error.
705
+ if (!result.success && result.errorCode === ai_identity_1.GATEWAY_ERROR_CODE.CREDENTIAL_INVALID && retryCount >= 1) {
706
+ // Gateway path does not have normalizedResource/resourceFingerprint in scope
707
+ // (resource resolution happens server-side), so resourceInfo is omitted.
708
+ const oobFallback = await this.tryOOBFallbackAfterRetryExhaustion(action, provider, vcResult.credential.id, params);
709
+ if (oobFallback)
710
+ return oobFallback;
711
+ }
643
712
  this.logAudit({
644
713
  action, provider,
645
714
  decision: result.success ? 'allow' : 'deny',
@@ -728,7 +797,14 @@ class ExecutionEngine {
728
797
  if (statusResult.status === 'expired') {
729
798
  return { success: false, error: 'Approval request has expired. Please try again.' };
730
799
  }
800
+ if (statusResult.status === 'consumed') {
801
+ return { success: false, error: 'Approval was already claimed by another session. Please request a new approval.' };
802
+ }
731
803
  if (statusResult.status === 'pending') {
804
+ const rawExpiry = statusResult.requestExpiresAt
805
+ ? Math.floor((new Date(statusResult.requestExpiresAt).getTime() - Date.now()) / 1000)
806
+ : NaN;
807
+ const expiresIn = Number.isNaN(rawExpiry) ? OOB_APPROVAL_DEFAULT_EXPIRES_IN_SECONDS : Math.max(0, rawExpiry);
732
808
  return {
733
809
  success: false,
734
810
  waitingForApproval: {
@@ -737,45 +813,84 @@ class ExecutionEngine {
737
813
  requestId: pendingRequestId,
738
814
  action,
739
815
  riskLevel: this.getActionRiskLevel(action),
740
- expiresIn: OOB_APPROVAL_EXPIRES_IN_SECONDS,
816
+ expiresIn,
741
817
  message: 'Still waiting for approval. Please approve in your browser.',
742
818
  },
743
819
  };
744
820
  }
745
- // approved — store credential and execute directly (no wallet round-trip)
746
- if (statusResult.status === 'approved' && statusResult.credential) {
821
+ // approved — claim VC, store, and execute directly
822
+ if (statusResult.status === 'approved') {
747
823
  const provider = action.split('.')[0];
824
+ let claimed;
825
+ try {
826
+ claimed = await this.gatewayClient.claimVC(pendingRequestId, this.context.agentDid);
827
+ }
828
+ catch (claimErr) {
829
+ const msg = claimErr instanceof Error ? claimErr.message : String(claimErr);
830
+ if (msg === 'CLAIM_CONSUMED') {
831
+ return { success: false, error: 'Approval was already claimed by another session' };
832
+ }
833
+ if (msg === 'CLAIM_EXPIRED') {
834
+ return { success: false, error: 'Approval request has expired. Please try again.' };
835
+ }
836
+ return { success: false, error: `Failed to claim VC: ${msg}` };
837
+ }
838
+ // Restore original action parameters from approval request if not provided in retry call
839
+ const fallbackParams = claimed.actionParams;
840
+ const effectiveInput = Object.keys(request.input).length > 0
841
+ ? request.input
842
+ : fallbackParams ?? null;
843
+ if (!effectiveInput) {
844
+ return { success: false, error: 'Action parameters could not be restored. Please retry with explicit parameters.' };
845
+ }
846
+ // Security: re-validate resource constraint using effectiveInput (defense-in-depth)
847
+ // The pre-claim check (line 838) may have been skipped when request.input was empty.
848
+ if (statusResult.resources?.length) {
849
+ const extractedId = this.extractResourceIdFromInput(action, effectiveInput);
850
+ if (extractedId) {
851
+ const normalizedId = (0, executor_registry_1.isLocalAction)(action) && effectiveInput.file_path
852
+ ? (0, resource_canonicalizer_1.canonicalizePath)(effectiveInput.file_path)
853
+ : extractedId;
854
+ const approvedResources = statusResult.resources;
855
+ const resourceMatch = approvedResources.some((r) => r.id === extractedId || r.id === normalizedId ||
856
+ r.pattern === extractedId || r.pattern === normalizedId);
857
+ if (!resourceMatch) {
858
+ return {
859
+ success: false,
860
+ error: `Resource mismatch: approval was for [${approvedResources.map((r) => r.id || r.pattern).join(', ')}], but effective input resolves to "${extractedId}"`,
861
+ };
862
+ }
863
+ }
864
+ }
748
865
  const stored = this.wallet.storeCredential({
749
866
  holderDid: this.context.agentDid,
750
867
  projectId: this.context.projectId,
751
- credentialJwt: statusResult.credential,
752
- actions: [action],
868
+ credentialJwt: claimed.credential.jwt,
869
+ actions: claimed.actions.length ? claimed.actions : [action],
753
870
  provider,
754
- expiresAt: statusResult.expiresAt
755
- ? new Date(statusResult.expiresAt).getTime()
756
- : Date.now() + OOB_CREDENTIAL_DEFAULT_TTL_MS,
757
- resources: (0, wallet_1.validateResources)(statusResult.resources),
871
+ expiresAt: claimed.credential.expiresAt,
872
+ resources: (0, wallet_1.validateResources)(claimed.resources),
758
873
  });
759
874
  // Build VCAcquired and execute directly (skip re-enter via this.execute)
760
875
  const vcAcquired = {
761
876
  credential: stored,
762
877
  approvalMode: 'one_time',
763
- decisionSource: 'new_vc_from_quick_approve',
878
+ decisionSource: 'claimed_from_approval',
764
879
  };
765
880
  const routing = (0, executor_registry_1.determineRouting)(provider, action);
766
881
  let executeResult;
767
- const normalizedResource = (0, executor_registry_1.isLocalAction)(action) && request.input.file_path
768
- ? (0, resource_canonicalizer_1.canonicalizePath)(request.input.file_path)
882
+ const normalizedResource = (0, executor_registry_1.isLocalAction)(action) && effectiveInput.file_path
883
+ ? (0, resource_canonicalizer_1.canonicalizePath)(effectiveInput.file_path)
769
884
  : undefined;
770
885
  const resourceFingerprint = normalizedResource
771
886
  ? (0, resource_canonicalizer_1.computeFingerprint)(normalizedResource, this.context.rootDid)
772
887
  : undefined;
773
888
  try {
774
889
  if (routing.executor === 'local') {
775
- executeResult = await this.executeLocalWithVC(action, request.input, provider, vcAcquired, normalizedResource, resourceFingerprint, request.input.file_path);
890
+ executeResult = await this.executeLocalWithVC(action, effectiveInput, provider, vcAcquired, normalizedResource, resourceFingerprint, effectiveInput.file_path);
776
891
  }
777
892
  else {
778
- executeResult = await this.executeViaGatewayWithVC(provider, action, request.input, vcAcquired);
893
+ executeResult = await this.executeViaGatewayWithVC(provider, action, effectiveInput, vcAcquired);
779
894
  }
780
895
  }
781
896
  finally {
@@ -789,8 +904,15 @@ class ExecutionEngine {
789
904
  return { success: false, error: `Unexpected approval status: ${statusResult.status}` };
790
905
  }
791
906
  /**
792
- * Create an OOB approval request for high/critical risk actions.
907
+ * Create an OOB (out-of-band) approval request.
793
908
  * Returns a URL for the user to approve in their browser.
909
+ *
910
+ * Called in two contexts:
911
+ * - ensureVC Step 4: for high/critical risk actions that require browser approval.
912
+ * - tryOOBFallbackAfterRetryExhaustion: for ANY risk level when a VC expires
913
+ * mid-execution and retry is exhausted. In this case OOB is used as a fallback
914
+ * because the normal in-band flow cannot recover an expired credential — the
915
+ * user must re-authorize via browser regardless of risk level.
794
916
  */
795
917
  async createOOBApprovalRequest(action, provider, riskLevel, resourceInfo, input) {
796
918
  const resourceType = this.getResourceType(action);
@@ -806,7 +928,12 @@ class ExecutionEngine {
806
928
  projectId: this.context.projectId,
807
929
  actions: [action],
808
930
  resources,
931
+ actionParams: input,
809
932
  });
933
+ const rawExpiry = result.expiresAt
934
+ ? Math.floor((new Date(result.expiresAt).getTime() - Date.now()) / 1000)
935
+ : NaN;
936
+ const expiresIn = Number.isNaN(rawExpiry) ? OOB_APPROVAL_DEFAULT_EXPIRES_IN_SECONDS : Math.max(0, rawExpiry);
810
937
  return {
811
938
  waitingForApproval: {
812
939
  status: 'waiting_for_approval',
@@ -815,7 +942,7 @@ class ExecutionEngine {
815
942
  action,
816
943
  resource: resourceInfo?.normalizedResource ?? extractedId,
817
944
  riskLevel,
818
- expiresIn: OOB_APPROVAL_EXPIRES_IN_SECONDS,
945
+ expiresIn,
819
946
  message: `This ${riskLevel}-risk action requires browser approval (login required). Open the URL to approve.`,
820
947
  },
821
948
  };
@@ -824,6 +951,42 @@ class ExecutionEngine {
824
951
  throw new Error(`Failed to create OOB approval request: ${err instanceof Error ? err.message : String(err)}`);
825
952
  }
826
953
  }
954
+ /**
955
+ * Revoke the stale credential and attempt to create an OOB approval request
956
+ * as a fallback after retry exhaustion. Returns an ExecuteResult with
957
+ * waitingForApproval on success, or null if OOB creation fails (caller falls
958
+ * through to the original error).
959
+ */
960
+ async tryOOBFallbackAfterRetryExhaustion(action, provider, credentialId, params, resourceInfo, executionType = 'gateway') {
961
+ const enforcementType = executionType === 'local' ? 'gateway_verified_local' : 'gateway';
962
+ this.wallet.revokeCredential(credentialId);
963
+ try {
964
+ const riskLevel = this.getActionRiskLevel(action);
965
+ const oobResult = await this.createOOBApprovalRequest(action, provider, riskLevel, resourceInfo, params);
966
+ this.logAudit({
967
+ action, provider,
968
+ decision: 'deny',
969
+ enforcementType,
970
+ decisionSource: 'gateway_execution',
971
+ decisionReason: `Credential expired after retry — OOB approval requested (requestId=${oobResult.waitingForApproval.requestId})`,
972
+ executionType,
973
+ credentialId,
974
+ });
975
+ return { success: false, waitingForApproval: oobResult.waitingForApproval };
976
+ }
977
+ catch (err) {
978
+ this.logAudit({
979
+ action, provider,
980
+ decision: 'deny',
981
+ enforcementType,
982
+ decisionSource: 'gateway_execution',
983
+ decisionReason: `OOB approval fallback failed after retry exhaustion: ${err instanceof Error ? err.message : String(err)}`,
984
+ executionType,
985
+ credentialId,
986
+ });
987
+ return null;
988
+ }
989
+ }
827
990
  /**
828
991
  * Create an in-band approval response for low/medium risk actions.
829
992
  * Generates an approval token for the MCP client to present back.
@@ -1180,13 +1343,21 @@ class ExecutionEngine {
1180
1343
  const binding = actionDef.target_bindings.resource_id;
1181
1344
  if (binding.source !== 'param')
1182
1345
  return undefined;
1183
- const value = input[binding.param];
1346
+ let value = input[binding.param];
1347
+ // Fallback: AI agents may use alternative param names (e.g., "issueKey" instead of "issueIdOrKey")
1348
+ if (!value && binding.fallback_param) {
1349
+ value = input[binding.fallback_param];
1350
+ }
1184
1351
  if (!value || typeof value !== 'string')
1185
1352
  return undefined;
1186
1353
  // Defense-in-depth: reject control characters and excessively long values
1187
1354
  const MAX_RESOURCE_ID_LENGTH = 500;
1188
1355
  if (value.length > MAX_RESOURCE_ID_LENGTH || /[\x00-\x1f]/.test(value))
1189
1356
  return undefined;
1357
+ // Apply derive transformation (e.g., "AIDENTITY-34" → "AIDENTITY" for project-level scoping)
1358
+ if (binding.derive === 'project_key') {
1359
+ value = (0, ai_identity_1.extractProjectKey)(value) ?? value;
1360
+ }
1190
1361
  return value;
1191
1362
  }
1192
1363
  isRecentOOBRequest(action, resourceId) {