@astrasyncai/verification-gateway 3.12.0 → 4.1.0

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 (134) hide show
  1. package/README.md +106 -29
  2. package/dist/adapter-interface/interface.d.mts +2 -2
  3. package/dist/adapter-interface/interface.d.ts +2 -2
  4. package/dist/adapters/express.d.mts +2 -2
  5. package/dist/adapters/express.d.ts +2 -2
  6. package/dist/adapters/express.js +482 -22
  7. package/dist/adapters/express.js.map +1 -1
  8. package/dist/adapters/express.mjs +482 -22
  9. package/dist/adapters/express.mjs.map +1 -1
  10. package/dist/adapters/http-pdlss.d.mts +3 -3
  11. package/dist/adapters/http-pdlss.d.ts +3 -3
  12. package/dist/adapters/http-pdlss.js.map +1 -1
  13. package/dist/adapters/http-pdlss.mjs.map +1 -1
  14. package/dist/adapters/mcp.d.mts +56 -36
  15. package/dist/adapters/mcp.d.ts +56 -36
  16. package/dist/adapters/mcp.js +309 -10
  17. package/dist/adapters/mcp.js.map +1 -1
  18. package/dist/adapters/mcp.mjs +309 -10
  19. package/dist/adapters/mcp.mjs.map +1 -1
  20. package/dist/adapters/nextjs.d.mts +2 -2
  21. package/dist/adapters/nextjs.d.ts +2 -2
  22. package/dist/adapters/nextjs.js +518 -25
  23. package/dist/adapters/nextjs.js.map +1 -1
  24. package/dist/adapters/nextjs.mjs +518 -25
  25. package/dist/adapters/nextjs.mjs.map +1 -1
  26. package/dist/adapters/sdk.d.mts +2 -2
  27. package/dist/adapters/sdk.d.ts +2 -2
  28. package/dist/adapters/sdk.js +72 -9
  29. package/dist/adapters/sdk.js.map +1 -1
  30. package/dist/adapters/sdk.mjs +72 -9
  31. package/dist/adapters/sdk.mjs.map +1 -1
  32. package/dist/agent/index.d.mts +2 -2
  33. package/dist/agent/index.d.ts +2 -2
  34. package/dist/agent/index.js.map +1 -1
  35. package/dist/agent/index.mjs.map +1 -1
  36. package/dist/bin/astrasync-claude-hook.js +83 -20
  37. package/dist/bin/astrasync-codex-hook.js +83 -20
  38. package/dist/bin/astrasync-guard.js +83 -20
  39. package/dist/bin/astrasync.js +150 -33
  40. package/dist/browser/background.js +83 -20
  41. package/dist/browser/background.js.map +1 -1
  42. package/dist/browser/background.mjs +83 -20
  43. package/dist/browser/background.mjs.map +1 -1
  44. package/dist/browser/browser-adapter.d.mts +2 -2
  45. package/dist/browser/browser-adapter.d.ts +2 -2
  46. package/dist/claude-code/claude-code-adapter.d.mts +2 -2
  47. package/dist/claude-code/claude-code-adapter.d.ts +2 -2
  48. package/dist/cli/index.d.mts +2 -2
  49. package/dist/cli/index.d.ts +2 -2
  50. package/dist/codex/index.d.mts +3 -3
  51. package/dist/codex/index.d.ts +3 -3
  52. package/dist/codex/index.js +83 -20
  53. package/dist/codex/index.js.map +1 -1
  54. package/dist/codex/index.mjs +83 -20
  55. package/dist/codex/index.mjs.map +1 -1
  56. package/dist/cursor/cursor-adapter.d.mts +2 -2
  57. package/dist/cursor/cursor-adapter.d.ts +2 -2
  58. package/dist/cursor/extension.d.mts +2 -2
  59. package/dist/cursor/extension.d.ts +2 -2
  60. package/dist/cursor/extension.js +83 -20
  61. package/dist/cursor/extension.js.map +1 -1
  62. package/dist/cursor/extension.mjs +83 -20
  63. package/dist/cursor/extension.mjs.map +1 -1
  64. package/dist/edge-config.d.mts +22 -7
  65. package/dist/edge-config.d.ts +22 -7
  66. package/dist/edge-config.js.map +1 -1
  67. package/dist/edge-config.mjs.map +1 -1
  68. package/dist/edge-core/index.d.mts +425 -0
  69. package/dist/edge-core/index.d.ts +425 -0
  70. package/dist/edge-core/index.js +1482 -0
  71. package/dist/edge-core/index.js.map +1 -0
  72. package/dist/edge-core/index.mjs +1437 -0
  73. package/dist/edge-core/index.mjs.map +1 -0
  74. package/dist/{express-B39o89gf.d.mts → express-BVd1_3FE.d.ts} +9 -7
  75. package/dist/{express-BuxKNO_q.d.ts → express-D_4hTn5Z.d.mts} +9 -7
  76. package/dist/gateway/gateway.d.mts +2 -2
  77. package/dist/gateway/gateway.d.ts +2 -2
  78. package/dist/gateway/gateway.js +83 -20
  79. package/dist/gateway/gateway.js.map +1 -1
  80. package/dist/gateway/gateway.mjs +83 -20
  81. package/dist/gateway/gateway.mjs.map +1 -1
  82. package/dist/git-trigger/git-hooks.d.mts +2 -2
  83. package/dist/git-trigger/git-hooks.d.ts +2 -2
  84. package/dist/{index-CLHdqs2M.d.ts → index-B0YHu_SP.d.ts} +30 -27
  85. package/dist/{index-DME3w3lf.d.mts → index-BPEBlOsE.d.mts} +30 -27
  86. package/dist/{index-CqydB4ks.d.mts → index-DQb5-_1X.d.mts} +1 -1
  87. package/dist/{index-BG62SXso.d.ts → index-T1aBoUcc.d.ts} +1 -1
  88. package/dist/index.d.mts +11 -11
  89. package/dist/index.d.ts +11 -11
  90. package/dist/index.js +595 -272
  91. package/dist/index.js.map +1 -1
  92. package/dist/index.mjs +592 -272
  93. package/dist/index.mjs.map +1 -1
  94. package/dist/local-evaluator/evaluator.d.mts +2 -2
  95. package/dist/local-evaluator/evaluator.d.ts +2 -2
  96. package/dist/metadata-capture.d.mts +63 -4
  97. package/dist/metadata-capture.d.ts +63 -4
  98. package/dist/metadata-capture.js +67 -0
  99. package/dist/metadata-capture.js.map +1 -1
  100. package/dist/metadata-capture.mjs +64 -0
  101. package/dist/metadata-capture.mjs.map +1 -1
  102. package/dist/{nextjs-D74W7wt-.d.mts → nextjs-WyeVr2Kp.d.mts} +3 -3
  103. package/dist/{nextjs-Ddlw_J1J.d.ts → nextjs-w7RTbP1P.d.ts} +3 -3
  104. package/dist/platform-signatures.d.mts +5 -6
  105. package/dist/platform-signatures.d.ts +5 -6
  106. package/dist/platform-signatures.js.map +1 -1
  107. package/dist/platform-signatures.mjs.map +1 -1
  108. package/dist/registration/index.d.mts +10 -11
  109. package/dist/registration/index.d.ts +10 -11
  110. package/dist/registration/index.js.map +1 -1
  111. package/dist/registration/index.mjs.map +1 -1
  112. package/dist/{sdk-CT7gdkfR.d.ts → sdk-CbTNIkAa.d.mts} +4 -4
  113. package/dist/{sdk-Bn6bj7kt.d.mts → sdk-QeX0Z8Ho.d.ts} +4 -4
  114. package/dist/transport/index.d.mts +2 -2
  115. package/dist/transport/index.d.ts +2 -2
  116. package/dist/transport/index.js.map +1 -1
  117. package/dist/transport/index.mjs.map +1 -1
  118. package/dist/{types-DHu7m9HI.d.ts → types-BLUx92FJ.d.ts} +1 -1
  119. package/dist/{types-Djw4GLtz.d.mts → types-DqfPU5Bl.d.mts} +1 -1
  120. package/dist/{types-BntuzEEn.d.mts → types-r850cOt0.d.mts} +121 -88
  121. package/dist/{types-SDu4Llbe.d.ts → types-sMxBT-nM.d.ts} +121 -88
  122. package/dist/ui/index.d.mts +11 -7
  123. package/dist/ui/index.d.ts +11 -7
  124. package/dist/ui/index.js +13 -9
  125. package/dist/ui/index.js.map +1 -1
  126. package/dist/ui/index.mjs +10 -8
  127. package/dist/ui/index.mjs.map +1 -1
  128. package/dist/verify.d.mts +11 -8
  129. package/dist/verify.d.ts +11 -8
  130. package/dist/verify.js +76 -8
  131. package/dist/verify.js.map +1 -1
  132. package/dist/verify.mjs +75 -8
  133. package/dist/verify.mjs.map +1 -1
  134. package/package.json +8 -2
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Shared purpose/action resolver for the HTTP adapters (express + nextjs) —
3
- * vocabulary-unification round (Bug 14, adapter-spec §4.6).
3
+ * the vocabulary-unification layer.
4
4
  *
5
5
  * The PDLSS two-axis contract: `purpose` = bare category noun (`shopping`,
6
6
  * `data`, custom nouns like `trading`); `action` = dotted verb
@@ -13,7 +13,7 @@
13
13
  * duplicated here and pinned strictly equal by a monorepo CI test
14
14
  * (vocab-equality.test.ts). Change them in BOTH places or that test fails.
15
15
  */
16
- /** Pinned method→action derivation table (§4.6). */
16
+ /** Pinned method→action derivation table. */
17
17
  declare const HTTP_METHOD_ACTION_TABLE: Readonly<Record<string, string>>;
18
18
  /** Unknown methods are treated as mutating until proven otherwise. */
19
19
  declare const DEFAULT_HTTP_ACTION = "data.write";
@@ -63,7 +63,7 @@ declare function normalizePurposeHeader(value: string): {
63
63
  /**
64
64
  * Resolve the {purpose, action} pair for one HTTP request.
65
65
  *
66
- * Precedence (both axes; §4.6):
66
+ * Precedence (both axes):
67
67
  * action: route mapping → extractAction option → X-Astra-Action header →
68
68
  * candidate from a dotted X-Astra-Purpose → pinned method table
69
69
  * purpose: route mapping → extractPurpose option → X-Astra-Purpose →
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Shared purpose/action resolver for the HTTP adapters (express + nextjs) —
3
- * vocabulary-unification round (Bug 14, adapter-spec §4.6).
3
+ * the vocabulary-unification layer.
4
4
  *
5
5
  * The PDLSS two-axis contract: `purpose` = bare category noun (`shopping`,
6
6
  * `data`, custom nouns like `trading`); `action` = dotted verb
@@ -13,7 +13,7 @@
13
13
  * duplicated here and pinned strictly equal by a monorepo CI test
14
14
  * (vocab-equality.test.ts). Change them in BOTH places or that test fails.
15
15
  */
16
- /** Pinned method→action derivation table (§4.6). */
16
+ /** Pinned method→action derivation table. */
17
17
  declare const HTTP_METHOD_ACTION_TABLE: Readonly<Record<string, string>>;
18
18
  /** Unknown methods are treated as mutating until proven otherwise. */
19
19
  declare const DEFAULT_HTTP_ACTION = "data.write";
@@ -63,7 +63,7 @@ declare function normalizePurposeHeader(value: string): {
63
63
  /**
64
64
  * Resolve the {purpose, action} pair for one HTTP request.
65
65
  *
66
- * Precedence (both axes; §4.6):
66
+ * Precedence (both axes):
67
67
  * action: route mapping → extractAction option → X-Astra-Action header →
68
68
  * candidate from a dotted X-Astra-Purpose → pinned method table
69
69
  * purpose: route mapping → extractPurpose option → X-Astra-Purpose →
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/http-pdlss.ts"],"sourcesContent":["/**\n * Shared purpose/action resolver for the HTTP adapters (express + nextjs) —\n * vocabulary-unification round (Bug 14, adapter-spec §4.6).\n *\n * The PDLSS two-axis contract: `purpose` = bare category noun (`shopping`,\n * `data`, custom nouns like `trading`); `action` = dotted verb\n * (`shopping.search`) or an enumerated transport token. Transport verbs\n * (GET/POST/...) NEVER travel as PDLSS actions — they map through the pinned\n * method table below.\n *\n * SELF-CONTAINED on purpose: this published package cannot depend on the\n * private @astrasyncai/pdlss-vocab workspace package, so the constants are\n * duplicated here and pinned strictly equal by a monorepo CI test\n * (vocab-equality.test.ts). Change them in BOTH places or that test fails.\n */\n\n/** Pinned method→action derivation table (§4.6). */\nexport const HTTP_METHOD_ACTION_TABLE: Readonly<Record<string, string>> = {\n GET: 'data.read',\n HEAD: 'data.read',\n OPTIONS: 'data.read',\n POST: 'data.write',\n PUT: 'data.write',\n PATCH: 'data.write',\n DELETE: 'data.delete',\n};\n\n/** Unknown methods are treated as mutating until proven otherwise. */\nexport const DEFAULT_HTTP_ACTION = 'data.write';\n\n/** Generic category emitted when nothing supplies a domain purpose. */\nexport const DEFAULT_HTTP_PURPOSE = 'data';\n\nexport function actionForHttpMethod(method: string): string {\n return HTTP_METHOD_ACTION_TABLE[method.toUpperCase()] ?? DEFAULT_HTTP_ACTION;\n}\n\nexport type HttpPurposeSource =\n | 'route_config'\n | 'custom_extractor'\n | 'header'\n | 'legacy_header'\n | 'query'\n | 'action_derived'\n | 'transport_default';\n\nexport type HttpActionSource =\n | 'route_config'\n | 'custom_extractor'\n | 'header'\n | 'purpose_header_derived'\n | 'method_table';\n\nexport interface HttpPdlssInput {\n method: string;\n /** `X-Astra-Purpose` header value (agent's declaration). */\n astraPurpose?: string;\n /** `X-Astra-Action` header value (agent's declaration, symmetric with MCP). */\n astraAction?: string;\n /** Legacy `x-purpose` header — deprecated, removal scheduled for 4.0.0. */\n legacyPurpose?: string;\n /** Legacy `?purpose` query param (express only) — deprecated, removal 4.0.0. */\n queryPurpose?: string;\n /** Dashboard route config send-mapping (RouteAccessConfig.purpose/.action). */\n routePurpose?: string;\n routeAction?: string;\n /** Results of the merchant's extractPurpose/extractAction options. */\n customPurpose?: string;\n hasCustomPurposeExtractor?: boolean;\n customAction?: string;\n hasCustomActionExtractor?: boolean;\n}\n\nexport interface HttpPdlssResolution {\n purpose: string;\n action: string;\n purposeSource: HttpPurposeSource;\n actionSource: HttpActionSource;\n}\n\n/**\n * Normalize an `X-Astra-Purpose` header value.\n * - colon form `\"category:action\"` (legacy wire shape): take the prefix; the\n * remainder is free-text and exists in no vocabulary — discard it and let\n * the method table supply the action (matches pre-3.1.0 discard behavior).\n * - dotted form `\"shopping.purchase\"` (the shape older docs taught): prefix\n * becomes the purpose, the WHOLE token becomes an action candidate.\n * - bare token: purpose verbatim.\n */\nexport function normalizePurposeHeader(value: string): {\n purpose: string;\n actionCandidate?: string;\n} {\n const colon = value.indexOf(':');\n if (colon >= 0) {\n return { purpose: value.slice(0, colon) };\n }\n const dot = value.indexOf('.');\n if (dot > 0 && dot < value.length - 1) {\n return { purpose: value.slice(0, dot), actionCandidate: value };\n }\n return { purpose: value };\n}\n\n/**\n * Resolve the {purpose, action} pair for one HTTP request.\n *\n * Precedence (both axes; §4.6):\n * action: route mapping → extractAction option → X-Astra-Action header →\n * candidate from a dotted X-Astra-Purpose → pinned method table\n * purpose: route mapping → extractPurpose option → X-Astra-Purpose →\n * legacy x-purpose / ?purpose → category prefix of the resolved\n * action → 'data'\n *\n * A configured custom extractor masks the header/legacy/query steps of its\n * axis (it replaces the default chain, as extractPurpose always has); when it\n * returns undefined the axis falls through to the table/prefix fallback.\n * The route mapping is merchant policy fetched from the dashboard — it\n * outranks agent-supplied headers the way MCP toolGates do.\n */\nexport function resolveHttpPdlss(input: HttpPdlssInput): HttpPdlssResolution {\n const fromHeader = input.astraPurpose ? normalizePurposeHeader(input.astraPurpose) : undefined;\n\n // ── Action axis ──\n let action: string;\n let actionSource: HttpActionSource;\n if (input.routeAction) {\n action = input.routeAction;\n actionSource = 'route_config';\n } else if (input.hasCustomActionExtractor && input.customAction) {\n action = input.customAction;\n actionSource = 'custom_extractor';\n } else if (!input.hasCustomActionExtractor && input.astraAction) {\n action = input.astraAction;\n actionSource = 'header';\n } else if (!input.hasCustomActionExtractor && fromHeader?.actionCandidate) {\n action = fromHeader.actionCandidate;\n actionSource = 'purpose_header_derived';\n } else {\n action = actionForHttpMethod(input.method);\n actionSource = 'method_table';\n }\n\n // ── Purpose axis ──\n let purpose: string | undefined;\n let purposeSource: HttpPurposeSource | undefined;\n if (input.routePurpose) {\n purpose = input.routePurpose;\n purposeSource = 'route_config';\n } else if (input.hasCustomPurposeExtractor) {\n if (input.customPurpose) {\n purpose = input.customPurpose;\n purposeSource = 'custom_extractor';\n }\n } else if (fromHeader) {\n purpose = fromHeader.purpose;\n purposeSource = 'header';\n } else if (input.legacyPurpose) {\n purpose = input.legacyPurpose;\n purposeSource = 'legacy_header';\n } else if (input.queryPurpose) {\n purpose = input.queryPurpose;\n purposeSource = 'query';\n }\n\n if (!purpose) {\n // Keep the pair coherent: an agent that sent only X-Astra-Action (or a\n // route that mapped only the action) gets that action's own category.\n const dot = action.indexOf('.');\n if (dot > 0) {\n purpose = action.slice(0, dot);\n purposeSource = 'action_derived';\n } else {\n purpose = DEFAULT_HTTP_PURPOSE;\n purposeSource = 'transport_default';\n }\n }\n\n return { purpose, action, purposeSource: purposeSource!, actionSource };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBO,IAAM,2BAA6D;AAAA,EACxE,KAAK;AAAA,EACL,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AACV;AAGO,IAAM,sBAAsB;AAG5B,IAAM,uBAAuB;AAE7B,SAAS,oBAAoB,QAAwB;AAC1D,SAAO,yBAAyB,OAAO,YAAY,CAAC,KAAK;AAC3D;AAsDO,SAAS,uBAAuB,OAGrC;AACA,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,MAAI,SAAS,GAAG;AACd,WAAO,EAAE,SAAS,MAAM,MAAM,GAAG,KAAK,EAAE;AAAA,EAC1C;AACA,QAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,MAAI,MAAM,KAAK,MAAM,MAAM,SAAS,GAAG;AACrC,WAAO,EAAE,SAAS,MAAM,MAAM,GAAG,GAAG,GAAG,iBAAiB,MAAM;AAAA,EAChE;AACA,SAAO,EAAE,SAAS,MAAM;AAC1B;AAkBO,SAAS,iBAAiB,OAA4C;AAC3E,QAAM,aAAa,MAAM,eAAe,uBAAuB,MAAM,YAAY,IAAI;AAGrF,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM,aAAa;AACrB,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,MAAM,4BAA4B,MAAM,cAAc;AAC/D,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,CAAC,MAAM,4BAA4B,MAAM,aAAa;AAC/D,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,CAAC,MAAM,4BAA4B,YAAY,iBAAiB;AACzE,aAAS,WAAW;AACpB,mBAAe;AAAA,EACjB,OAAO;AACL,aAAS,oBAAoB,MAAM,MAAM;AACzC,mBAAe;AAAA,EACjB;AAGA,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM,cAAc;AACtB,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB,WAAW,MAAM,2BAA2B;AAC1C,QAAI,MAAM,eAAe;AACvB,gBAAU,MAAM;AAChB,sBAAgB;AAAA,IAClB;AAAA,EACF,WAAW,YAAY;AACrB,cAAU,WAAW;AACrB,oBAAgB;AAAA,EAClB,WAAW,MAAM,eAAe;AAC9B,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB,WAAW,MAAM,cAAc;AAC7B,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB;AAEA,MAAI,CAAC,SAAS;AAGZ,UAAM,MAAM,OAAO,QAAQ,GAAG;AAC9B,QAAI,MAAM,GAAG;AACX,gBAAU,OAAO,MAAM,GAAG,GAAG;AAC7B,sBAAgB;AAAA,IAClB,OAAO;AACL,gBAAU;AACV,sBAAgB;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,QAAQ,eAA+B,aAAa;AACxE;","names":[]}
1
+ {"version":3,"sources":["../../src/adapters/http-pdlss.ts"],"sourcesContent":["/**\n * Shared purpose/action resolver for the HTTP adapters (express + nextjs) —\n * the vocabulary-unification layer.\n *\n * The PDLSS two-axis contract: `purpose` = bare category noun (`shopping`,\n * `data`, custom nouns like `trading`); `action` = dotted verb\n * (`shopping.search`) or an enumerated transport token. Transport verbs\n * (GET/POST/...) NEVER travel as PDLSS actions — they map through the pinned\n * method table below.\n *\n * SELF-CONTAINED on purpose: this published package cannot depend on the\n * private @astrasyncai/pdlss-vocab workspace package, so the constants are\n * duplicated here and pinned strictly equal by a monorepo CI test\n * (vocab-equality.test.ts). Change them in BOTH places or that test fails.\n */\n\n/** Pinned method→action derivation table. */\nexport const HTTP_METHOD_ACTION_TABLE: Readonly<Record<string, string>> = {\n GET: 'data.read',\n HEAD: 'data.read',\n OPTIONS: 'data.read',\n POST: 'data.write',\n PUT: 'data.write',\n PATCH: 'data.write',\n DELETE: 'data.delete',\n};\n\n/** Unknown methods are treated as mutating until proven otherwise. */\nexport const DEFAULT_HTTP_ACTION = 'data.write';\n\n/** Generic category emitted when nothing supplies a domain purpose. */\nexport const DEFAULT_HTTP_PURPOSE = 'data';\n\nexport function actionForHttpMethod(method: string): string {\n return HTTP_METHOD_ACTION_TABLE[method.toUpperCase()] ?? DEFAULT_HTTP_ACTION;\n}\n\nexport type HttpPurposeSource =\n | 'route_config'\n | 'custom_extractor'\n | 'header'\n | 'legacy_header'\n | 'query'\n | 'action_derived'\n | 'transport_default';\n\nexport type HttpActionSource =\n | 'route_config'\n | 'custom_extractor'\n | 'header'\n | 'purpose_header_derived'\n | 'method_table';\n\nexport interface HttpPdlssInput {\n method: string;\n /** `X-Astra-Purpose` header value (agent's declaration). */\n astraPurpose?: string;\n /** `X-Astra-Action` header value (agent's declaration, symmetric with MCP). */\n astraAction?: string;\n /** Legacy `x-purpose` header — deprecated, removal scheduled for 4.0.0. */\n legacyPurpose?: string;\n /** Legacy `?purpose` query param (express only) — deprecated, removal 4.0.0. */\n queryPurpose?: string;\n /** Dashboard route config send-mapping (RouteAccessConfig.purpose/.action). */\n routePurpose?: string;\n routeAction?: string;\n /** Results of the merchant's extractPurpose/extractAction options. */\n customPurpose?: string;\n hasCustomPurposeExtractor?: boolean;\n customAction?: string;\n hasCustomActionExtractor?: boolean;\n}\n\nexport interface HttpPdlssResolution {\n purpose: string;\n action: string;\n purposeSource: HttpPurposeSource;\n actionSource: HttpActionSource;\n}\n\n/**\n * Normalize an `X-Astra-Purpose` header value.\n * - colon form `\"category:action\"` (legacy wire shape): take the prefix; the\n * remainder is free-text and exists in no vocabulary — discard it and let\n * the method table supply the action (matches pre-3.1.0 discard behavior).\n * - dotted form `\"shopping.purchase\"` (the shape older docs taught): prefix\n * becomes the purpose, the WHOLE token becomes an action candidate.\n * - bare token: purpose verbatim.\n */\nexport function normalizePurposeHeader(value: string): {\n purpose: string;\n actionCandidate?: string;\n} {\n const colon = value.indexOf(':');\n if (colon >= 0) {\n return { purpose: value.slice(0, colon) };\n }\n const dot = value.indexOf('.');\n if (dot > 0 && dot < value.length - 1) {\n return { purpose: value.slice(0, dot), actionCandidate: value };\n }\n return { purpose: value };\n}\n\n/**\n * Resolve the {purpose, action} pair for one HTTP request.\n *\n * Precedence (both axes):\n * action: route mapping → extractAction option → X-Astra-Action header →\n * candidate from a dotted X-Astra-Purpose → pinned method table\n * purpose: route mapping → extractPurpose option → X-Astra-Purpose →\n * legacy x-purpose / ?purpose → category prefix of the resolved\n * action → 'data'\n *\n * A configured custom extractor masks the header/legacy/query steps of its\n * axis (it replaces the default chain, as extractPurpose always has); when it\n * returns undefined the axis falls through to the table/prefix fallback.\n * The route mapping is merchant policy fetched from the dashboard — it\n * outranks agent-supplied headers the way MCP toolGates do.\n */\nexport function resolveHttpPdlss(input: HttpPdlssInput): HttpPdlssResolution {\n const fromHeader = input.astraPurpose ? normalizePurposeHeader(input.astraPurpose) : undefined;\n\n // ── Action axis ──\n let action: string;\n let actionSource: HttpActionSource;\n if (input.routeAction) {\n action = input.routeAction;\n actionSource = 'route_config';\n } else if (input.hasCustomActionExtractor && input.customAction) {\n action = input.customAction;\n actionSource = 'custom_extractor';\n } else if (!input.hasCustomActionExtractor && input.astraAction) {\n action = input.astraAction;\n actionSource = 'header';\n } else if (!input.hasCustomActionExtractor && fromHeader?.actionCandidate) {\n action = fromHeader.actionCandidate;\n actionSource = 'purpose_header_derived';\n } else {\n action = actionForHttpMethod(input.method);\n actionSource = 'method_table';\n }\n\n // ── Purpose axis ──\n let purpose: string | undefined;\n let purposeSource: HttpPurposeSource | undefined;\n if (input.routePurpose) {\n purpose = input.routePurpose;\n purposeSource = 'route_config';\n } else if (input.hasCustomPurposeExtractor) {\n if (input.customPurpose) {\n purpose = input.customPurpose;\n purposeSource = 'custom_extractor';\n }\n } else if (fromHeader) {\n purpose = fromHeader.purpose;\n purposeSource = 'header';\n } else if (input.legacyPurpose) {\n purpose = input.legacyPurpose;\n purposeSource = 'legacy_header';\n } else if (input.queryPurpose) {\n purpose = input.queryPurpose;\n purposeSource = 'query';\n }\n\n if (!purpose) {\n // Keep the pair coherent: an agent that sent only X-Astra-Action (or a\n // route that mapped only the action) gets that action's own category.\n const dot = action.indexOf('.');\n if (dot > 0) {\n purpose = action.slice(0, dot);\n purposeSource = 'action_derived';\n } else {\n purpose = DEFAULT_HTTP_PURPOSE;\n purposeSource = 'transport_default';\n }\n }\n\n return { purpose, action, purposeSource: purposeSource!, actionSource };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBO,IAAM,2BAA6D;AAAA,EACxE,KAAK;AAAA,EACL,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AACV;AAGO,IAAM,sBAAsB;AAG5B,IAAM,uBAAuB;AAE7B,SAAS,oBAAoB,QAAwB;AAC1D,SAAO,yBAAyB,OAAO,YAAY,CAAC,KAAK;AAC3D;AAsDO,SAAS,uBAAuB,OAGrC;AACA,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,MAAI,SAAS,GAAG;AACd,WAAO,EAAE,SAAS,MAAM,MAAM,GAAG,KAAK,EAAE;AAAA,EAC1C;AACA,QAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,MAAI,MAAM,KAAK,MAAM,MAAM,SAAS,GAAG;AACrC,WAAO,EAAE,SAAS,MAAM,MAAM,GAAG,GAAG,GAAG,iBAAiB,MAAM;AAAA,EAChE;AACA,SAAO,EAAE,SAAS,MAAM;AAC1B;AAkBO,SAAS,iBAAiB,OAA4C;AAC3E,QAAM,aAAa,MAAM,eAAe,uBAAuB,MAAM,YAAY,IAAI;AAGrF,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM,aAAa;AACrB,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,MAAM,4BAA4B,MAAM,cAAc;AAC/D,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,CAAC,MAAM,4BAA4B,MAAM,aAAa;AAC/D,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,CAAC,MAAM,4BAA4B,YAAY,iBAAiB;AACzE,aAAS,WAAW;AACpB,mBAAe;AAAA,EACjB,OAAO;AACL,aAAS,oBAAoB,MAAM,MAAM;AACzC,mBAAe;AAAA,EACjB;AAGA,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM,cAAc;AACtB,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB,WAAW,MAAM,2BAA2B;AAC1C,QAAI,MAAM,eAAe;AACvB,gBAAU,MAAM;AAChB,sBAAgB;AAAA,IAClB;AAAA,EACF,WAAW,YAAY;AACrB,cAAU,WAAW;AACrB,oBAAgB;AAAA,EAClB,WAAW,MAAM,eAAe;AAC9B,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB,WAAW,MAAM,cAAc;AAC7B,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB;AAEA,MAAI,CAAC,SAAS;AAGZ,UAAM,MAAM,OAAO,QAAQ,GAAG;AAC9B,QAAI,MAAM,GAAG;AACX,gBAAU,OAAO,MAAM,GAAG,GAAG;AAC7B,sBAAgB;AAAA,IAClB,OAAO;AACL,gBAAU;AACV,sBAAgB;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,QAAQ,eAA+B,aAAa;AACxE;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/http-pdlss.ts"],"sourcesContent":["/**\n * Shared purpose/action resolver for the HTTP adapters (express + nextjs) —\n * vocabulary-unification round (Bug 14, adapter-spec §4.6).\n *\n * The PDLSS two-axis contract: `purpose` = bare category noun (`shopping`,\n * `data`, custom nouns like `trading`); `action` = dotted verb\n * (`shopping.search`) or an enumerated transport token. Transport verbs\n * (GET/POST/...) NEVER travel as PDLSS actions — they map through the pinned\n * method table below.\n *\n * SELF-CONTAINED on purpose: this published package cannot depend on the\n * private @astrasyncai/pdlss-vocab workspace package, so the constants are\n * duplicated here and pinned strictly equal by a monorepo CI test\n * (vocab-equality.test.ts). Change them in BOTH places or that test fails.\n */\n\n/** Pinned method→action derivation table (§4.6). */\nexport const HTTP_METHOD_ACTION_TABLE: Readonly<Record<string, string>> = {\n GET: 'data.read',\n HEAD: 'data.read',\n OPTIONS: 'data.read',\n POST: 'data.write',\n PUT: 'data.write',\n PATCH: 'data.write',\n DELETE: 'data.delete',\n};\n\n/** Unknown methods are treated as mutating until proven otherwise. */\nexport const DEFAULT_HTTP_ACTION = 'data.write';\n\n/** Generic category emitted when nothing supplies a domain purpose. */\nexport const DEFAULT_HTTP_PURPOSE = 'data';\n\nexport function actionForHttpMethod(method: string): string {\n return HTTP_METHOD_ACTION_TABLE[method.toUpperCase()] ?? DEFAULT_HTTP_ACTION;\n}\n\nexport type HttpPurposeSource =\n | 'route_config'\n | 'custom_extractor'\n | 'header'\n | 'legacy_header'\n | 'query'\n | 'action_derived'\n | 'transport_default';\n\nexport type HttpActionSource =\n | 'route_config'\n | 'custom_extractor'\n | 'header'\n | 'purpose_header_derived'\n | 'method_table';\n\nexport interface HttpPdlssInput {\n method: string;\n /** `X-Astra-Purpose` header value (agent's declaration). */\n astraPurpose?: string;\n /** `X-Astra-Action` header value (agent's declaration, symmetric with MCP). */\n astraAction?: string;\n /** Legacy `x-purpose` header — deprecated, removal scheduled for 4.0.0. */\n legacyPurpose?: string;\n /** Legacy `?purpose` query param (express only) — deprecated, removal 4.0.0. */\n queryPurpose?: string;\n /** Dashboard route config send-mapping (RouteAccessConfig.purpose/.action). */\n routePurpose?: string;\n routeAction?: string;\n /** Results of the merchant's extractPurpose/extractAction options. */\n customPurpose?: string;\n hasCustomPurposeExtractor?: boolean;\n customAction?: string;\n hasCustomActionExtractor?: boolean;\n}\n\nexport interface HttpPdlssResolution {\n purpose: string;\n action: string;\n purposeSource: HttpPurposeSource;\n actionSource: HttpActionSource;\n}\n\n/**\n * Normalize an `X-Astra-Purpose` header value.\n * - colon form `\"category:action\"` (legacy wire shape): take the prefix; the\n * remainder is free-text and exists in no vocabulary — discard it and let\n * the method table supply the action (matches pre-3.1.0 discard behavior).\n * - dotted form `\"shopping.purchase\"` (the shape older docs taught): prefix\n * becomes the purpose, the WHOLE token becomes an action candidate.\n * - bare token: purpose verbatim.\n */\nexport function normalizePurposeHeader(value: string): {\n purpose: string;\n actionCandidate?: string;\n} {\n const colon = value.indexOf(':');\n if (colon >= 0) {\n return { purpose: value.slice(0, colon) };\n }\n const dot = value.indexOf('.');\n if (dot > 0 && dot < value.length - 1) {\n return { purpose: value.slice(0, dot), actionCandidate: value };\n }\n return { purpose: value };\n}\n\n/**\n * Resolve the {purpose, action} pair for one HTTP request.\n *\n * Precedence (both axes; §4.6):\n * action: route mapping → extractAction option → X-Astra-Action header →\n * candidate from a dotted X-Astra-Purpose → pinned method table\n * purpose: route mapping → extractPurpose option → X-Astra-Purpose →\n * legacy x-purpose / ?purpose → category prefix of the resolved\n * action → 'data'\n *\n * A configured custom extractor masks the header/legacy/query steps of its\n * axis (it replaces the default chain, as extractPurpose always has); when it\n * returns undefined the axis falls through to the table/prefix fallback.\n * The route mapping is merchant policy fetched from the dashboard — it\n * outranks agent-supplied headers the way MCP toolGates do.\n */\nexport function resolveHttpPdlss(input: HttpPdlssInput): HttpPdlssResolution {\n const fromHeader = input.astraPurpose ? normalizePurposeHeader(input.astraPurpose) : undefined;\n\n // ── Action axis ──\n let action: string;\n let actionSource: HttpActionSource;\n if (input.routeAction) {\n action = input.routeAction;\n actionSource = 'route_config';\n } else if (input.hasCustomActionExtractor && input.customAction) {\n action = input.customAction;\n actionSource = 'custom_extractor';\n } else if (!input.hasCustomActionExtractor && input.astraAction) {\n action = input.astraAction;\n actionSource = 'header';\n } else if (!input.hasCustomActionExtractor && fromHeader?.actionCandidate) {\n action = fromHeader.actionCandidate;\n actionSource = 'purpose_header_derived';\n } else {\n action = actionForHttpMethod(input.method);\n actionSource = 'method_table';\n }\n\n // ── Purpose axis ──\n let purpose: string | undefined;\n let purposeSource: HttpPurposeSource | undefined;\n if (input.routePurpose) {\n purpose = input.routePurpose;\n purposeSource = 'route_config';\n } else if (input.hasCustomPurposeExtractor) {\n if (input.customPurpose) {\n purpose = input.customPurpose;\n purposeSource = 'custom_extractor';\n }\n } else if (fromHeader) {\n purpose = fromHeader.purpose;\n purposeSource = 'header';\n } else if (input.legacyPurpose) {\n purpose = input.legacyPurpose;\n purposeSource = 'legacy_header';\n } else if (input.queryPurpose) {\n purpose = input.queryPurpose;\n purposeSource = 'query';\n }\n\n if (!purpose) {\n // Keep the pair coherent: an agent that sent only X-Astra-Action (or a\n // route that mapped only the action) gets that action's own category.\n const dot = action.indexOf('.');\n if (dot > 0) {\n purpose = action.slice(0, dot);\n purposeSource = 'action_derived';\n } else {\n purpose = DEFAULT_HTTP_PURPOSE;\n purposeSource = 'transport_default';\n }\n }\n\n return { purpose, action, purposeSource: purposeSource!, actionSource };\n}\n"],"mappings":";AAiBO,IAAM,2BAA6D;AAAA,EACxE,KAAK;AAAA,EACL,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AACV;AAGO,IAAM,sBAAsB;AAG5B,IAAM,uBAAuB;AAE7B,SAAS,oBAAoB,QAAwB;AAC1D,SAAO,yBAAyB,OAAO,YAAY,CAAC,KAAK;AAC3D;AAsDO,SAAS,uBAAuB,OAGrC;AACA,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,MAAI,SAAS,GAAG;AACd,WAAO,EAAE,SAAS,MAAM,MAAM,GAAG,KAAK,EAAE;AAAA,EAC1C;AACA,QAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,MAAI,MAAM,KAAK,MAAM,MAAM,SAAS,GAAG;AACrC,WAAO,EAAE,SAAS,MAAM,MAAM,GAAG,GAAG,GAAG,iBAAiB,MAAM;AAAA,EAChE;AACA,SAAO,EAAE,SAAS,MAAM;AAC1B;AAkBO,SAAS,iBAAiB,OAA4C;AAC3E,QAAM,aAAa,MAAM,eAAe,uBAAuB,MAAM,YAAY,IAAI;AAGrF,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM,aAAa;AACrB,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,MAAM,4BAA4B,MAAM,cAAc;AAC/D,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,CAAC,MAAM,4BAA4B,MAAM,aAAa;AAC/D,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,CAAC,MAAM,4BAA4B,YAAY,iBAAiB;AACzE,aAAS,WAAW;AACpB,mBAAe;AAAA,EACjB,OAAO;AACL,aAAS,oBAAoB,MAAM,MAAM;AACzC,mBAAe;AAAA,EACjB;AAGA,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM,cAAc;AACtB,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB,WAAW,MAAM,2BAA2B;AAC1C,QAAI,MAAM,eAAe;AACvB,gBAAU,MAAM;AAChB,sBAAgB;AAAA,IAClB;AAAA,EACF,WAAW,YAAY;AACrB,cAAU,WAAW;AACrB,oBAAgB;AAAA,EAClB,WAAW,MAAM,eAAe;AAC9B,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB,WAAW,MAAM,cAAc;AAC7B,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB;AAEA,MAAI,CAAC,SAAS;AAGZ,UAAM,MAAM,OAAO,QAAQ,GAAG;AAC9B,QAAI,MAAM,GAAG;AACX,gBAAU,OAAO,MAAM,GAAG,GAAG;AAC7B,sBAAgB;AAAA,IAClB,OAAO;AACL,gBAAU;AACV,sBAAgB;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,QAAQ,eAA+B,aAAa;AACxE;","names":[]}
1
+ {"version":3,"sources":["../../src/adapters/http-pdlss.ts"],"sourcesContent":["/**\n * Shared purpose/action resolver for the HTTP adapters (express + nextjs) —\n * the vocabulary-unification layer.\n *\n * The PDLSS two-axis contract: `purpose` = bare category noun (`shopping`,\n * `data`, custom nouns like `trading`); `action` = dotted verb\n * (`shopping.search`) or an enumerated transport token. Transport verbs\n * (GET/POST/...) NEVER travel as PDLSS actions — they map through the pinned\n * method table below.\n *\n * SELF-CONTAINED on purpose: this published package cannot depend on the\n * private @astrasyncai/pdlss-vocab workspace package, so the constants are\n * duplicated here and pinned strictly equal by a monorepo CI test\n * (vocab-equality.test.ts). Change them in BOTH places or that test fails.\n */\n\n/** Pinned method→action derivation table. */\nexport const HTTP_METHOD_ACTION_TABLE: Readonly<Record<string, string>> = {\n GET: 'data.read',\n HEAD: 'data.read',\n OPTIONS: 'data.read',\n POST: 'data.write',\n PUT: 'data.write',\n PATCH: 'data.write',\n DELETE: 'data.delete',\n};\n\n/** Unknown methods are treated as mutating until proven otherwise. */\nexport const DEFAULT_HTTP_ACTION = 'data.write';\n\n/** Generic category emitted when nothing supplies a domain purpose. */\nexport const DEFAULT_HTTP_PURPOSE = 'data';\n\nexport function actionForHttpMethod(method: string): string {\n return HTTP_METHOD_ACTION_TABLE[method.toUpperCase()] ?? DEFAULT_HTTP_ACTION;\n}\n\nexport type HttpPurposeSource =\n | 'route_config'\n | 'custom_extractor'\n | 'header'\n | 'legacy_header'\n | 'query'\n | 'action_derived'\n | 'transport_default';\n\nexport type HttpActionSource =\n | 'route_config'\n | 'custom_extractor'\n | 'header'\n | 'purpose_header_derived'\n | 'method_table';\n\nexport interface HttpPdlssInput {\n method: string;\n /** `X-Astra-Purpose` header value (agent's declaration). */\n astraPurpose?: string;\n /** `X-Astra-Action` header value (agent's declaration, symmetric with MCP). */\n astraAction?: string;\n /** Legacy `x-purpose` header — deprecated, removal scheduled for 4.0.0. */\n legacyPurpose?: string;\n /** Legacy `?purpose` query param (express only) — deprecated, removal 4.0.0. */\n queryPurpose?: string;\n /** Dashboard route config send-mapping (RouteAccessConfig.purpose/.action). */\n routePurpose?: string;\n routeAction?: string;\n /** Results of the merchant's extractPurpose/extractAction options. */\n customPurpose?: string;\n hasCustomPurposeExtractor?: boolean;\n customAction?: string;\n hasCustomActionExtractor?: boolean;\n}\n\nexport interface HttpPdlssResolution {\n purpose: string;\n action: string;\n purposeSource: HttpPurposeSource;\n actionSource: HttpActionSource;\n}\n\n/**\n * Normalize an `X-Astra-Purpose` header value.\n * - colon form `\"category:action\"` (legacy wire shape): take the prefix; the\n * remainder is free-text and exists in no vocabulary — discard it and let\n * the method table supply the action (matches pre-3.1.0 discard behavior).\n * - dotted form `\"shopping.purchase\"` (the shape older docs taught): prefix\n * becomes the purpose, the WHOLE token becomes an action candidate.\n * - bare token: purpose verbatim.\n */\nexport function normalizePurposeHeader(value: string): {\n purpose: string;\n actionCandidate?: string;\n} {\n const colon = value.indexOf(':');\n if (colon >= 0) {\n return { purpose: value.slice(0, colon) };\n }\n const dot = value.indexOf('.');\n if (dot > 0 && dot < value.length - 1) {\n return { purpose: value.slice(0, dot), actionCandidate: value };\n }\n return { purpose: value };\n}\n\n/**\n * Resolve the {purpose, action} pair for one HTTP request.\n *\n * Precedence (both axes):\n * action: route mapping → extractAction option → X-Astra-Action header →\n * candidate from a dotted X-Astra-Purpose → pinned method table\n * purpose: route mapping → extractPurpose option → X-Astra-Purpose →\n * legacy x-purpose / ?purpose → category prefix of the resolved\n * action → 'data'\n *\n * A configured custom extractor masks the header/legacy/query steps of its\n * axis (it replaces the default chain, as extractPurpose always has); when it\n * returns undefined the axis falls through to the table/prefix fallback.\n * The route mapping is merchant policy fetched from the dashboard — it\n * outranks agent-supplied headers the way MCP toolGates do.\n */\nexport function resolveHttpPdlss(input: HttpPdlssInput): HttpPdlssResolution {\n const fromHeader = input.astraPurpose ? normalizePurposeHeader(input.astraPurpose) : undefined;\n\n // ── Action axis ──\n let action: string;\n let actionSource: HttpActionSource;\n if (input.routeAction) {\n action = input.routeAction;\n actionSource = 'route_config';\n } else if (input.hasCustomActionExtractor && input.customAction) {\n action = input.customAction;\n actionSource = 'custom_extractor';\n } else if (!input.hasCustomActionExtractor && input.astraAction) {\n action = input.astraAction;\n actionSource = 'header';\n } else if (!input.hasCustomActionExtractor && fromHeader?.actionCandidate) {\n action = fromHeader.actionCandidate;\n actionSource = 'purpose_header_derived';\n } else {\n action = actionForHttpMethod(input.method);\n actionSource = 'method_table';\n }\n\n // ── Purpose axis ──\n let purpose: string | undefined;\n let purposeSource: HttpPurposeSource | undefined;\n if (input.routePurpose) {\n purpose = input.routePurpose;\n purposeSource = 'route_config';\n } else if (input.hasCustomPurposeExtractor) {\n if (input.customPurpose) {\n purpose = input.customPurpose;\n purposeSource = 'custom_extractor';\n }\n } else if (fromHeader) {\n purpose = fromHeader.purpose;\n purposeSource = 'header';\n } else if (input.legacyPurpose) {\n purpose = input.legacyPurpose;\n purposeSource = 'legacy_header';\n } else if (input.queryPurpose) {\n purpose = input.queryPurpose;\n purposeSource = 'query';\n }\n\n if (!purpose) {\n // Keep the pair coherent: an agent that sent only X-Astra-Action (or a\n // route that mapped only the action) gets that action's own category.\n const dot = action.indexOf('.');\n if (dot > 0) {\n purpose = action.slice(0, dot);\n purposeSource = 'action_derived';\n } else {\n purpose = DEFAULT_HTTP_PURPOSE;\n purposeSource = 'transport_default';\n }\n }\n\n return { purpose, action, purposeSource: purposeSource!, actionSource };\n}\n"],"mappings":";AAiBO,IAAM,2BAA6D;AAAA,EACxE,KAAK;AAAA,EACL,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AACV;AAGO,IAAM,sBAAsB;AAG5B,IAAM,uBAAuB;AAE7B,SAAS,oBAAoB,QAAwB;AAC1D,SAAO,yBAAyB,OAAO,YAAY,CAAC,KAAK;AAC3D;AAsDO,SAAS,uBAAuB,OAGrC;AACA,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,MAAI,SAAS,GAAG;AACd,WAAO,EAAE,SAAS,MAAM,MAAM,GAAG,KAAK,EAAE;AAAA,EAC1C;AACA,QAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,MAAI,MAAM,KAAK,MAAM,MAAM,SAAS,GAAG;AACrC,WAAO,EAAE,SAAS,MAAM,MAAM,GAAG,GAAG,GAAG,iBAAiB,MAAM;AAAA,EAChE;AACA,SAAO,EAAE,SAAS,MAAM;AAC1B;AAkBO,SAAS,iBAAiB,OAA4C;AAC3E,QAAM,aAAa,MAAM,eAAe,uBAAuB,MAAM,YAAY,IAAI;AAGrF,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM,aAAa;AACrB,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,MAAM,4BAA4B,MAAM,cAAc;AAC/D,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,CAAC,MAAM,4BAA4B,MAAM,aAAa;AAC/D,aAAS,MAAM;AACf,mBAAe;AAAA,EACjB,WAAW,CAAC,MAAM,4BAA4B,YAAY,iBAAiB;AACzE,aAAS,WAAW;AACpB,mBAAe;AAAA,EACjB,OAAO;AACL,aAAS,oBAAoB,MAAM,MAAM;AACzC,mBAAe;AAAA,EACjB;AAGA,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM,cAAc;AACtB,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB,WAAW,MAAM,2BAA2B;AAC1C,QAAI,MAAM,eAAe;AACvB,gBAAU,MAAM;AAChB,sBAAgB;AAAA,IAClB;AAAA,EACF,WAAW,YAAY;AACrB,cAAU,WAAW;AACrB,oBAAgB;AAAA,EAClB,WAAW,MAAM,eAAe;AAC9B,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB,WAAW,MAAM,cAAc;AAC7B,cAAU,MAAM;AAChB,oBAAgB;AAAA,EAClB;AAEA,MAAI,CAAC,SAAS;AAGZ,UAAM,MAAM,OAAO,QAAQ,GAAG;AAC9B,QAAI,MAAM,GAAG;AACX,gBAAU,OAAO,MAAM,GAAG,GAAG;AAC7B,sBAAgB;AAAA,IAClB,OAAO;AACL,gBAAU;AACV,sBAAgB;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,QAAQ,eAA+B,aAAa;AACxE;","names":[]}
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, RequestHandler } from 'express';
2
- import { a as AccessLevel, G as GatewayConfig, w as VerificationResult } from '../types-BntuzEEn.mjs';
2
+ import { a as AccessLevel, G as GatewayConfig, x as VerificationResult } from '../types-r850cOt0.mjs';
3
3
  import '../metadata-capture.mjs';
4
4
 
5
5
  /**
@@ -81,7 +81,7 @@ interface ParsedMcpRequest {
81
81
  */
82
82
  agentIdFromBody?: string;
83
83
  /**
84
- * Round-12 (F19) + round-13 (R13-1): purpose extracted from the MCP body
84
+ * Purpose extracted from the MCP body
85
85
  * with the symmetric precedence chain. Sourced from `_meta.astrasync.purpose`
86
86
  * (canonical SDK location) OR `params.arguments.purpose` (legacy /
87
87
  * conventional callers). The discriminator is on `purposeSourceFromBody`.
@@ -92,7 +92,7 @@ interface ParsedMcpRequest {
92
92
  /** Which body location resolved `purposeFromBody`. */
93
93
  purposeSourceFromBody?: 'meta' | 'tool_argument';
94
94
  /**
95
- * Round-13 (R13-2): action extracted from the MCP body with the same
95
+ * Action extracted from the MCP body with the same
96
96
  * symmetric chain as purpose. Sourced from `_meta.astrasync.action`
97
97
  * (canonical) OR `params.arguments.action` (legacy). Adapter combines
98
98
  * with the `X-Astra-Action` header (header wins) before mapping; final
@@ -130,15 +130,24 @@ declare function parseMcpJsonRpc(body: unknown): ParsedMcpRequest | null;
130
130
  * - `purpose` defaults to `undefined` (backend evaluator applies
131
131
  * skip-when-undefined). Per-tool overrides via `toolGates` config let
132
132
  * merchants declare the semantic purpose each tool fulfils.
133
- * - `action` is the bare tool name for `tools/call`, the JSON-RPC method
134
- * otherwise. Header/body overrides available.
133
+ * - `action` comes only from declarations (toolGate config, header, or
134
+ * body). For `tools/call` with none declared it is `undefined` —
135
+ * evaluation is purpose-only, because a raw tool name is a transport
136
+ * token, not a PDLSS action, and sending it as one guarantees an
137
+ * action-policy mismatch. For other JSON-RPC methods the method string
138
+ * is used.
135
139
  */
136
140
  interface McpPdlssMapping {
137
141
  purpose: string | undefined;
138
- action: string;
142
+ /**
143
+ * `undefined` when a `tools/call` request declared no action anywhere —
144
+ * the verify-access call then evaluates purpose-only. Tool names never
145
+ * travel as PDLSS actions.
146
+ */
147
+ action: string | undefined;
139
148
  resource: string;
140
149
  purposeSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | undefined;
141
- actionSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | 'transport_layer';
150
+ actionSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | 'transport_layer' | undefined;
142
151
  }
143
152
  /**
144
153
  * v2.5.0 — PDLSS field derivation for MCP requests.
@@ -150,11 +159,13 @@ interface McpPdlssMapping {
150
159
  * Resource precedence:
151
160
  * - `toolGate.resource` if provided, else `requestPath`.
152
161
  *
153
- * Action precedence (3.1.0, Bug 14 §4.6 — toolGate override added for
154
- * symmetry with purpose):
162
+ * Action precedence (3.1.0 — toolGate override added for
163
+ * symmetry with purpose; 4.1.0 — the bare-tool-name fallback for
164
+ * `tools/call` was removed):
155
165
  * - `toolGate.action` authoritative → header → body `_meta` → body
156
- * `arguments` → bare tool name / method (transport_layer, unchanged —
157
- * bare tool names are legitimate enumerated actions, never aliased).
166
+ * `arguments` → then: `undefined` for `tools/call` (purpose-only
167
+ * evaluation — tool names are transport tokens, never PDLSS actions),
168
+ * or the JSON-RPC method string for other methods (transport_layer).
158
169
  *
159
170
  * @param requestPath The HTTP request path (e.g. '/mcp'). Required.
160
171
  * @param toolGate Resolved per-tool config from `toolGates`, if present.
@@ -167,7 +178,7 @@ declare function mcpToPdlss(parsed: ParsedMcpRequest, requestPath: string, heade
167
178
  /**
168
179
  * Recommended minimum access level per method type. The MCP middleware uses
169
180
  * this to split low-risk handshake / introspection traffic from high-risk
170
- * tool execution — defect (a) from the cohort-3 review.
181
+ * tool execution.
171
182
  *
172
183
  * - `initialize` / `notifications/initialized` → `none` (handshake must work for unregistered probes)
173
184
  * - `tools/list` / `prompts/list` / `resources/list` → `none` (introspection is public-surface)
@@ -218,12 +229,16 @@ declare function mcpRiskTier(parsed: ParsedMcpRequest): AccessLevel;
218
229
  * createMcpMiddleware({
219
230
  * apiBaseUrl: 'https://astrasync.ai/api',
220
231
  * apiKey: process.env.ASTRASYNC_API_KEY,
221
- * // Optional per-tool overrides — tools not listed get the default tier
222
- * // from `mcpRiskTier` (`tools/call` → 'standard').
232
+ * // Per-tool gates — tools not listed get the default tier from
233
+ * // `mcpRiskTier` (`tools/call` → 'standard'). Use the object form:
234
+ * // gated tools need a PDLSS purpose (and ideally an action).
223
235
  * toolGates: {
224
- * start_checkout: 'standard',
225
- * confirm_purchase: 'full',
226
- * browse_catalog: 'read-only',
236
+ * browse_catalog: { minAccessLevel: 'read-only', purpose: 'shopping' },
237
+ * start_checkout: {
238
+ * minAccessLevel: 'standard',
239
+ * purpose: 'shopping',
240
+ * action: 'shopping.purchase',
241
+ * },
227
242
  * },
228
243
  * }),
229
244
  * yourMcpServerHandler,
@@ -246,10 +261,11 @@ declare global {
246
261
  * `X-Astra-Purpose` header is ignored. This lets the merchant declare what
247
262
  * semantic purpose each tool fulfils rather than trusting agent self-declaration.
248
263
  *
249
- * When `action` is set (Bug 14, §4.6 — symmetric with `purpose`), it is
264
+ * When `action` is set (symmetric with `purpose`), it is
250
265
  * authoritative over `X-Astra-Action` / body declarations, letting the
251
266
  * merchant pin a dotted-verb action (e.g. `shopping.search`) for a tool
252
- * whose callers would otherwise fall to the bare tool-name transport default.
267
+ * whose callers would otherwise leave the action undeclared (purpose-only
268
+ * evaluation — raw tool names never travel as PDLSS actions).
253
269
  *
254
270
  * When `resource` is set, it overrides the default (`req.path`) for that
255
271
  * tool's verify-access call — e.g. mapping `list_products` to `/api/catalog`.
@@ -265,26 +281,31 @@ interface McpMiddlewareOptions extends GatewayConfig {
265
281
  * Per-tool gating for `tools/call` invocations. Tools not listed inherit
266
282
  * the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
267
283
  *
268
- * Accepts both the shorthand access-level string and the full object shape.
269
- * NOTE (3.2.0): the access-level band no longer gates, and a gated `tools/call`
270
- * must carry a resolvable PDLSS **purpose** (the backend requires it). So the
271
- * bare string shorthand — which carries no purpose — only works if the caller
272
- * supplies the purpose another way (an `X-Astra-Purpose` header, or
273
- * `params._meta.astrasync.purpose`); otherwise the call fails fast with a
274
- * `PDLSS_PURPOSE_REQUIRED` 400. Prefer the **object form with `purpose`**:
284
+ * Use the **object form** — it lets the merchant declare the PDLSS
285
+ * `purpose` (required for any gated tool: the backend rejects gated calls
286
+ * with no resolvable purpose) and pin a dotted-verb `action`:
275
287
  * ```typescript
276
288
  * toolGates: {
277
- * browse_catalog: 'read-only', // shorthand — purpose must come
278
- * // from a header / _meta, else 400
279
- * list_products: { purpose: 'shopping', // object form (recommended)
289
+ * list_products: { minAccessLevel: 'read-only',
290
+ * purpose: 'shopping',
280
291
  * action: 'shopping.search',
281
292
  * resource: '/api/catalog' },
282
- * start_checkout: { purpose: 'shopping',
293
+ * start_checkout: { minAccessLevel: 'standard',
294
+ * purpose: 'shopping',
283
295
  * action: 'shopping.purchase',
284
296
  * resource: '/api/checkout/*' },
285
297
  * }
286
298
  * ```
287
299
  *
300
+ * The bare access-level string shorthand (`browse_catalog: 'read-only'`)
301
+ * is a legacy form and a known footgun: it carries no purpose, so the call
302
+ * only succeeds when the AGENT declares one (an `X-Astra-Purpose` header
303
+ * or `params._meta.astrasync.purpose`) — otherwise it fails fast with a
304
+ * `PDLSS_PURPOSE_REQUIRED` 400. It also declares no action, so evaluation
305
+ * is purpose-only (the SDK never sends the raw tool name as the PDLSS
306
+ * action). `createMcpMiddleware` logs a warning at construction for every
307
+ * bare-string gate.
308
+ *
288
309
  * When `tools/call` arrives for a tool not declared in `toolGates`, the SDK
289
310
  * falls back to a risk-tier default based on the method classification
290
311
  * (`mcpRiskTier`). For `tools/call` the fallback is `'standard'`. Best
@@ -324,7 +345,7 @@ interface McpMiddlewareOptions extends GatewayConfig {
324
345
  * If `true`, trust an inbound `X-Astra-Verified-Hop` header to skip
325
346
  * verify-access when it carries a recent marker for the resolved agent.
326
347
  *
327
- * **DEFAULT FLIPPED TO `false` in SDK 2.4.13** (audit F-A1-01). The marker
348
+ * **DEFAULT FLIPPED TO `false` in SDK 2.4.13.** The marker
328
349
  * is plaintext semicolon-delimited with no HMAC, so any client that sets
329
350
  * the header (with the right format + fresh timestamp + matching ASTRA-id)
330
351
  * skipped verify-access entirely. The per-process verify-access result
@@ -335,8 +356,8 @@ interface McpMiddlewareOptions extends GatewayConfig {
335
356
  * the header AND you trust the network path between hops (mTLS, internal
336
357
  * VPC, etc.). For most installs, leave at the safe default.
337
358
  *
338
- * Future SDK release will either remove this option entirely OR add HMAC
339
- * signing to the marker. Tracked as round-19+ work.
359
+ * A future SDK release will either remove this option entirely OR add
360
+ * HMAC signing to the marker.
340
361
  */
341
362
  trustVerifiedHop?: boolean;
342
363
  /** Window for accepting an upstream verified-hop marker. Default 60_000ms. */
@@ -352,9 +373,8 @@ interface McpMiddlewareOptions extends GatewayConfig {
352
373
  * Posture when the MCP middleware itself throws an internal error.
353
374
  * Default `'open'` for backward compatibility in SDK 2.4.13. Shadow logs
354
375
  * record what WOULD be denied with `failOnError: 'closed'` so merchants
355
- * can grep correlationId for impact analysis during the 1-week
356
- * observation window. Default flips to `'closed'` in a follow-up release.
357
- * Audit F-A1-06.
376
+ * can grep correlationId for impact analysis during the observation
377
+ * window. Default flips to `'closed'` in a follow-up release.
358
378
  */
359
379
  failOnError?: 'open' | 'closed';
360
380
  }
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, RequestHandler } from 'express';
2
- import { a as AccessLevel, G as GatewayConfig, w as VerificationResult } from '../types-SDu4Llbe.js';
2
+ import { a as AccessLevel, G as GatewayConfig, x as VerificationResult } from '../types-sMxBT-nM.js';
3
3
  import '../metadata-capture.js';
4
4
 
5
5
  /**
@@ -81,7 +81,7 @@ interface ParsedMcpRequest {
81
81
  */
82
82
  agentIdFromBody?: string;
83
83
  /**
84
- * Round-12 (F19) + round-13 (R13-1): purpose extracted from the MCP body
84
+ * Purpose extracted from the MCP body
85
85
  * with the symmetric precedence chain. Sourced from `_meta.astrasync.purpose`
86
86
  * (canonical SDK location) OR `params.arguments.purpose` (legacy /
87
87
  * conventional callers). The discriminator is on `purposeSourceFromBody`.
@@ -92,7 +92,7 @@ interface ParsedMcpRequest {
92
92
  /** Which body location resolved `purposeFromBody`. */
93
93
  purposeSourceFromBody?: 'meta' | 'tool_argument';
94
94
  /**
95
- * Round-13 (R13-2): action extracted from the MCP body with the same
95
+ * Action extracted from the MCP body with the same
96
96
  * symmetric chain as purpose. Sourced from `_meta.astrasync.action`
97
97
  * (canonical) OR `params.arguments.action` (legacy). Adapter combines
98
98
  * with the `X-Astra-Action` header (header wins) before mapping; final
@@ -130,15 +130,24 @@ declare function parseMcpJsonRpc(body: unknown): ParsedMcpRequest | null;
130
130
  * - `purpose` defaults to `undefined` (backend evaluator applies
131
131
  * skip-when-undefined). Per-tool overrides via `toolGates` config let
132
132
  * merchants declare the semantic purpose each tool fulfils.
133
- * - `action` is the bare tool name for `tools/call`, the JSON-RPC method
134
- * otherwise. Header/body overrides available.
133
+ * - `action` comes only from declarations (toolGate config, header, or
134
+ * body). For `tools/call` with none declared it is `undefined` —
135
+ * evaluation is purpose-only, because a raw tool name is a transport
136
+ * token, not a PDLSS action, and sending it as one guarantees an
137
+ * action-policy mismatch. For other JSON-RPC methods the method string
138
+ * is used.
135
139
  */
136
140
  interface McpPdlssMapping {
137
141
  purpose: string | undefined;
138
- action: string;
142
+ /**
143
+ * `undefined` when a `tools/call` request declared no action anywhere —
144
+ * the verify-access call then evaluates purpose-only. Tool names never
145
+ * travel as PDLSS actions.
146
+ */
147
+ action: string | undefined;
139
148
  resource: string;
140
149
  purposeSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | undefined;
141
- actionSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | 'transport_layer';
150
+ actionSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | 'transport_layer' | undefined;
142
151
  }
143
152
  /**
144
153
  * v2.5.0 — PDLSS field derivation for MCP requests.
@@ -150,11 +159,13 @@ interface McpPdlssMapping {
150
159
  * Resource precedence:
151
160
  * - `toolGate.resource` if provided, else `requestPath`.
152
161
  *
153
- * Action precedence (3.1.0, Bug 14 §4.6 — toolGate override added for
154
- * symmetry with purpose):
162
+ * Action precedence (3.1.0 — toolGate override added for
163
+ * symmetry with purpose; 4.1.0 — the bare-tool-name fallback for
164
+ * `tools/call` was removed):
155
165
  * - `toolGate.action` authoritative → header → body `_meta` → body
156
- * `arguments` → bare tool name / method (transport_layer, unchanged —
157
- * bare tool names are legitimate enumerated actions, never aliased).
166
+ * `arguments` → then: `undefined` for `tools/call` (purpose-only
167
+ * evaluation — tool names are transport tokens, never PDLSS actions),
168
+ * or the JSON-RPC method string for other methods (transport_layer).
158
169
  *
159
170
  * @param requestPath The HTTP request path (e.g. '/mcp'). Required.
160
171
  * @param toolGate Resolved per-tool config from `toolGates`, if present.
@@ -167,7 +178,7 @@ declare function mcpToPdlss(parsed: ParsedMcpRequest, requestPath: string, heade
167
178
  /**
168
179
  * Recommended minimum access level per method type. The MCP middleware uses
169
180
  * this to split low-risk handshake / introspection traffic from high-risk
170
- * tool execution — defect (a) from the cohort-3 review.
181
+ * tool execution.
171
182
  *
172
183
  * - `initialize` / `notifications/initialized` → `none` (handshake must work for unregistered probes)
173
184
  * - `tools/list` / `prompts/list` / `resources/list` → `none` (introspection is public-surface)
@@ -218,12 +229,16 @@ declare function mcpRiskTier(parsed: ParsedMcpRequest): AccessLevel;
218
229
  * createMcpMiddleware({
219
230
  * apiBaseUrl: 'https://astrasync.ai/api',
220
231
  * apiKey: process.env.ASTRASYNC_API_KEY,
221
- * // Optional per-tool overrides — tools not listed get the default tier
222
- * // from `mcpRiskTier` (`tools/call` → 'standard').
232
+ * // Per-tool gates — tools not listed get the default tier from
233
+ * // `mcpRiskTier` (`tools/call` → 'standard'). Use the object form:
234
+ * // gated tools need a PDLSS purpose (and ideally an action).
223
235
  * toolGates: {
224
- * start_checkout: 'standard',
225
- * confirm_purchase: 'full',
226
- * browse_catalog: 'read-only',
236
+ * browse_catalog: { minAccessLevel: 'read-only', purpose: 'shopping' },
237
+ * start_checkout: {
238
+ * minAccessLevel: 'standard',
239
+ * purpose: 'shopping',
240
+ * action: 'shopping.purchase',
241
+ * },
227
242
  * },
228
243
  * }),
229
244
  * yourMcpServerHandler,
@@ -246,10 +261,11 @@ declare global {
246
261
  * `X-Astra-Purpose` header is ignored. This lets the merchant declare what
247
262
  * semantic purpose each tool fulfils rather than trusting agent self-declaration.
248
263
  *
249
- * When `action` is set (Bug 14, §4.6 — symmetric with `purpose`), it is
264
+ * When `action` is set (symmetric with `purpose`), it is
250
265
  * authoritative over `X-Astra-Action` / body declarations, letting the
251
266
  * merchant pin a dotted-verb action (e.g. `shopping.search`) for a tool
252
- * whose callers would otherwise fall to the bare tool-name transport default.
267
+ * whose callers would otherwise leave the action undeclared (purpose-only
268
+ * evaluation — raw tool names never travel as PDLSS actions).
253
269
  *
254
270
  * When `resource` is set, it overrides the default (`req.path`) for that
255
271
  * tool's verify-access call — e.g. mapping `list_products` to `/api/catalog`.
@@ -265,26 +281,31 @@ interface McpMiddlewareOptions extends GatewayConfig {
265
281
  * Per-tool gating for `tools/call` invocations. Tools not listed inherit
266
282
  * the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
267
283
  *
268
- * Accepts both the shorthand access-level string and the full object shape.
269
- * NOTE (3.2.0): the access-level band no longer gates, and a gated `tools/call`
270
- * must carry a resolvable PDLSS **purpose** (the backend requires it). So the
271
- * bare string shorthand — which carries no purpose — only works if the caller
272
- * supplies the purpose another way (an `X-Astra-Purpose` header, or
273
- * `params._meta.astrasync.purpose`); otherwise the call fails fast with a
274
- * `PDLSS_PURPOSE_REQUIRED` 400. Prefer the **object form with `purpose`**:
284
+ * Use the **object form** — it lets the merchant declare the PDLSS
285
+ * `purpose` (required for any gated tool: the backend rejects gated calls
286
+ * with no resolvable purpose) and pin a dotted-verb `action`:
275
287
  * ```typescript
276
288
  * toolGates: {
277
- * browse_catalog: 'read-only', // shorthand — purpose must come
278
- * // from a header / _meta, else 400
279
- * list_products: { purpose: 'shopping', // object form (recommended)
289
+ * list_products: { minAccessLevel: 'read-only',
290
+ * purpose: 'shopping',
280
291
  * action: 'shopping.search',
281
292
  * resource: '/api/catalog' },
282
- * start_checkout: { purpose: 'shopping',
293
+ * start_checkout: { minAccessLevel: 'standard',
294
+ * purpose: 'shopping',
283
295
  * action: 'shopping.purchase',
284
296
  * resource: '/api/checkout/*' },
285
297
  * }
286
298
  * ```
287
299
  *
300
+ * The bare access-level string shorthand (`browse_catalog: 'read-only'`)
301
+ * is a legacy form and a known footgun: it carries no purpose, so the call
302
+ * only succeeds when the AGENT declares one (an `X-Astra-Purpose` header
303
+ * or `params._meta.astrasync.purpose`) — otherwise it fails fast with a
304
+ * `PDLSS_PURPOSE_REQUIRED` 400. It also declares no action, so evaluation
305
+ * is purpose-only (the SDK never sends the raw tool name as the PDLSS
306
+ * action). `createMcpMiddleware` logs a warning at construction for every
307
+ * bare-string gate.
308
+ *
288
309
  * When `tools/call` arrives for a tool not declared in `toolGates`, the SDK
289
310
  * falls back to a risk-tier default based on the method classification
290
311
  * (`mcpRiskTier`). For `tools/call` the fallback is `'standard'`. Best
@@ -324,7 +345,7 @@ interface McpMiddlewareOptions extends GatewayConfig {
324
345
  * If `true`, trust an inbound `X-Astra-Verified-Hop` header to skip
325
346
  * verify-access when it carries a recent marker for the resolved agent.
326
347
  *
327
- * **DEFAULT FLIPPED TO `false` in SDK 2.4.13** (audit F-A1-01). The marker
348
+ * **DEFAULT FLIPPED TO `false` in SDK 2.4.13.** The marker
328
349
  * is plaintext semicolon-delimited with no HMAC, so any client that sets
329
350
  * the header (with the right format + fresh timestamp + matching ASTRA-id)
330
351
  * skipped verify-access entirely. The per-process verify-access result
@@ -335,8 +356,8 @@ interface McpMiddlewareOptions extends GatewayConfig {
335
356
  * the header AND you trust the network path between hops (mTLS, internal
336
357
  * VPC, etc.). For most installs, leave at the safe default.
337
358
  *
338
- * Future SDK release will either remove this option entirely OR add HMAC
339
- * signing to the marker. Tracked as round-19+ work.
359
+ * A future SDK release will either remove this option entirely OR add
360
+ * HMAC signing to the marker.
340
361
  */
341
362
  trustVerifiedHop?: boolean;
342
363
  /** Window for accepting an upstream verified-hop marker. Default 60_000ms. */
@@ -352,9 +373,8 @@ interface McpMiddlewareOptions extends GatewayConfig {
352
373
  * Posture when the MCP middleware itself throws an internal error.
353
374
  * Default `'open'` for backward compatibility in SDK 2.4.13. Shadow logs
354
375
  * record what WOULD be denied with `failOnError: 'closed'` so merchants
355
- * can grep correlationId for impact analysis during the 1-week
356
- * observation window. Default flips to `'closed'` in a follow-up release.
357
- * Audit F-A1-06.
376
+ * can grep correlationId for impact analysis during the observation
377
+ * window. Default flips to `'closed'` in a follow-up release.
358
378
  */
359
379
  failOnError?: 'open' | 'closed';
360
380
  }