@astrasyncai/verification-gateway 5.17.2 → 5.18.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 (103) hide show
  1. package/CHANGELOG.md +1849 -0
  2. package/README.md +141 -7
  3. package/dist/adapters/express.d.mts +1 -1
  4. package/dist/adapters/express.d.ts +1 -1
  5. package/dist/adapters/express.js +30 -12
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +30 -12
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/kit-express.d.mts +63 -0
  10. package/dist/adapters/kit-express.d.ts +63 -0
  11. package/dist/adapters/kit-express.js +88 -0
  12. package/dist/adapters/kit-express.js.map +1 -0
  13. package/dist/adapters/kit-express.mjs +61 -0
  14. package/dist/adapters/kit-express.mjs.map +1 -0
  15. package/dist/adapters/mcp.d.mts +1 -1
  16. package/dist/adapters/mcp.d.ts +1 -1
  17. package/dist/adapters/mcp.js +1 -1
  18. package/dist/adapters/mcp.js.map +1 -1
  19. package/dist/adapters/mcp.mjs +1 -1
  20. package/dist/adapters/mcp.mjs.map +1 -1
  21. package/dist/adapters/nextjs.d.mts +1 -1
  22. package/dist/adapters/nextjs.d.ts +1 -1
  23. package/dist/adapters/nextjs.js +18 -8
  24. package/dist/adapters/nextjs.js.map +1 -1
  25. package/dist/adapters/nextjs.mjs +18 -8
  26. package/dist/adapters/nextjs.mjs.map +1 -1
  27. package/dist/adapters/sdk.d.mts +1 -1
  28. package/dist/adapters/sdk.d.ts +1 -1
  29. package/dist/adapters/sdk.js +1 -1
  30. package/dist/adapters/sdk.js.map +1 -1
  31. package/dist/adapters/sdk.mjs +1 -1
  32. package/dist/adapters/sdk.mjs.map +1 -1
  33. package/dist/agent/index.js +1 -1
  34. package/dist/agent/index.js.map +1 -1
  35. package/dist/agent/index.mjs +1 -1
  36. package/dist/agent/index.mjs.map +1 -1
  37. package/dist/bin/astrasync-claude-hook.js +1 -1
  38. package/dist/bin/astrasync-codex-hook.js +1 -1
  39. package/dist/bin/astrasync-guard.js +1 -1
  40. package/dist/bin/astrasync.js +19 -1
  41. package/dist/browser/background.js +1 -1
  42. package/dist/browser/background.js.map +1 -1
  43. package/dist/browser/background.mjs +1 -1
  44. package/dist/browser/background.mjs.map +1 -1
  45. package/dist/cli/index.js +1 -1
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/cli/index.mjs +1 -1
  48. package/dist/cli/index.mjs.map +1 -1
  49. package/dist/codex/index.js +1 -1
  50. package/dist/codex/index.js.map +1 -1
  51. package/dist/codex/index.mjs +1 -1
  52. package/dist/codex/index.mjs.map +1 -1
  53. package/dist/cursor/extension.js +1 -1
  54. package/dist/cursor/extension.js.map +1 -1
  55. package/dist/cursor/extension.mjs +1 -1
  56. package/dist/cursor/extension.mjs.map +1 -1
  57. package/dist/edge-config.d.mts +1 -1
  58. package/dist/edge-config.d.ts +1 -1
  59. package/dist/edge-config.js +1 -1
  60. package/dist/edge-config.js.map +1 -1
  61. package/dist/edge-config.mjs +1 -1
  62. package/dist/edge-config.mjs.map +1 -1
  63. package/dist/edge-core/index.d.mts +5 -4
  64. package/dist/edge-core/index.d.ts +5 -4
  65. package/dist/edge-core/index.js +18 -8
  66. package/dist/edge-core/index.js.map +1 -1
  67. package/dist/edge-core/index.mjs +18 -8
  68. package/dist/edge-core/index.mjs.map +1 -1
  69. package/dist/gateway/gateway.js +1 -1
  70. package/dist/gateway/gateway.js.map +1 -1
  71. package/dist/gateway/gateway.mjs +1 -1
  72. package/dist/gateway/gateway.mjs.map +1 -1
  73. package/dist/git-trigger/git-hooks.d.mts +1 -1
  74. package/dist/git-trigger/git-hooks.d.ts +1 -1
  75. package/dist/index.d.mts +390 -12
  76. package/dist/index.d.ts +390 -12
  77. package/dist/index.js +766 -230
  78. package/dist/index.js.map +1 -1
  79. package/dist/index.mjs +749 -222
  80. package/dist/index.mjs.map +1 -1
  81. package/dist/registration/index.js +1 -1
  82. package/dist/registration/index.js.map +1 -1
  83. package/dist/registration/index.mjs +1 -1
  84. package/dist/registration/index.mjs.map +1 -1
  85. package/dist/transport/index.d.mts +4 -3
  86. package/dist/transport/index.d.ts +4 -3
  87. package/dist/transport/index.js +1 -1
  88. package/dist/transport/index.js.map +1 -1
  89. package/dist/transport/index.mjs +1 -1
  90. package/dist/transport/index.mjs.map +1 -1
  91. package/dist/{types-vNBvYD7Z.d.mts → types-3ObBpsnf.d.mts} +7 -0
  92. package/dist/{types-DA8LzAOB.d.ts → types-BoxZEYPS.d.ts} +7 -0
  93. package/dist/{types-DRb_plOh.d.mts → types-Cns4l8gX.d.mts} +17 -0
  94. package/dist/{types-DRb_plOh.d.ts → types-Cns4l8gX.d.ts} +17 -0
  95. package/dist/ui/index.d.mts +1 -1
  96. package/dist/ui/index.d.ts +1 -1
  97. package/dist/verify.d.mts +1 -1
  98. package/dist/verify.d.ts +1 -1
  99. package/dist/verify.js +1 -1
  100. package/dist/verify.js.map +1 -1
  101. package/dist/verify.mjs +1 -1
  102. package/dist/verify.mjs.map +1 -1
  103. package/package.json +8 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,1849 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@astrasyncai/verification-gateway` are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
6
+ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Full architectural context for each release lives in the platform spec at
9
+ `docs/research/adapter-architecture-technical-requirements.md`.
10
+
11
+ ## [5.18.0] — 2026-10-16 — checkout kit fixes, orders, Express mounting, MPP interop
12
+
13
+ ### Fixed
14
+
15
+ - **A held purchase never settles (UCP + ACP kits).** The kits only treated a
16
+ `requires_approval` outcome or a `stepUpApproval` with `status: 'pending'`
17
+ as a hold, but verify-access step-up results carry `requiresStepUp`,
18
+ `requiresApproval`, `recommendation: 'step_up_required'` and
19
+ `policyAllowed: true` — so a held purchase reached `settle` and `fulfil`.
20
+ Any of those signals, or a `stepUpApproval` that isn't `consumed`, now holds:
21
+ UCP `requires_escalation` with `continue_url` = the approval page, ACP
22
+ `pending_approval`. A just-redeemed approval (`status: 'consumed'`) proceeds.
23
+ `StepUpApprovalInfo.status?: 'pending' | 'consumed'` is typed.
24
+ - **`settle` → `pending` no longer creates an order.** The session stays
25
+ `complete_in_progress` with no order until your processor confirms; see
26
+ `resolvePending` below.
27
+ - **Concurrent completes can't both charge.** `complete` claims
28
+ `complete_in_progress` before verifying (new optional
29
+ `CheckoutStore.claim(id, from, to)`, an atomic compare-and-set; the memory
30
+ stores implement it). The loser gets the session unchanged plus a
31
+ recoverable `checkout_in_progress` message (UCP) or `409
32
+ checkout_in_progress` (ACP). A store without `claim` logs a one-time warning.
33
+ - **Caller scoping.** Sessions and idempotency keys were scoped to the raw
34
+ `Authorization` header (ACP, `''` when absent — shared by every
35
+ unauthenticated caller) or to the `UCP-Agent` profile (UCP — shared by every
36
+ agent on a platform). They're now scoped to the verified ASTRA-id
37
+ (`kit.handle(req, { callerId })` / `resolveCallerId`), else `Authorization`,
38
+ else `X-Astra-Id`; UCP falls back to the platform profile, ACP refuses a
39
+ request with none (401). Scopes are stored hashed — no bearer token is
40
+ written to your store. Sessions created before the upgrade aren't found
41
+ afterwards. `beginIdempotent` has no shared `anon` scope any more: a request
42
+ with no caller isn't deduplicated, or gets `401 idempotency_caller_required`
43
+ when `required`; `idempotencyMiddleware` / `withIdempotency` also accept
44
+ `X-Astra-Id` as the caller.
45
+ - **UCP `complete` needs exactly one payment instrument** whose `handler_id`
46
+ is one of the profile's `payment_handlers` (handler `id` or name); anything
47
+ else is `payment_failed` and nothing is verified.
48
+ - **UCP `UCP-Agent` is bound to the verified agent.** `checkUcpAgentBinding`
49
+ was exported but unused: after `verifyPurchase` the kit now checks the
50
+ profile against `result.agent.protocolIdentities` (or
51
+ `resolveIdentities(req, result)`); a mismatch is `ucp_agent_mismatch` and
52
+ never settles. `ucpAgentBinding: 'registered'` (default) passes agents with
53
+ no registered profile (bridge-relayed calls); `'strict'` refuses them;
54
+ `'off'` skips it.
55
+ - **UCP GETs don't re-sign.** `render()` signed `ap2.merchant_authorization`
56
+ and wrote the session on every response, including GETs. A GET now returns
57
+ the stored authorization when it signs the same view.
58
+ - **Canceling a completed or canceled checkout is `405`** (UCP and ACP).
59
+ - **Express: credentialed callers on unmatched routes.** The no-match
60
+ pass-through ran before credentials were read, so
61
+ `evaluateAlwaysIfCredentialed` never applied to unmatched routes. Credentials
62
+ are read first; with the flag and an `X-Astra-Id` the request is evaluated in
63
+ observe mode (never vetoes, sets `req.agentVerification`).
64
+ - **`detectCommerce`: signed UCP requests are UCP.** The RFC 9421 branch ran
65
+ first, so a signed UCP call was labelled plain `rfc9421`. UCP is checked
66
+ first and carries the signature as its `rfc9421` artifact.
67
+ - **MPP challenge ids match mppx.** `buildMppChallenge` / `verifyMppChallenge`
68
+ bound a newline-joined `key=value` list; mppx binds pipe-joined positional
69
+ slots `realm|method|intent|request|expires|digest|opaque`. Ids now
70
+ interoperate both ways (ids issued by 5.15.0–5.17.2 no longer verify).
71
+ `buildMppChallenge` takes `meta` (encoded as `opaque`).
72
+
73
+ ### Added
74
+
75
+ - **`kit.resolvePending(sessionId, { status: 'settled' | 'failed', processorRef?, txHash?, chainId?, failureCode? })`**
76
+ (UCP + ACP): call it from your processor's webhook. Settled → reports the
77
+ settlement, runs `fulfil`, completes the session (and sends the UCP order
78
+ webhook); failed → reports it and returns the session to
79
+ `ready_for_complete` / `ready_for_payment`. A second call is a no-op. The
80
+ stored result excludes the settlement token, voucher, mandates and KYAPay
81
+ token. Type `PendingResolution`.
82
+ - **UCP `links`** (`{ type: 'terms_of_service' | 'privacy_policy' |
83
+ 'refund_policy' | 'shipping_policy' | 'faq', url, title? }[]`), returned on
84
+ every checkout and covered by the merchant authorization.
85
+ - **UCP order capability:** `profile.orders: true` adds
86
+ `dev.ucp.shopping.order` (`UCP_ORDER_CAPABILITY`); `GET /orders/{id}`;
87
+ `buildUcpOrder(session)` (type `UcpOrder`); `webhook: { url?, privateKey?,
88
+ keyId?, businessProfileUrl?, attempts?, fetch?, onResult? }` sends the signed
89
+ order entity via `sendUcpWebhook` when an order is created (URL defaults to
90
+ the platform profile's order `config.webhook_url`, key to `signingKey`);
91
+ `kit.sendOrderWebhook(sessionId)` re-sends after an update. Optional
92
+ `CheckoutStore.findByOrderId(orderId)`.
93
+ - **`permalinkBase`** (UCP + ACP): `permalink_url` when `fulfil` returns none.
94
+ ACP `order.line_items` now mirrors the session's line items, plus
95
+ `checkout_session_id`.
96
+ - **ACP:** `API-Version` values are checked (`400 unsupported_api_version` with
97
+ `supported_versions`; `ACP_SUPPORTED_API_VERSIONS`); `Idempotency-Key` and
98
+ `Request-Id` are echoed on responses; `buildAcpWellKnown({ endpoint,
99
+ paymentHandlers, apiVersions?, links? })` and `acpPaymentHandlers()`;
100
+ `AcpCheckoutStore` type.
101
+ - **`expressHandler(kit, { callerId? })`** (root and
102
+ `@astrasyncai/verification-gateway/kit-express`): mounts a fetch-style kit on
103
+ Express and keeps the raw body (`express.raw()` or `req.rawBody`) for
104
+ signature checks; passes `req.agentVerification`'s verified ASTRA-id as the
105
+ caller.
106
+ - **`kit.handle(req, ctx?: KitRequestContext)`** and the `resolveCallerId`
107
+ option (UCP + ACP).
108
+ - **Express `defaultRoutePolicy: 'pass' | 'observe'`** (default `'pass'`):
109
+ `'observe'` evaluates unmatched routes in observe mode for any caller with
110
+ AstraSync credentials.
111
+ - **In-memory store warning:** the kits warn when they run on their in-memory
112
+ stores outside `NODE_ENV=test`.
113
+ - **MPP:** `buildMpp402Response(challenges, problem?)` (402, one
114
+ `WWW-Authenticate` per method, `Cache-Control: no-store`,
115
+ `application/problem+json`); `verifyMppChallenge(challenge, secret, { now?,
116
+ body?, store? })` → `Promise<MppVerifyResult>` checks the body against the
117
+ challenge `digest` and, with a `MppChallengeStore` (`MemoryMppChallengeStore`),
118
+ accepts each challenge once; `mppBodyDigest()`. The `(challenge, secret,
119
+ now?)` boolean form is unchanged.
120
+ - `CHANGELOG.md` ships in the package.
121
+
122
+ ### Notes
123
+
124
+ - mppx stays pinned at 0.8.2. 0.13.x has the same challenge-id binding, but
125
+ installing it raises the monorepo's `viem` (peer `>=2.57.1`) and
126
+ `@x402/core`, and its `eventsource-parser` dependency needs Node 22.12+.
127
+
128
+ ## [5.17.2] — 2026-10-15 — guard browse says where to approve
129
+
130
+ ### Changed
131
+
132
+ - **`astrasync guard browse` prints the approval link** while it waits for the
133
+ owner's passkey (`Approve it at: https://…/activity/pending/<id>`), from the
134
+ sign-in route's new `approvalUrl`. There is no push notification: the owner
135
+ approves in the dashboard while signed in, and the link takes them there.
136
+ - **Fleet enrolment commands** in the docs name the binary:
137
+ `npx -y -p @astrasyncai/verification-gateway astrasync guard …`. The package
138
+ installs several commands, so `npx @astrasyncai/verification-gateway guard …`
139
+ fails with "could not determine executable to run".
140
+
141
+ ## [5.17.1] — 2026-10-13 — one approval for a route that holds its own
142
+
143
+ ### Added
144
+
145
+ - **`approvalHeldByRoute` (Express).** A function that returns true for
146
+ requests whose route asks the owner for approval itself — it creates its own
147
+ hold and answers `202` while it waits, as AstraSync's agent dashboard sign-in
148
+ does. For those requests a result that only needs the owner's approval is
149
+ passed to the route (`req.agentVerification` carries `requiresStepUp` and the
150
+ `failures` naming what needs approving) instead of refused, and verify-access
151
+ is told so it creates no approval of its own. The owner approves once. Any
152
+ deny is still refused, and so is a result that already carries a
153
+ `stepUpApproval`.
154
+
155
+ ```ts
156
+ createMiddleware({
157
+ // …
158
+ approvalHeldByRoute: (req) => req.method === 'POST' && req.path === '/signin',
159
+ });
160
+ ```
161
+
162
+ ### Fixed
163
+
164
+ - **A hold on purpose, duration, scope or sub-agents is no longer described as
165
+ a spending limit.** The refusal now reads "The agent's owner asked to approve
166
+ this kind of request" and lists the server's `pdlss.<dimension>.approval_required`
167
+ entries; `commerce.intent.approval_required` and "Transaction is above the
168
+ autonomous limit" are kept for spending holds only.
169
+ - **Step-up results carry the server's `failures`.** An allowed-but-escalate
170
+ result now includes what needs approving, as a deny always has.
171
+ - **`astrasync guard browse` says why a sign-in was refused.** It read the
172
+ route error shape only, so a gateway refusal printed "no detail". A refused
173
+ status check (wrong device, unknown sign-in) now stops at once instead of
174
+ waiting 10 minutes for an approval that can't arrive.
175
+
176
+ ## [5.17.0] — 2026-10-12 — `astrasync guard browse` (agent dashboard sign-in)
177
+
178
+ ### Added
179
+
180
+ - **`astrasync guard browse`.** Signs an agent in to its owner's AstraSync
181
+ dashboard from this machine (round 28.5). It starts a local device proxy
182
+ for the AstraSync host, asks to sign in through the agent's Workforce Guard
183
+ checkpoint with this machine's Local Guard device credential, waits for the
184
+ owner's passkey approval when the agent's policy asks for it, and prints
185
+ the one-time sign-in URL to open through the proxy. The session is bound to
186
+ this machine: a copied cookie without the device is refused.
187
+ - The proxy carries WebSockets and tunnelled plain HTTP, and serves the
188
+ AstraSync host with a locally generated certificate.
189
+
190
+ ```bash
191
+ astrasync guard browse --checkpoint <checkpoint-url> --slug <agent-slug> \
192
+ [--upstream astrasync-api] [--port <n>] [--host <extra-host>]
193
+ ```
194
+
195
+ - **Flags.** `--checkpoint` (required): the Workforce Guard checkpoint's base
196
+ URL. `--slug` (required): the agent's checkpoint slug. `--upstream`: the
197
+ checkpoint's upstream for the AstraSync API (default `astrasync-api`); the
198
+ sign-in goes to `<checkpoint>/a/<slug>/h/<upstream>/agent-sessions`.
199
+ `--port`: the local proxy port (default: any free port; it listens on
200
+ `127.0.0.1` only). `--host`: one extra hostname that also gets the device
201
+ credential (for example a separate app host); the enrolled AstraSync API
202
+ host always does.
203
+ - **Prerequisites.** The machine is enrolled in the organisation's fleet with
204
+ `astrasync guard enroll <token>` (user scope by default; `--system` enrols
205
+ the whole machine and needs administrator rights). The agent is linked to
206
+ a Workforce Guard checkpoint whose upstream for the AstraSync API forwards
207
+ the device credential, and the agent's policy allows the `dashboard.signin`
208
+ action (off for every agent until the owner allows it; put it on the
209
+ approval list to require the owner's passkey for each sign-in). The site's
210
+ route policy must allow the sign-in route, and the platform requires a
211
+ passed runtime challenge, which the checkpoint answers.
212
+ - **Certificate.** The proxy terminates TLS only for the AstraSync hosts,
213
+ with a self-signed ECDSA P-256 certificate generated in memory at start
214
+ (valid 30 days, never written to disk or added to a trust store). Point the
215
+ automation browser at the proxy and let it ignore certificate errors, e.g.
216
+ Playwright `chromium.launch({ proxy: { server: 'http://127.0.0.1:<port>' } })`
217
+ with `newContext({ ignoreHTTPSErrors: true })`. The proxy re-originates to
218
+ the real host over verified TLS. Every other host is a blind tunnel that
219
+ the proxy never inspects.
220
+ - **One-time code.** Once signed in (or once the owner approves), the command
221
+ prints `<site>/agent-session#code=…`. Open it once, through the proxy,
222
+ within 2 minutes: the code is single use, bound to this machine, and rides
223
+ in the URL fragment so it never reaches a server log or a Referer. The page
224
+ exchanges it for the session cookie and drops it from the address bar.
225
+ - **Session gate.** Every request in an agent session must carry the
226
+ machine's device credential (the proxy adds it; the browser never holds
227
+ it). A request without it, or from another device, ends the session. Each
228
+ request is checked against the agent's policy as `dashboard.read` or
229
+ `dashboard.change`. When the policy asks, or the area is admin, the owner
230
+ approves that exact request with a passkey (202 `OWNER_APPROVAL_REQUIRED`).
231
+ Ctrl-C stops the proxy.
232
+
233
+ ## [5.16.0] — 2026-10-07 — Per-route owners + runtime challenge, governed git
234
+
235
+ ### Fixed
236
+
237
+ - **UCP detection needs the `UCP-Agent` header.** A request to any
238
+ `/checkout-sessions` path was treated as a UCP checkout and verify-access
239
+ replaced the route's purpose with `commerce.unknown` — an ordinary API with
240
+ such a route was denied. UCP platforms always send `UCP-Agent`, so detection
241
+ now requires it.
242
+
243
+ ### Changed (security — breaking for hand-rolled challenge responders)
244
+
245
+ - **`ChallengeHandler` no longer auto-includes.** Since 3.7.1, a handler with
246
+ nothing registered as pending answered with whatever counterparty the
247
+ challenge asked about. That made the runtime challenge prove only that the
248
+ agent's server was up: anyone presenting the agent's ASTRA-id passed. It
249
+ now answers only for counterparties registered with `registerPending()`
250
+ (counted, so concurrent calls to one counterparty don't clear each other).
251
+ - **`AgentClient` does the registering for you:** pass `challengeHandler` to
252
+ the client and each `fetch()` registers its destination (by origin, and by
253
+ `counterpartyId` when you pass the ASTRAE-id) for exactly as long as the
254
+ call is in flight.
255
+ - **Migration:** if you call counterparties without `AgentClient`, wrap each
256
+ call in `registerPending(id)` / `removePending(id)`. Endpoints that require
257
+ the challenge deny calls that aren't pending.
258
+
259
+ ### Added
260
+
261
+ - **Per-route policy fields** (set on the endpoint's routes in the dashboard):
262
+ `allowedOwners` (only agents owned by the listed ASTRAO-/ASTRAD-/ASTRAI-ids
263
+ pass the route) and `requiresRuntimeChallenge` (the runtime challenge on
264
+ this route only). The Express and Next.js middleware forward the matched
265
+ route's values to verify-access as `routePolicy`, which enforces them — the
266
+ agent's owner is resolved by AstraSync and never disclosed to you. Routes
267
+ without them behave exactly as before. `routePolicyOf(route)` and the
268
+ `RoutePolicy` type are exported.
269
+ - **`astrasync guard git-route`**: routes this machine's `git fetch` / `git
270
+ push` to github.com through your organisation's Workforce Guard checkpoint
271
+ (`--checkpoint <url> --slug <connector> --agent <ASTRA-id>`, `--undo`).
272
+ `git commit` and its local hooks are unchanged. The checkpoint checks each
273
+ push against the agent's mandate (protected branches, deletions, repository
274
+ allow-list) and attaches the git host token from the AstraSync vault; the
275
+ machine authenticates with its Local Guard device credential via the new
276
+ `astrasync guard git-credential` helper. No git host token is stored on the
277
+ machine.
278
+
279
+ ## [5.15.0] — 2026-10-06 — Agentic commerce protocols: AP2 mandates, UCP/ACP kits, KYAPay, signing
280
+
281
+ ### Added
282
+
283
+ - **`reportSettlement()` errors:** throws `SettlementReportError` (`statusCode`,
284
+ `code`, `issues`). `isRetryableSettlementError(err)` is true for network
285
+ failures (`statusCode: 0`), 429 and 5xx; every other error is final.
286
+ - **Agent protocol identities:** `result.agent.protocolIdentities` (agent
287
+ signing key, UCP profile, Web Bot Auth key directory, A2A card, x402 wallet,
288
+ network agent ids, and which AstraSync verified).
289
+ - **`checkUcpAgentBinding(header, identities)`** / `parseUcpAgentHeader()`:
290
+ reject a `UCP-Agent` profile that isn't the agent's registered one
291
+ (`ucp_agent_mismatch`, `invalid_ucp_agent`).
292
+ - **Idempotency:** `idempotencyMiddleware()` (Express) and `withIdempotency()`
293
+ (fetch / Next.js) with `MemoryIdempotencyStore` / `RedisIdempotencyStore`.
294
+ Same key + same request replays the stored response
295
+ (`Idempotent-Replayed: true`); store down → 503 `idempotency_unavailable`;
296
+ 5xx responses aren't stored. 24 h.
297
+ - **RFC 9421 signing:** `signHttpRequest()` (ES256 / Ed25519, `tag`, `nonce`,
298
+ RFC 9530 `Content-Digest`). Verification now also checks that a covered
299
+ `Content-Digest` matches the body.
300
+ - **Route patterns:** `:name` matches one path segment; `routePatternToRegExp()`
301
+ / `matchRoutePattern()` are shared by the Express, Next.js and edge
302
+ matchers. Literal characters such as `.` are now escaped (previously `.`
303
+ matched any character).
304
+ - **`StepUpApprovalInfo.approvalUrl`:** the buyer-facing approval page. Return
305
+ it as UCP `continue_url` with `requires_escalation`.
306
+ - **`EdgeConfig.minTls`** (`'1.2' | '1.3'`): agent requests below it get 426 in
307
+ enforce mode and are logged in observe mode; browsers are never gated.
308
+ - **Express / Next.js adapters forward protocol artifacts** (VI, AP2, ACP, UCP,
309
+ MPP, x402 payloads; Visa TAP / Mastercard Agent Pay / Web Bot Auth
310
+ signatures) to verify-access, as the edge already did. Keep `req.rawBody` for
311
+ exact body-signature verification.
312
+
313
+ - **AP2 v0.2 mandates** (delegate SD-JWT, `~~` chains, exact `vct`s):
314
+ `verifyAp2Mandate()` (root signer by kid, KB hops against `cnf`,
315
+ `sd_hash` / `issuer_jwt_hash` binding, expiry, unknown constraints fail,
316
+ `checkout_hash` = SHA-256 of `checkout_jwt`), builders
317
+ (`closedCheckoutMandate`, `closedPaymentMandate`, `openCheckoutMandate`,
318
+ `openPaymentMandate`), `signRootMandate()`, and `createKbHop()` for agents
319
+ closing an open mandate.
320
+ - **`result.mandates`**: AP2 mandates AstraSync issued (merchant lane).
321
+ Pass your signed checkout as `checkoutJwt` (verify / `confirmCheckout`).
322
+ - **`result.kyaToken`**: a KYAPay `kya+jwt` for the agent at your endpoint
323
+ (`hid.email` = where to send the receipt). `verifyKyaPayTokens()` verifies
324
+ `KYAPay-Token` headers (identity tokens only unless you opt in to pay).
325
+ - **UCP 2026-08-25**: `createUcpCheckoutKit()` (create / get / update /
326
+ complete / cancel; negotiation, idempotency, optional signature checks,
327
+ AP2 merchant authorization + mandate checks, escalation →
328
+ `requires_escalation` with the approval URL), `buildUcpProfile()`,
329
+ `fetchUcpProfile()` (SSRF-guarded), `negotiateUcp()`, `signUcpRequest()` /
330
+ `verifyUcpRequest()` / `signUcpResponse()`, `sendUcpWebhook()`, message
331
+ builders.
332
+ - **ACP 2026-04-17**: `createAcpCheckoutKit()` (step-up → `pending_approval`
333
+ with the approval URL; API-Version and Idempotency-Key required).
334
+ - **A-Comm Evidence Protocol (AEP v1.0.3-rc.2):** `AepChain`,
335
+ `aepCanonicalJson`, `aepHash`, `aepSign`, `verifyAepChain`,
336
+ `aepPublicKeyDocument` — reproduces the published conformance vectors
337
+ byte for byte. `result.aep` (when your endpoint lists `aep`): AstraSync's
338
+ policy, delegation and platform-countersigned intent payloads to append to
339
+ your chain.
340
+ - **x402 v2 / MPP helpers** (`buildX402Challenge`, `parseX402Payment`,
341
+ `x402PaymentMatchesOffer`, `buildMppChallenge`, `verifyMppChallenge`,
342
+ `encodeMppReceipt`, …). The merchant's facilitator / processor settles.
343
+
344
+ ### Changed
345
+
346
+ - **Idempotency** follows the spec of your protocol: `profile: 'ucp'`
347
+ (default; raw body, 409) or `'acp'` (canonical JSON, 422
348
+ `idempotency_conflict`; 409 `idempotency_in_flight` with `Retry-After`).
349
+ The key is echoed on responses.
350
+ - **x402 v2 is read from `PAYMENT-SIGNATURE` / `PAYMENT-REQUIRED`** (was the
351
+ body); v1 headers still work. MPP `Payment-Authorization` is detected.
352
+ ACP 2026-04-17 `payment_data.instrument.credential.token` is read.
353
+ - Requests carrying protocol artifacts are never served from or written to
354
+ the verify cache.
355
+ - `RegistryName` gains `'astrasync'` (the platform's composite resolver).
356
+
357
+ ## [5.14.1] — 2026-10-05 — AstraSync never charges; runtime challenge is opt-in
358
+
359
+ ### Changed
360
+
361
+ - **Every purchase is settled by the merchant.** The platform no longer
362
+ charges any card itself. A purchase grant records an order and returns
363
+ `settlementOutcome: { status: 'pending_merchant', orderId }` together with
364
+ the artifact the merchant settles: `settlementToken` for a card on the
365
+ first-party Stripe tenant, or the `settlement` voucher (stablecoin) for any
366
+ merchant. Redeem the voucher, then call `reportSettlement()`.
367
+ - **`reportSettlement()`** accepts `orderId` instead of `sessionId`, and a
368
+ stablecoin `txHash` (+ `chainId`) instead of a card `processorRef`. Every
369
+ merchant may report, not only first-party ones.
370
+ - **The voucher redeem response** no longer carries a `settlement` block (no
371
+ charge happens at redeem). It carries `order: { status: 'redeemed' }` when
372
+ the voucher funded a recorded order.
373
+ - **A voucher is minted only on a purchase leg**: the confirm leg, or an
374
+ unphased call that carries a value. Browse and quote calls no longer mint.
375
+ When the request's commerce protocol already carries the payment
376
+ credential (ACP / MPP token, x402 payment payload, AP2 payment mandate),
377
+ nothing is minted and the order settles by report-back.
378
+ - **Runtime challenge is opt-in.** `enableRuntimeChallenge` defaults to false
379
+ (backend and the Next.js adapter). Ask for it explicitly, or set the
380
+ endpoint's `requiresRuntimeChallenge`.
381
+
382
+ ### Fixed
383
+
384
+ - **A dead agent callback no longer stalls verify.** A callback that times
385
+ out or can't be reached gives the same outcome for 60 seconds without being
386
+ called again, and at most 5 challenges are in flight to one host — the rest
387
+ resolve at once as `timeout`.
388
+
389
+ ### Migration
390
+
391
+ - Merchants that relied on the platform charging first-party cards must now
392
+ charge the `settlementToken` and report back (the 5.5.0 self-settle flow).
393
+
394
+ ## [5.14.0] — 2026-10-03 — edge beacons are no longer stranded in a reclaimed container
395
+
396
+ ### Fixed
397
+
398
+ - **Repeat beacons are no longer held for 60 seconds.** The edge buffer used
399
+ to collect repeats of the same request (same caller, user agent and path)
400
+ into an aggregate that waited until its 60-second window closed. Lambda@Edge
401
+ has no `waitUntil`, so that aggregate was only sent if the same container
402
+ was invoked again after the window. When AWS reclaimed the container first,
403
+ the beacons were lost. On staging on 3 Oct, only ~68% of agent and script
404
+ requests were beaconed. Repeats now merge only into an item that is still
405
+ queued. Otherwise the repeat is a new item, sent on the same flush. Under
406
+ load, repeats still merge while a batch is in flight.
407
+
408
+ ### Added
409
+
410
+ - **Batch metadata identifies the container.** Each batch's `meta` now
411
+ carries `containerId` (one per cold start) and the container's cumulative
412
+ `enqueuedTotal`, `deliveredTotal` and `droppedTotal`. The backend uses them
413
+ to count items left queued in a container that stops reporting, which the
414
+ container's own drop counter can't report.
415
+
416
+ ## [5.13.0] — 2026-10-02 — a key that can't verify for its endpoint fails at deploy
417
+
418
+ ### Added
419
+
420
+ - **The startup self-check names the endpoint.** When `counterpartyId` (or
421
+ `counterpartyUrl`) is configured, the one-time init check sends it to
422
+ `GET /agents/verify-access/self-check`. The backend answers whether this API
423
+ key may verify for that endpoint: verify-access now only accepts a key whose
424
+ account owns the endpoint, or an active member of the organisation that owns
425
+ it. A key from another account, or an unknown endpoint, is logged loudly at
426
+ startup (`strictInit: true` throws) instead of every inbound agent being
427
+ refused with `COUNTERPARTY_KEY_MISMATCH`.
428
+
429
+ - **A refused route-policy read is no longer silent.** When the Express or
430
+ Next.js middleware can't read its endpoint's route policy (HTTP 401/403/404:
431
+ a key from another account, or a wrong `counterpartyId`), it now logs one
432
+ loud error per endpoint saying the gateway is in pass-through mode and no
433
+ agent is being verified. It used to fall through quietly. Transient 5xx
434
+ failures stay quiet and are retried on refresh.
435
+
436
+ ### Changed
437
+
438
+ - **A 403 `COUNTERPARTY_KEY_MISMATCH` is merchant misconfiguration**, like a
439
+ rejected key: `misconfigured: true`, `misconfigCode:
440
+ 'counterparty_key_mismatch'`, failure dimension
441
+ `verify_access.merchant_misconfigured`, and the agent is told to contact the
442
+ site operator. It was previously a retryable `api_error` ("temporarily
443
+ unavailable — retry"), which retrying can never fix.
444
+
445
+ ### Fixed
446
+
447
+ - **`viem` is a declared dependency.** `mppx` 0.8.2 imports `viem` when the
448
+ package loads but only lists it as a peer, so an install that skips peers
449
+ (`legacy-peer-deps`) failed at import with `Cannot find package 'viem'`.
450
+
451
+ ### Notes
452
+
453
+ - Older backends ignore the query and return no `counterparty` block: silent,
454
+ as before.
455
+
456
+ ## [5.12.0] — 2026-10-01 — verification outages are retryable, never an identity deny
457
+
458
+ ### Changed
459
+
460
+ - **Outage posture in the Express, Next.js and MCP adapters and `quickVerify`.**
461
+ When verify-access is unreachable or rate-limited, an observe route or an
462
+ anonymous read (GET/HEAD; for MCP, anything other than `tools/call`) is
463
+ served and tagged `X-Astra-Verification: unavailable`. An enforced route
464
+ returns **503** with `Retry-After` (the backend's value, else 30 s) and
465
+ `VERIFICATION_UNAVAILABLE`. For MCP that is JSON-RPC `-32003`,
466
+ `recommendation: 'error'`, retryable. Previously an outage — including a
467
+ 429 — produced **401 UNAUTHORIZED**, so anonymous catalog reads failed
468
+ closed.
469
+ - `failOnError: 'closed'` now fails with the same retryable 503 instead of an
470
+ identity-shaped 401.
471
+ - **Identity status mapping.** Missing credentials still get 401. A presented
472
+ identifier that is malformed or unknown now gets **403**
473
+ `AGENT_IDENTITY_REJECTED` with registration guidance. A 404 from
474
+ verify-access (unknown agent) is an identity deny, not an `api_error`.
475
+ - `VerificationResult` gains `httpStatus` and `retryAfterMs`. Non-JSON error
476
+ bodies (e.g. a proxy 429) keep their status and `Retry-After`.
477
+ - `reportAttempt(config, report, { onFailure })` checks the response status
478
+ and retries once on 429/5xx, honouring `Retry-After` (capped at 2 s). It
479
+ never throws. `AttemptReport.servedFrom` (`'bridge_cache' | 'merchant'`) is
480
+ new.
481
+ - **Edge beacons are buffered and batched.** The edge core queues beacons in
482
+ memory (up to 1,000 items, dropping the oldest first) and sends batches of
483
+ up to 100 to `/verification-activity/edge-events` with the endpoint's API
484
+ key. An in-flight send carries over to the next invocation. Failed batches
485
+ are retried up to 3 times, and a 429 is honoured. Identical requests within
486
+ 60 s are collapsed into `repeatCount`. Each batch reports `droppedSinceLast`
487
+ and `queueDepth`. This fixes the ~60% beacon loss on Lambda@Edge, where a
488
+ 300 ms budget used to drop single beacons.
489
+ - `reportUnregisteredAttempt` and the Express/Next anonymous beacon send the
490
+ API key when one is configured, so rows are attributed as verified.
491
+ - `confirmCheckout()` accepts `attemptId`.
492
+ - The ChatGPT platform signature now matches `GPTBot` and `OAI-SearchBot`.
493
+ - **A recognised but unregistered agent's read is served.** An allowed
494
+ verify-access response that says `step_up_required` for a caller with no
495
+ verified identity is treated as a grant. Step-up is for a registered
496
+ agent's owner to approve a valued action; an unregistered caller has no
497
+ owner. Older backends sent that verdict for recognised platform agents
498
+ (Claude-User, the AstraSync bridge) on plain reads, and every adapter
499
+ answered **401** "requires human approval" to an anonymous catalog GET
500
+ (since 5.0.0). A registered agent's valued step-up still fails closed.
501
+ - **An unknown agent id is an identity rejection.** The backend's 200
502
+ `agent.lookup` deny (a well-formed id that matches no registered agent) now
503
+ gets **403** `AGENT_IDENTITY_REJECTED` with registration guidance, like the
504
+ 404 mapping, instead of a policy deny. It is never `identityVerified`.
505
+ - **`observe` never vetoes.** With `evaluateAlwaysIfCredentialed`, a
506
+ credentialed call to an `observe` tool (MCP) or route (Express) now passes
507
+ through whatever the verdict, as documented. It used to be denied (MCP:
508
+ 403 / `-32001`, or 400 `PDLSS_PURPOSE_REQUIRED` with no purpose). Enforced
509
+ tools and routes are unchanged.
510
+
511
+ ### Added
512
+
513
+ - `failure-posture` helpers: `isVerificationUnavailable`,
514
+ `unavailableRetryAfterSeconds`, `buildVerificationUnavailableBody`,
515
+ `denyStatusFor`, `isRejectedIdentityClaim`, `VERIFICATION_STATUS_HEADER`.
516
+ - `BeaconBuffer`, `getBeaconBuffer`, `resetBeaconBuffer` (edge core).
517
+
518
+ ### Migration
519
+
520
+ - If you have a custom `onDenied` handler, branch on
521
+ `isVerificationUnavailable(result)` and return 503 + `Retry-After` rather
522
+ than 401.
523
+ - If your MCP host sits behind a proxy, set Express `trust proxy` so the
524
+ caller IP is correct.
525
+
526
+ ## [5.11.0] — 2026-09-29 — Local Guard fleet mode (organisation-managed policy)
527
+
528
+ ### Added
529
+
530
+ - **Fleet mode for Local Guard** (Workforce Guard / Enterprise Governance
531
+ orgs). `astrasync guard enroll <token> [--api-base-url <url>] [--system]`
532
+ enrols the machine with an org-minted token; from then on the Claude Code
533
+ hook, the Codex hook and the Cursor extension enforce the org's **central
534
+ signed policy** (ES256 JWS, `typ fleet-policy+jwt`, `aud
535
+ astrasync:fleet-policy`, verified against the org's own key, pinned at
536
+ enrolment).
537
+ - **Key rotation without re-enrolment.** When the org rotates its key, the
538
+ platform returns a signed rotation chain (`typ fleet-keys+jwt`, each new
539
+ key signed by the one it replaces). The guard follows the chain from its
540
+ pinned key, stops trusting the retired key, and caches the chain in
541
+ `~/.astrasync/fleet/keychain.json` (re-verified from the pin on every
542
+ load). A heartbeat reporting a new `activeKid` triggers the refresh.
543
+ - Enrolled machines **ignore** every local `policy.yaml` / `guard.json`,
544
+ `ASTRASYNC_GUARD_DISABLE`, `ASTRASYNC_GUARD_FAIL_OPEN`, guard.json
545
+ `failOpen` and the passive default — posture, failOpen, governMcp and
546
+ skipTools come only from the signed settings.
547
+ - Fail closed: a bad signature, wrong `aud`/`typ`/`iss`/`sub`, a policy hash
548
+ mismatch, a version rollback or an unreadable enrolment denies. A revoked
549
+ device keeps enforcing its last good policy.
550
+ - `ASTRASYNC_FLEET_REQUIRED=1` (set by managed settings) makes an
551
+ unenrolled machine deny with the enrol instruction.
552
+ - Heartbeat after each decision, at most once per interval with a 1.5 s
553
+ budget (never delays a decision); a changed active policy is fetched,
554
+ verified and swapped in for the next call.
555
+ - Machine-scope enrolment (`--system`: `/etc/astrasync`,
556
+ `/Library/Application Support/AstraSync`, `%ProgramData%\AstraSync`) wins
557
+ over user scope (`~/.astrasync/fleet/`, 0600).
558
+ - `astrasync guard fleet-status` and fleet lines in `astrasync guard status`.
559
+ - `astrasync guard managed-settings [--claude] [--codex] [--out <dir>]`: MDM
560
+ templates — Claude Code `managed-settings.json` (managed PreToolUse hook,
561
+ `allowManagedHooksOnly`, `env.ASTRASYNC_FLEET_REQUIRED`, guard-file deny
562
+ rules) and Codex `requirements.toml` (`allow_managed_hooks_only`,
563
+ `[[hooks.PreToolUse]]`), per OS.
564
+ - Root exports `parseLocalPolicyYaml` / `validateLocalPolicy` (+ result
565
+ types) so the platform validates fleet YAML with the guard's own parser.
566
+ - `resolveGuard()` / `resolveFleet()` (fleet-first resolution);
567
+ `resolveGuardConfig()` keeps its 5.10.0 signature.
568
+ - Claude Code permission deny rules now also cover the fleet enrolment paths
569
+ (`~/.astrasync/fleet/**` and the machine-scope directories).
570
+
571
+ ### Removed
572
+
573
+ - `astrasync guard upgrade` no longer uploads the local policy to
574
+ `POST /api/v1/agents/policies/upload` — that route never existed, so the
575
+ upgrade always failed at that step. The flow now asks for the ASTRA- id of
576
+ an agent registered in the dashboard (optional) instead.
577
+
578
+ Unenrolled machines behave exactly as in 5.10.0.
579
+
580
+ ## [5.10.0] — 2026-09-26 — only an ASTRA id is an agent claim at the edge
581
+
582
+ ### Fixed
583
+
584
+ - **Edge classification: a bare `Authorization: Bearer` JWT (or a non-`kya_`
585
+ `x-api-key`) is no longer tiered `verified`.** verify-access is sent the ASTRA
586
+ id only, so such a request could never verify — under enforce it was always
587
+ denied ("Endpoint policy denies unverified agents"). That self-DoS'd any
588
+ property whose signed-in users call its own API with Bearer tokens (found on
589
+ AstraSync's own staging: first-party chat/OAuth sessions on `/api/*`). These
590
+ requests now classify on their other signals (UA / commerce protocol /
591
+ fingerprint) — a made-up token neither verifies nor bypasses the edge. An
592
+ `X-Astra-Id` (with or without a bearer) is still an agent claim.
593
+ - **A malformed agent id is a deny, not an outage.** A non-canonical id
594
+ (e.g. `ASTRA-bogus123`) makes verify-access answer `400 INVALID_AGENT_ID`;
595
+ the SDK treated that as a transient `verify_access.api_error`, so the edge
596
+ returned 503 "verification unavailable — retry" under enforce. It is now an
597
+ identity deny (`identity.invalid_agent_id`, backend message + guidance) →
598
+ the edge's 403 with registration guidance. Other 400s are unchanged.
599
+
600
+ ## [5.9.0] — 2026-09-25 — `blockchainAnchored` (breaking rename)
601
+
602
+ ### Changed (breaking)
603
+
604
+ - **`VerificationResult.agent.blockchainVerified` → `blockchainAnchored`.** The
605
+ server's `agent.blockchainStatus` values were renamed `verified` → `anchored`
606
+ and `unverified` → `not_anchored` (`pending` / `failed` unchanged). The flag
607
+ only ever meant "this agent's identity record is on-chain" — never an
608
+ identity-verification claim — and "verified" collided with the WHO axis
609
+ (`identityVerified`). Migration: replace `result.agent.blockchainVerified`
610
+ with `result.agent.blockchainAnchored`; direct-API consumers comparing
611
+ `blockchainStatus === 'verified'` compare against `'anchored'`.
612
+
613
+ ## [5.8.0] — 2026-08-20 — attempt-chain redemption (X-Astra-Attempt-Id)
614
+
615
+ ### Added
616
+
617
+ - **Express + Next.js middleware read `X-Astra-Attempt-Id`** (the bridge
618
+ stamps it on every delivered venue call; the edge pipeline already read it)
619
+ and thread the id into the verify-access body. Server-side, a step-up
620
+ approval the owner already granted for the same attempt chain now passes
621
+ every later hop carrying the id — bridge pre-verify, the venue middleware's
622
+ re-verify, and post-failure re-drives — instead of minting a fresh approval
623
+ each time (the Meridian two-hop approval-loop fix). Values not matching
624
+ `att_<32 hex>` are dropped, never forwarded.
625
+ - **README: bridge-fronted hold budgets** — documented the bridge's 30 s
626
+ default / 60 s max response budget (per-route `responseBudgetMs` override)
627
+ and the recommended venue `stepUpMaxWaitMs` posture behind the bridge, plus
628
+ the `X-Astra-Attempt-Id` wire contract for venue-side idempotency.
629
+
630
+ ## [5.7.0] — 2026-08-20 — first-party account keys at the edge + runtimeChallenge not_configured
631
+
632
+ ### Fixed
633
+
634
+ - **Edge classifier: account API keys are first-party, never an agent claim.**
635
+ `classify()` previously tiered ANY `Authorization: Bearer` / `x-api-key`
636
+ credential as a `verified` agent claim, so plain API clients (curl, Postman,
637
+ non-SDK backends) presenting a VALID `kya_…` account key were sent through
638
+ verify-access — which can never verify an account key as an agent — and
639
+ enforce mode 403'd them ("Endpoint policy denies unverified agents"),
640
+ masking key validity and creating a self-DoS vector. A bare account-key
641
+ credential now classifies `self` (pass-through, zero beacons; origin auth
642
+ stays authoritative). An explicit `X-Astra-Id` alongside still classifies
643
+ as an agent claim.
644
+
645
+ ### Changed
646
+
647
+ - **`runtimeChallenge.status`: `not_supported` → `not_configured`** (contract
648
+ correction, matches the backend): the value means THIS agent registered
649
+ without a callback endpoint, not that the capability is unsupported.
650
+
651
+ ## [5.6.0] — 2026-08-11 — fulfilment email: account default + agent override
652
+
653
+ Additive, non-breaking.
654
+
655
+ - **`fulfilment` on `VerificationResult`** (first-party confirm legs that
656
+ progressed — settled, pending_merchant, held, requires_action):
657
+ `{ email, emailSource }`. The platform now ALWAYS resolves a fulfilment
658
+ email for first-party orders: the agent-supplied `buyerEmail` when present
659
+ (`emailSource: 'agent_provided'`), else the buyer's account email
660
+ (`emailSource: 'account'`). Emails arrive canonicalized (trimmed,
661
+ lower-cased). Store it with the pending order; fulfil against it only once
662
+ the order is settled / pending_merchant. Merchant-only lane like
663
+ `settlementToken` — the bridge strips it from the agent plane.
664
+ `emailSource` is an open union: treat unknown sources like
665
+ `'agent_provided'`.
666
+ - **`buyerEmail` on `VerificationRequest`** + a `buyerEmail` option on
667
+ `confirmCheckout()`: transit-only forward of an alternate receipt/delivery
668
+ contact. You no longer need to require agents to collect an email — omit
669
+ it and `fulfilment.email` carries the account default. Precedence when
670
+ both exist: an explicit `buyerEmail` (bridge handoff body or your
671
+ `confirmCheckout` call) wins over the account default.
672
+ - Never emitted on quote legs or for non-first-party counterparties.
673
+
674
+ ## [5.5.0] — 2026-08-09 — merchant self-settlement (interim), open outcome union, fulfilment PII lanes
675
+
676
+ Additive, non-breaking (one behavioral rule tightened — see the open union).
677
+
678
+ - **`settlementToken`** on `VerificationResult` (confirm leg, firstParty
679
+ merchants opted into `settlement_mode='self_settle'`): agent-blind
680
+ `{ stripeCustomerId, stripePaymentMethodId, amountMinor, currency,
681
+ sessionId, orderId, jti, statementSuffix, expiresAt }`. YOUR server charges
682
+ (Stripe idempotency key `voucher:<jti>` + `metadata.jti`) and reports back.
683
+ Merchant-only lane — never expose to the agent plane; the bridge strips it
684
+ defensively.
685
+ - **`client.reportSettlement({ sessionId, status, amountMinor, currency,
686
+ processorRef?, failureCode? })`** — reconciles the buyer dashboard,
687
+ /orders, and the agent's poll surface. Amounts must match the order
688
+ EXACTLY; replays of the same terminal state are safe.
689
+ - **`SettlementOutcomeInfo.status` is now an OPEN union** (`… | (string &
690
+ {})`). Contract rule, effective immediately: treat any status you do not
691
+ recognize (`pending_merchant` is the first) as **pending, never failed** —
692
+ the 5.4.1-era fail-close on an unknown status is what produced phantom
693
+ failed orders in the field.
694
+ - **`no_instrument` on a held/approved session is NON-terminal**: the
695
+ human's approval is NOT consumed; once a card is on file, a re-drive with
696
+ the same `checkoutSessionId` redeems the original approval and settles.
697
+ - **Fulfilment PII lanes** (bridge contract): optional `buyerEmail` rides
698
+ the checkout handoff transit-only (receipts / digital delivery — never
699
+ stored by AstraSync); shipping addresses go agent ↔ merchant DIRECT,
700
+ correlated by `sessionId`, never via AstraSync.
701
+ - `SDK_VERSION` constant re-lockstepped with package.json (had drifted at
702
+ 5.4.1 through the 5.4.2 type-only release).
703
+
704
+ ## [5.4.2] — 2026-08-08 — `requires_approval` settlement outcome (type-only)
705
+
706
+ Type-only, non-breaking at runtime. Held confirm legs now carry an explicit
707
+ `settlementOutcome: { status: 'requires_approval' }` alongside the pending
708
+ `stepUpApproval` — merchants must record the order as _pending approval_ and
709
+ expect a re-drive with the same `checkoutSessionId` (one order row per
710
+ session is normative). Platforms emitted this status ahead of some deployed
711
+ SDKs; if you are on 5.4.1, treat "absent outcome + pending stepUpApproval" —
712
+ and any unknown non-terminal status — identically to `requires_approval`.
713
+
714
+ ## [5.4.1] — 2026-08-06 — confirm-leg cache correctness (UAT2 Bug B)
715
+
716
+ Bugfix, non-breaking. Staging UAT found an under-limit confirm could return
717
+ `unsettled_placeholder` non-deterministically: the verify cache key omitted
718
+ `commercePhase`/`checkoutSessionId`, so a confirm issued within the cache TTL
719
+ of its `start_checkout` quote was served the cached quote-leg grant (which
720
+ carries no `settlementOutcome`) and never reached the backend — settlement
721
+ silently did not run.
722
+
723
+ - **Cache key** now includes `commercePhase` and `checkoutSessionId` (same
724
+ F-A1-07 bug class as the earlier duration/counterparty omissions): a quote
725
+ and a confirm for the same cart can never collide, nor can two carts.
726
+ - **`confirmCheckout()` passes `bypassCache: true`** — belt-and-braces: the
727
+ confirm leg is the only leg that can settle, so it must always reach the
728
+ backend regardless of cache state.
729
+ - **`VerificationGatewayClient` now honors `counterpartyId`** — the
730
+ constructor silently dropped this typed, documented option, so a
731
+ client-driven confirm could only identify its endpoint by URL; with
732
+ multiple endpoints on one origin the first-party resolution was
733
+ non-deterministic and settlement could silently not run.
734
+
735
+ ## [5.4.0] — 2026-08-05 — `confirmCheckout()` client method
736
+
737
+ Additive, non-breaking. Exposes the 5.3.0 confirm leg through the high-level
738
+ client so a first-party merchant no longer has to hand-roll `verify(config,
739
+ request)` or raw `POST /api/agents/verify-access` to drive charge-at-redeem.
740
+
741
+ - **`VerificationGatewayClient.confirmCheckout(options)`** — sends
742
+ `commercePhase: 'confirm'` with `checkoutSessionId` (per-cart idempotency),
743
+ `checkoutItems` (order/receipt plane), `transactionValue`/`currency`, and the
744
+ `counterpartyUrl`/`counterpartyType` that make the grant merchant-mediated.
745
+ `purpose`/`action` default to `shopping` / `shopping.purchase`. Returns the
746
+ full `VerificationResult`; read `.settlementOutcome`
747
+ (`settled` | `failed` | `requires_action` | `no_instrument`, + `orderId`).
748
+ Settlement fires only on this leg — `verify()` still cannot trigger it.
749
+ - **`SettlementOutcomeInfo`** is now exported so consumers can type the outcome.
750
+
751
+ No wire change: `commercePhase`/`checkoutSessionId`/`checkoutItems` were already
752
+ forwarded and `settlementOutcome` already read back in 5.3.0 — this is purely the
753
+ client convenience surface over them.
754
+
755
+ ## [5.3.0] — 2026-08-05 — first-party checkout phase + charge-at-redeem settlement outcome
756
+
757
+ astra-pay card rail (PR #563). Additive, non-breaking: all new fields are
758
+ optional and older backends/agents that never send or read them are unaffected.
759
+ Builds on 5.2.0 (round-11, #564).
760
+
761
+ ### Added
762
+
763
+ - **`VerificationRequest.commercePhase`** `'quote' | 'confirm'` — checkout-leg
764
+ discriminator. First-party settlement (charge-at-redeem on the owner's saved
765
+ instrument) fires ONLY on the `'confirm'` leg; quote/browse legs omit it (or
766
+ send `'quote'`) and never move money.
767
+ - **`VerificationRequest.checkoutSessionId`** — per-cart idempotency key
768
+ forwarded on the confirm leg, so two concurrent confirms for one cart charge
769
+ the owner's card at most once.
770
+ - **`VerificationRequest.checkoutItems`** — the checkout's authoritative line
771
+ items, forwarded on the confirm leg for the order plane (dashboard order
772
+ audit). Informational only — never the money authority (that remains
773
+ `transactionValue` bound into the settlement voucher).
774
+ - **`VerificationResult.settlementOutcome`** + **`SettlementOutcomeInfo`**
775
+ `{ status: 'settled' | 'failed' | 'requires_action' | 'no_instrument',
776
+ orderId?, failureCode? }` — sanitized first-party settlement result, present
777
+ INSTEAD of `settlement` when the counterparty is an AstraSync-operated
778
+ storefront and the backend redeemed the voucher + charged server-side. Carries
779
+ no voucher/instrument material — the settlement channel stays merchant-only.
780
+
781
+ ## [5.1.0] — 2026-08-04 — MCP-connector steering in agent-facing guidance
782
+
783
+ Round-7 "kickstarting" fix: an anonymous agent reading a deny body was steered
784
+ to the human dashboard ("register the agent") even when it was running inside
785
+ an MCP host with the AstraSync connector installed. Guidance now offers the
786
+ connector as the agent-actionable path wherever the backend advertises a
787
+ hosted bridge.
788
+
789
+ ### Added
790
+
791
+ - **`WellKnownAgenticCommerce.mcpEndpoint` / `.mcpDiscoveryUrl`** — optional
792
+ fields the backend's `/.well-known/agentic-commerce` doc emits on
793
+ astrasync.ai hosts (absent on self-hosted backends; validation unchanged, so
794
+ older backends keep working).
795
+ - **`GuidanceInfo.mcp`** `{ endpoint, discoveryUrl?, message? }` — attached to
796
+ registration-shaped guidance (server-denied and no-credentials templates)
797
+ when the well-known cache carries `mcpEndpoint`. The copy states that
798
+ registration through the connector is keyless and human-approved (in-chat
799
+ approval card; the agent never handles credentials). `merchant_misconfig` /
800
+ `api_error` guidance deliberately does NOT steer to the connector —
801
+ registration is not the remedy there — and the in-connector registration
802
+ envelope (`registration/guidance.ts`) is unchanged.
803
+ - **Edge deny body `error.guidance.mcp`** — threaded from `verify()` when the
804
+ well-known cache is warm, with a cold-cache fallback deriving
805
+ `https://mcp.<apex>/mcp` from `apiBaseUrl`, gated to astrasync.ai hosts so a
806
+ self-hosted deployment never advertises a dead endpoint.
807
+
808
+ ## [5.0.0] — 2026-08-02 — remove the `accessLevel` band (breaking)
809
+
810
+ The access-level band (`none` / `restricted` / `read-only` / `standard` / `full`
811
+ / `internal`) is removed entirely. It never gated the access **decision** — the
812
+ server does, resting on a **verified ID + runtime challenge + PDLSS match + trust
813
+ score**. The band had been inert in the adapters since 3.2.0; this release drops
814
+ it from the types, the result, the adapters, the edge core, and the UI so nothing
815
+ consumes or advertises an arbitrary tier.
816
+
817
+ ### Removed (breaking)
818
+
819
+ - **`AccessLevel` type** and all band helpers (`ACCESS_LEVEL_HIERARCHY`,
820
+ `ACCESS_LEVEL_DESCRIPTIONS`, `DEFAULT_TRUST_THRESHOLDS`, `hasMinimumAccess`,
821
+ `getAccessLevelForScore`, `determineAccessLevel`, `getCapabilities`,
822
+ `AccessCapabilities`). `getTrustLevel` / `TRUST_LEVEL_RANGES` survive (moved to
823
+ `trust-levels`; the old `access-levels` module is gone).
824
+ - **`VerificationResult.accessLevel`** and `quickVerify().accessLevel`. Gate on
825
+ `identityVerified` + `policyAllowed` (+ `recommendation` / `stepUpApproval`).
826
+ - **`GatewayConfig.defaultAccessLevel`**, `RouteAccessConfig.minAccessLevel`,
827
+ `commerceShield.guestAccessLevel`.
828
+ - **`VerificationGatewayClient.hasAccess()` / `.getCapabilities()`** (the last
829
+ live band consumers).
830
+ - **`AccessLevelIndicator`** UI component + props.
831
+ - The `X-AstraSync-Access-Level` (Next.js) and `X-AstraSync-Permissions` (edge
832
+ ASAT) response headers.
833
+
834
+ ### Changed (breaking)
835
+
836
+ - **MCP `toolGates` / `methodGates`** values are now `ToolGateConfig | 'observe'`
837
+ (`ToolGateConfig` drops `minAccessLevel`). A gate object is **enforced**; the
838
+ literal `'observe'` passes the tool through unenforced (still evaluated when
839
+ `evaluateAlwaysIfCredentialed`). Replaces the old band value / `'none'` sentinel
840
+ and the bare-string shorthand.
841
+ - **`mcpRiskTier(parsed): AccessLevel`** → **`mcpDefaultEnforced(parsed): boolean`**.
842
+ - **HTTP route gating.** `RouteAccessConfig` gains **`observe?: boolean`** to
843
+ replace the `minAccessLevel: 'none'` observe sentinel. A matched route with no
844
+ `observe` flag is enforced (server decision). Endpoint-level
845
+ `unverifiedAgentPolicy: audit` remains the endpoint-wide observe mechanism.
846
+
847
+ ### Migration
848
+
849
+ - Delete any `accessLevel` / `minAccessLevel` / `defaultAccessLevel` reads — they
850
+ no longer exist. Gate on `identityVerified && policyAllowed`.
851
+ - MCP: `toolGates: { t: 'standard' }` → `{ t: { purpose: '…', action: '…' } }`;
852
+ `toolGates: { t: 'none' }` → `{ t: 'observe' }`.
853
+ - HTTP: a route previously `minAccessLevel: 'none'` → `observe: true`; any other
854
+ band value → drop it (tighten with `minTrustScore`).
855
+
856
+ ## [4.6.0] — 2026-08-02 — canonical two-limit model in the LocalEvaluator
857
+
858
+ Aligns the offline `LocalEvaluator` spend-limit ladder with the backend
859
+ `checkWithinLimits` so a local policy pre-check and the cloud verdict agree.
860
+
861
+ ### Changed (behaviour — review your local policies)
862
+
863
+ - **Two-limit ladder with fallback.** `limits` now has exactly two spend
864
+ thresholds — `autonomousThreshold` (Autonomous Limit) and the existing
865
+ `maxTransactionAmount` (now the **Hard Limit**). Evaluation:
866
+ - `value <= Autonomous Limit` → `ALLOW`
867
+ - `Autonomous Limit < value < Hard Limit` → `MANUAL_REVIEW` (human approval)
868
+ - `value >= Hard Limit` → `DENY`
869
+ The Hard Limit **falls back to the Autonomous Limit** when only the Autonomous
870
+ Limit is declared, and both default to `0`.
871
+ - **Hard-limit boundary is now inclusive (`>=`, was `>`).** A transaction
872
+ exactly at `maxTransactionAmount` is now denied, matching the backend.
873
+ - **A Hard-limit-only policy now steps up sub-cap spend.** Previously a policy
874
+ with only `maxTransactionAmount` allowed everything below it; now (Autonomous
875
+ Limit defaulting to `0`) any positive spend below the Hard Limit returns
876
+ `MANUAL_REVIEW`. **If you want the old auto-allow band, declare an
877
+ `autonomousThreshold`.**
878
+
879
+ ### Added
880
+
881
+ - Optional `limits.autonomousThreshold` on the local policy schema, validated as
882
+ a number and required to be `<= maxTransactionAmount`.
883
+ - `retryable?: boolean` on the verification result — set `true` on a transient
884
+ `verify_access.api_error` (5xx / timeout / CSRF-403). Lets a consumer surface
885
+ an infra outage as error-and-retry instead of a policy denial. Not set on a
886
+ deterministic merchant misconfig (retrying won't help).
887
+
888
+ ### Removed
889
+
890
+ - The enforcement-dead `stepUpThreshold` field is gone from the registration
891
+ `PDLSSLimits` type and the transport formatter (it never carried behaviour).
892
+
893
+ ## [4.3.0] — 2026-07-16 — CloudFront fingerprints, verified-hop everywhere, capture-matrix truth
894
+
895
+ Partner-feedback round: maximal connection metadata on every adapter, the
896
+ cross-hop dedupe marker made real outside MCP, and the capture story
897
+ documented per who-operates-the-CDN. Additive connection keys throughout —
898
+ `schemaVersion` remains 1 per the frozen-capture doctrine.
899
+
900
+ ### Added
901
+
902
+ - **CloudFront fingerprint + geo sources in the SDK derivation**
903
+ (`deriveConnectionFromHeaders`): `cloudfront-viewer-ja3-fingerprint` →
904
+ `tlsFingerprint`, `cloudfront-viewer-ja4-fingerprint` → `ja4`, plus
905
+ `headerOrder`/`headerCount` (header-structure fingerprint), `timeZone`,
906
+ `metroCode`, `countryRegionName`, and `cloudfront-viewer-address` as an
907
+ ip fallback (port-stripped, IPv6-safe). Connection values are truncated
908
+ to the new exported `MAX_CONNECTION_VALUE_CHARS` (256 — the platform's
909
+ hard zod bound; headerOrder routinely exceeds it).
910
+ - **Edge gateway emits `X-Astra-Verified-Hop` toward the origin** whenever
911
+ its verify-access call produced identity (authenticate/authorize depth) —
912
+ SDK-middleware origins behind the TAG no longer double-verify. Helpers
913
+ moved to a shared `verified-hop` module (transport re-exports unchanged).
914
+ - **`trustVerifiedHop` + `verifiedHopMaxAgeMs` on express AND nextjs
915
+ middleware options** (previously documented on express but implemented
916
+ only on MCP): a fresh marker matching the presented X-Astra-Id skips the
917
+ duplicate verify-access. Default off; unsigned dedupe advisory — enable
918
+ only behind a stripping gateway/trusted boundary (README trust-model note).
919
+
920
+ ### Fixed / hardened
921
+
922
+ - Evaluated edge passes now strip an inbound spoofed `X-Astra-Verified-Hop`
923
+ (previously only `x-astrasync-*` was stripped — a spoofed marker could
924
+ reach an origin that trusted it).
925
+ - `x-astra-verified-hop` added to the metadata sanitiser's secret set: the
926
+ short-lived dedupe token never persists in stored metadata.
927
+ - Canonical capture matrix rewritten (README): edge adapter / SDK behind
928
+ own CDN / SDK behind **platform-managed** CDN (IP only — Railway, Render,
929
+ Fly, Heroku don't forward provider geo/TLS headers to customer origins) /
930
+ SDK no CDN, with a fingerprint-availability column.
931
+
932
+ ## [4.2.1] — 2026-07-15 — dependency hygiene: @sd-jwt consolidation
933
+
934
+ ### Changed
935
+
936
+ - Migrated `@sd-jwt/decode` + `@sd-jwt/utils` (both deprecated upstream —
937
+ merged into `@sd-jwt/core` ≥ 0.20.0; the decode deprecation notice cites
938
+ security reference GHSA-f9j6-8p6x-r9j6) to a single `@sd-jwt/core@^0.20.0`
939
+ dependency. Same two functions consumed (`decodeSdJwtSync`, `splitSdJwt`),
940
+ now imported from core; no behavior change (308 transport tests unchanged).
941
+
942
+ ## [4.2.0] — 2026-07-15 — Local Guard: native permissions.deny self-protection
943
+
944
+ Closes the settings self-protection gap: Claude Code rejects schema-invalid
945
+ edits of its own `settings.json` without invoking PreToolUse hooks, and the
946
+ hook only blocks in active posture — so the hook alone cannot fully protect
947
+ the settings file it is registered in. The installer now also writes Claude
948
+ Code's native `permissions.deny` rules, which are evaluated before hooks,
949
+ apply regardless of guard posture, and cannot be removed by a governed agent
950
+ (the removal edit is itself denied — runtime-verified on Claude Code 2.1.210).
951
+
952
+ ### Added
953
+
954
+ - `astrasync guard install-claude-code` now installs `permissions.deny`
955
+ self-protection rules (Edit rules for `.claude/settings.json`,
956
+ `.claude/settings.local.json`, and `.astrasync/**` in both `~/` and `**/`
957
+ anchored forms) alongside the PreToolUse hook; uninstall removes them.
958
+ Rules are Edit()-only — Claude Code's file permission checks match
959
+ `Edit(path)` rules against every file-editing tool and never match
960
+ `Write(path)` rules.
961
+ - New exports from the Claude Code settings installer:
962
+ `installClaudePermissionRules`, `uninstallClaudePermissionRules`,
963
+ `GUARD_PERMISSION_DENY_RULES`, `LEGACY_GUARD_PERMISSION_DENY_RULES`.
964
+ Claude-Code-only — the Codex installer intentionally does not write a
965
+ `permissions` block (hooks.json has no permissions concept).
966
+ - Rule ownership is exact-string match (deny entries carry no marker), so
967
+ uninstall also removes an identical rule the user added independently;
968
+ the CLI prints every removed rule so users can re-add their own.
969
+
970
+ ## [4.1.0] — 2026-07-15 — Trusted Agent Gateway naming, edge core, published-docs fix
971
+
972
+ Naming split (an external product owns "Commerce Shield"): the edge product is
973
+ now the **Trusted Agent Gateway (TAG)**, the dashboard analytics product is
974
+ **Visit Intelligence**, and the unverified-agent overlay is the
975
+ **verification interstitial**. No breaking changes — deprecated aliases keep
976
+ every 4.0.0 import working.
977
+
978
+ ### Added
979
+
980
+ - **`./edge-core` subpath**: the portable Trusted Agent Gateway pipeline
981
+ (request normalization contract, three-tier classification, protocol
982
+ detection, verify orchestration, observe/enforce outcome application) that
983
+ platform adapters compose — `@astrasyncai/adapter-lambda` (CloudFront) and
984
+ the new `@astrasyncai/adapter-edge` (Vercel Edge / Cloudflare Workers /
985
+ Fastly Compute) are thin request/response translators over it.
986
+ - **MCP adapter metadata capture**: `createMcpMiddleware` now captures
987
+ header-level ObservedMetadata automatically on every verify, matching the
988
+ Express and Next.js adapters (it previously captured none).
989
+ - `VerificationInterstitial` / `useVerificationInterstitial` /
990
+ `VerificationInterstitialProps` and the `showInterstitial` middleware
991
+ option — renamed from the Commerce Shield names, which remain as
992
+ `@deprecated` aliases until the next major.
993
+ - `check:dts-leaks` publish gate: built declaration files are scanned for
994
+ internal references before publishing.
995
+ - **CDN-header connection derivation** (`deriveConnectionFromHeaders` +
996
+ shared `buildSdkObservedMetadata`): the Express/Next.js/MCP adapters now
997
+ promote CDN-provided signals (Cloudflare, Fastly, Vercel, CloudFront —
998
+ client IP, geo, ASN, TLS/HTTP version, and Cloudflare JA3/JA4 TLS
999
+ fingerprints) into `ObservedMetadata.connection`. Merchants behind a CDN
1000
+ get the connection layer without an edge worker; edge adapters' native
1001
+ collectors remain authoritative where deployed.
1002
+ - **Dashboard-driven anonymous beacon** (Express + Next.js): pass-through
1003
+ traffic (unmatched routes / `none` routes) from non-browser callers is now
1004
+ beaconed fire-and-forget, sampled at the endpoint's dashboard EdgeConfig
1005
+ rate (platform-fingerprinted agents unsampled; humans and static assets
1006
+ never). Zero request-path latency; disable with `anonymousBeacon: false`.
1007
+ - **`ObservedMetadata.schemaVersion`** (`CAPTURE_SCHEMA_VERSION = 1`),
1008
+ stamped by every emitter: capture semantics are frozen within a major and
1009
+ the version travels with the payload, so mixed 4.x deployments cannot
1010
+ drift silently.
1011
+
1012
+ ### Fixed
1013
+
1014
+ - **MCP tool names no longer travel as PDLSS actions**: a bare-string
1015
+ toolGate caused the raw MCP tool name to be sent as the PDLSS `action`,
1016
+ guaranteeing a policy denial. `tools/call` with no configured or declared
1017
+ action now evaluates purpose-only, and `createMcpMiddleware` warns at
1018
+ construction time about bare-string gates (use the object form with
1019
+ `purpose`/`action`).
1020
+ - **Stale `routes: [...]` example removed** from the Express adapter docs
1021
+ (per-route policy moved to the dashboard in v2.9.7); passing `routes` in
1022
+ middleware options now logs a one-time warning instead of being silently
1023
+ ignored.
1024
+
1025
+ ### Changed
1026
+
1027
+ - README overhauled: Trusted Agent Gateway section with the capture-split
1028
+ matrix (edge = headers + connection layer; Express/Next.js = header-level
1029
+ automatic; MCP = header-level from 4.1.0), and documentation for the
1030
+ `./metadata-capture`, `./platform-signatures`, `./edge-config` exports.
1031
+ - Published doc comments rewritten to self-contained public language
1032
+ (internal spec/workstream/audit references removed from all declaration
1033
+ files).
1034
+
1035
+ ## [4.0.0] — 2026-07-13 — Merchant-misconfig honesty + verify timeout
1036
+
1037
+ A rejected integration API key (revoked/expired/rotated) is now classified
1038
+ as **merchant misconfig**, not a transient `api_error`: inbound agents get
1039
+ an honest 503 instead of a misleading 401 blaming their own identity. Found
1040
+ live: a partner property silently 401'd every credentialed agent for six
1041
+ days on a rotated key.
1042
+
1043
+ ### Breaking
1044
+
1045
+ - **Adapter deny contract for the dead-key class**: when the backend rejects
1046
+ the integration's OWN API key, express/nextjs(API-route)/mcp adapters now
1047
+ respond **503** with `error.code: 'MERCHANT_VERIFICATION_MISCONFIGURED'`
1048
+ (mcp: JSON-RPC code `-32002`) instead of 401 `UNAUTHORIZED`. Integrations
1049
+ matching on the old envelope must adapt. nextjs web pages keep the shield
1050
+ flow.
1051
+ - **`verify()` outbound call now has a hard timeout** (default 10s,
1052
+ `GatewayConfig.verifyTimeoutMs`). Previously a hanging backend hung the
1053
+ merchant's middleware (and every inbound agent behind it) indefinitely;
1054
+ timeouts now surface as the fail-closed `verify_access.api_error` result.
1055
+
1056
+ ### Added
1057
+
1058
+ - **`VerificationResult.misconfigured`** typed marker + guidance source
1059
+ `merchant_misconfig` + failure dimension
1060
+ `verify_access.merchant_misconfigured` (guidance: "your identity and the
1061
+ AstraSync platform are fine — contact the site operator; retrying will not
1062
+ help"). Classified from the backend 401 (`code: api_key_*` when present —
1063
+ a verify-access 401 can only ever mean the caller's own key).
1064
+ - **Init self-check**: with an `apiKey` configured, the one-time init probe
1065
+ also calls `GET /agents/verify-access/self-check` — a dead key logs a loud
1066
+ `console.error` at startup (throws under `strictInit`); a key expiring
1067
+ within 7 days logs a rotation warning. 404 (older backend) is silent.
1068
+
1069
+ ## [3.12.0] — 2026-07-11 — Trust Record capture (§33.5 #3)
1070
+
1071
+ Trust Record capture: attemptId chain correlation (request/response
1072
+ pass-through, excluded from the verdict cache key), considerationSet on
1073
+ verify, `reportAttempt()` for post-hoc consideration/outcome reporting. All
1074
+ fields optional; no behavioural change for existing callers.
1075
+
1076
+ ### Added
1077
+
1078
+ - **`VerificationRequest.attemptId`** (`att_` + 32 lowercase hex): attempt-chain
1079
+ handle correlating the catalog → intent → settlement funnel into one attempt.
1080
+ Forwarded skip-when-undefined; the server mints one when absent and echoes it
1081
+ top-level on every response branch. **`VerificationResult.attemptId`** carries
1082
+ the echo (disjoint from `correlationId`/`sessionId` — an attempt CONTAINS
1083
+ events). On a cache hit the SDK rewrites the cached result's attemptId to the
1084
+ current request's own (or drops it) so a cached verdict never leaks a prior
1085
+ attempt's id.
1086
+ - **`VerificationRequest.considerationSet`** (`ConsiderationSet` /
1087
+ `ConsiderationItem` types): first-party observational data — the offers the
1088
+ agent evaluated at this checkpoint. Items capped at 50 server-side; order
1089
+ chosen-first so purchased counts survive truncation. Excluded from the
1090
+ verdict cache key alongside attemptId (observedMetadata precedent).
1091
+ - **`reportAttempt(config, report)`** → `POST /agents/verify-access/attempt-report`:
1092
+ post-hoc consideration/outcome reporting (`AttemptReport` / `AttemptOutcome`
1093
+ types) for signals that never reached verify-access (cache-served catalogs,
1094
+ transport-side denials, settled/blocked outcomes). Fire-and-forget — never
1095
+ throws; same credentials as verify().
1096
+
1097
+ ## [3.10.0] — 2026-07-08 — Maximal metadata capture (Workstream D)
1098
+
1099
+ Total metadata capture folded into the Commerce Shield console round: every
1100
+ header and connection signal an inbound agent presents is captured (secrets
1101
+ stripped, credential headers reduced to a safe format prefix), forwarded, and
1102
+ used for platform attribution — without adding verify-access decision latency.
1103
+
1104
+ ### Added
1105
+
1106
+ - **`./metadata-capture` module** (subpath + flat root re-exports):
1107
+ `sanitizeHeaders` (deny-lists genuine secrets — `Cookie`, `X-CSRF-Token`,
1108
+ our own `x-astrasync-*`/`kya` credentials, raw signatures; reduces
1109
+ `Authorization`/`X-Api-Key`/`X-Goog-Api-Key` to a NON-secret format prefix
1110
+ like `sk-ant-api03`; keeps everything else verbatim) with size caps (≤64
1111
+ headers, ≤1KB/value, ≤16KB total) that bound jsonb growth. Plus
1112
+ `extractPlatformHeaders`, `extractApiKeyFormat`, and the `ObservedMetadata`
1113
+ type. Pure/edge-safe (no Node built-ins).
1114
+ - **`CallerMetadata.observedMetadata`**: the express + nextjs adapters now
1115
+ forward the full sanitised header map on every verify-access call (local
1116
+ string work; kept OUT of the verify cache key so it doesn't defeat verdict
1117
+ caching). SDK-only merchants (no edge) get the same capture.
1118
+ - **Detection widened**: `PlatformDetectionInput` gains `apiKeyFormat` +
1119
+ `platformHeaders`; `PlatformSignatureDef` gains `apiKeyFormatPatterns` +
1120
+ `headerPatterns`; the seed signatures map `sk-ant`→claude, `sk-proj`→chatgpt,
1121
+ `aiza`/`x-goog-user-project`→gemini, `openai-organization`→chatgpt,
1122
+ `pplx-`→perplexity. A credential format / platform header is a stronger,
1123
+ less-spoofable signal than the User-Agent.
1124
+
1125
+ ### Notes
1126
+
1127
+ Backward-compatible and additive: `observedMetadata` is optional everywhere and
1128
+ absent on pre-3.10.0 callers. `@astrasyncai/adapter-lambda` consumes the new
1129
+ surface to capture CloudFront-Viewer connection signals (IP/ASN/TLS/country).
1130
+
1131
+ ## [3.9.0] — 2026-07-08 — Edge verification config + shared platform signatures (Commerce Shield edge adapter)
1132
+
1133
+ Foundation release for `@astrasyncai/adapter-lambda` (the Commerce Shield
1134
+ pathway-A edge shell, spec §7.4/§33.5 #1).
1135
+
1136
+ ### Added
1137
+
1138
+ - **`EdgeConfig` + `edge-config` module** (`./edge-config` subpath and flat
1139
+ root re-exports): the per-endpoint policy the edge adapter fetches from the
1140
+ dashboard — `mode` (`observe`/`enforce`), `depth`
1141
+ (`classify`/`authenticate`/`authorize`), ordered first-match-wins `pathRules`
1142
+ (case-insensitive `*` globs, regex metachars escaped), anonymous-beacon
1143
+ sampling, and a v1 `failurePosture` locked to `'open'`.
1144
+ `fetchEdgeConfig()` (ETag/If-None-Match aware), `getEdgeConfig()` (60s TTL,
1145
+ stale-while-revalidate, never throws; enforce degrades to observe when the
1146
+ last successful fetch is older than 24h — a dead backend can never keep a
1147
+ site enforcing), `matchEdgePathRule()`, `DEFAULT_EDGE_CONFIG`.
1148
+ - **`platform-signatures` module** (`./platform-signatures` subpath + root
1149
+ re-exports): `PLATFORM_AGENT_SIGNATURES` + `detectPlatformFingerprint()`
1150
+ lifted from the backend's platform-agent detector so backend and edge
1151
+ classify identically (single source of truth).
1152
+ - **`./verify` subpath export** — `verify`, `extractCredentials`,
1153
+ `reportUnregisteredAttempt`, `fetchRoutes` etc. importable without the root
1154
+ barrel (whose `./registration` re-export drags `ethers`); this is the
1155
+ ethers-free surface the edge adapter bundles. `reportUnregisteredAttempt`
1156
+ and `fetchRoutes` also join the root barrel.
1157
+
1158
+ ### Fixed
1159
+
1160
+ - **Version-constant drift**: `SDK_VERSION` (stamped on every verify call) was
1161
+ left at `3.7.1` by the 3.8.0 release, and the root barrel's `VERSION` had
1162
+ been frozen at `3.4.0`. Both now track the package version — `VERSION`
1163
+ re-exports `SDK_VERSION` instead of carrying its own literal.
1164
+
1165
+ ## [3.8.0] — 2026-07-07 — Settlement voucher v2 (port to main)
1166
+
1167
+ _(Backfilled — this release shipped without a changelog entry.)_
1168
+
1169
+ ### Changed
1170
+
1171
+ - **`SettlementArtifact.binding` becomes the v2 union**
1172
+ (`StablecoinSettlementBinding` — integer minor-units, asset-pinned — |
1173
+ `FiatSettlementBinding`). README Settlement Artifacts section rewritten with
1174
+ the voucher lifecycle and guarantee boundary (verify proves authenticity;
1175
+ redeem proves the authorization is live and single-use — it does not check
1176
+ or move funds).
1177
+
1178
+ ## [3.7.1] — 2026-07-05 — ChallengeHandler auto-include (sandbox port)
1179
+
1180
+ ### Changed
1181
+
1182
+ - **`ChallengeHandler` auto-includes the incoming counterparty.** When no
1183
+ counterparties have been pre-registered via `registerPending()`, a challenge
1184
+ response now echoes back the challenge payload's own `counterpartyId`/
1185
+ `counterpartyUrl` instead of an empty list — the correct default for agents
1186
+ that never call `registerPending()`. Pre-registered whitelists behave exactly
1187
+ as before. (Ported from the LMAX POC branch, where the empty default caused
1188
+ spurious challenge failures for agents without explicit pending lists.)
1189
+ - Runtime-challenge docs page: auto-include default documented, plus a warning
1190
+ for the double-appended challenge-path misconfiguration
1191
+ (`…/astrasync/astrasync/challenge`), pre-registration examples, and a raw-HTTP
1192
+ (no-SDK) example.
1193
+
1194
+ ## [3.7.0] — 2026-07-04 — AstraSync Local Guard for Claude Code
1195
+
1196
+ ### Added
1197
+
1198
+ - **`@astrasyncai/adapter-claude-code`** (subpath export `./claude-code`) — Layer 4
1199
+ Local Guard adapter for Anthropic's Claude Code CLI, intercepting tool calls via
1200
+ Claude Code's native PreToolUse hook. Strictly tightening decision mapping:
1201
+ DENY → hook `deny`, MANUAL_REVIEW → hook `ask` (Claude Code's own permission
1202
+ prompt is the human-in-the-loop), ALLOW → no hook output (the guard never emits
1203
+ `allow`, so it cannot bypass Claude Code's native permission system). MCP tools
1204
+ map `mcp__{server}__{tool}` → purpose `{server}.{tool}`, gated by `governMcp`.
1205
+ - **`astrasync-claude-hook` binary** — stateless per-tool-call hook entry.
1206
+ Policy resolution `$CLAUDE_PROJECT_DIR/.astrasync/policy.yaml` → payload cwd →
1207
+ `~/.astrasync/policy.yaml`; `guard.json` (`posture`, `failOpen`, `skipTools`,
1208
+ `governMcp`) beside the policy; JSONL traces beside the policy. Fail-closed on
1209
+ malformed payloads / invalid policy with `ASTRASYNC_GUARD_FAIL_OPEN=1` and
1210
+ `guard.json failOpen` opt-outs; `ASTRASYNC_GUARD_DISABLE=1` kill switch; missing
1211
+ policy anywhere = silent no-op.
1212
+ - **`astrasync guard install-claude-code` / `uninstall-claude-code`** — user-scope
1213
+ (`~/.claude/settings.json`, default) or `--project` install; merge-never-clobber
1214
+ settings editing (idempotent, preserves foreign hooks); generates a default
1215
+ coding-agent policy (permissive risk preset; recommended blocked commands and
1216
+ credential paths; guard self-protection patterns on `file.write`) and a passive
1217
+ `guard.json` for observe-then-enforce rollout. `astrasync guard status` reports
1218
+ Claude Code hook state.
1219
+
1220
+ ### Fixed
1221
+
1222
+ - **Guard CLI was never shipped** — `cli/guard.ts` (`astrasync guard setup/start/…`,
1223
+ Phase A) was missing from tsup entries and the package `bin` map. It now ships as
1224
+ the `astrasync-guard` binary, and the main `astrasync` bin delegates
1225
+ `astrasync guard <cmd>`. Direct-bin argv parsing fixed so flags after the
1226
+ subcommand are not misread as the command.
1227
+
1228
+ ## [3.6.0] — 2026-07-04 — step-up hold-and-poll + LMAX port alignment
1229
+
1230
+ ### Added
1231
+
1232
+ - **Step-up hold-and-poll (opt-in)** — `stepUpMaxWaitMs` / `stepUpPollIntervalMs` on
1233
+ `GatewayConfig`. When enabled, the express/mcp/nextjs middleware holds a
1234
+ `step_up_required` request, polls the approval (`{success, data:{status}}` envelope,
1235
+ flat fallback), and on `approved` re-verifies once cache-bypassed so the backend
1236
+ redeems the single-use approval (server: approved → consumed). Non-approved outcomes
1237
+ fall into the existing fail-closed deny with a new `stepUpOutcome` field
1238
+ (`denied | expired | consumed | timeout`) and an `X-Astra-Step-Up` response header.
1239
+ OFF by default — existing integrations keep the instant-403 contract. Next.js edge
1240
+ runtimes: set a small `stepUpMaxWaitMs` (≤20s) or gate in a Node route handler.
1241
+ - **`verify(config, request, { bypassCache })`** — per-call cache-read bypass (Java
1242
+ SDK parity); also threaded through `verifyAndRecord`.
1243
+ - **Exports**: `awaitStepUpApproval`, `pollStepUpStatus`, `StepUpApprovalStatus`,
1244
+ `StepUpOutcome`, `VerifyOptions`.
1245
+
1246
+ ### Changed
1247
+
1248
+ - **Step-up verdicts are never cached** (was: 300s TTL). A cached `step_up_required`
1249
+ result kept denying retries the owner had already approved and carried a stale
1250
+ pollUrl — the Java SDK 1.1.0 bug, now fixed here too. Not overridable via `cacheTtl`.
1251
+ Autonomous grants keep the 60s default.
1252
+ - **`denialReasons` server precedence** — the backend now emits top-level
1253
+ `denialReasons` on every deny path; the SDK prefers it verbatim and only falls back
1254
+ to client-side reconstruction (failures / recommendationReasons) for older backends.
1255
+ - **`reportUnregisteredAttempt`** — sends the configured `counterpartyId` and omits a
1256
+ null/undefined `counterpartyUrl` (backend attributes id-first and requires at least
1257
+ one of the two).
1258
+
1259
+ ## [3.5.0] — 2026-06-30 — partner feedback round
1260
+
1261
+ ### Added
1262
+
1263
+ - **`extractTransactionValue`** option on `ExpressMiddlewareOptions` — opt-in upfront value
1264
+ extraction so PDLSS limit evaluation runs at middleware time. Must return major units
1265
+ (dollars, not cents). `authorizeSettlement()` remains the required fail-closed settlement gate.
1266
+
1267
+ ### Changed
1268
+
1269
+ - **Settlement architecture** — merchant-mediated grants now enforce voucher-primary: only
1270
+ crypto wallets (`stablecoin_voucher`) mint settlement artifacts; fiat instruments log a
1271
+ diagnostic and do not mint (fiat rails coming soon). Direct-charge adapters (Stripe
1272
+ PaymentIntent, PayPal Order) are never called on merchant-mediated paths.
1273
+ - **Owner-wallet fallback** — when agent's `individualUuid` is null (company-owned agents),
1274
+ settlement resolves wallets via the owner's individuals. Currency/asset hard filter prevents
1275
+ cross-asset-class funding.
1276
+ - **x402 decimal conversion** — `extractX402TransactionValue` converts known tokens (USDC/6,
1277
+ USDT/6, DAI/18, ETH/18, WETH/18) from atomic units to major units. Unknown tokens return
1278
+ `rawUnits: true` which the limit engine rejects fail-closed.
1279
+ - **Currency-formatted denial messages** — hard-limit and step-up messages now show formatted
1280
+ amounts (`$1,800.00 USD` instead of `1800`).
1281
+ - **Purpose dotted-token hint** — when a dotted action is sent as purpose (e.g.
1282
+ `shopping.purchase`), the denial message explains the canonical two-axis contract.
1283
+ - **Limits cross-validation** — `approvalThreshold` must be >= `autonomousThreshold` at
1284
+ registration. Equal values are valid (no HITL band).
1285
+
1286
+ ### Fixed
1287
+
1288
+ - `SDK_VERSION` constant (was `3.3.0`, now `3.5.0`) — fixes drift from `package.json`.
1289
+
1290
+ ### Docs
1291
+
1292
+ - Agent docs: field names corrected (`autonomousLimit` → `autonomousThreshold`, etc.),
1293
+ units/currency callout, settlement statement, registration guidance, purpose/action contract.
1294
+ - Fiat instrument UI (onboarding, profiles): replaced with "Fiat (coming soon)" label.
1295
+
1296
+ ## [3.3.0] — 2026-06-16 — direct-path value enforcement (`authorizeSettlement`)
1297
+
1298
+ ### Added
1299
+
1300
+ - **`authorizeSettlement(config, { agentId, value, currency, purpose?, action? })`** — the
1301
+ value-aware, FAIL-CLOSED gate a direct merchant MUST call before settling a priced cart.
1302
+ The request middleware verifies identity + access but never sends the transaction VALUE to
1303
+ verify-access, so a compliant merchant could settle past the agent's PDLSS spend limits.
1304
+ `authorizeSettlement` wraps `verify()` with the **merchant's authoritative priced total**
1305
+ (never an agent-supplied/spoofable amount) and returns `SettlementDecision { authorized, ... }`.
1306
+ `authorized` is true ONLY on a clean grant — a step-up/approval-band value, a value at/above the
1307
+ Hard Limit, a missing value, or a verify-access error all return `authorized: false`. Settle only
1308
+ when `authorized === true`. See `/docs/merchants#direct-path-settlement`.
1309
+
1310
+ > The request middleware's `requiresHumanApproval` fail-closed (3.2.1) only fires when verify-access
1311
+ > returns a step-up — which never happens without a value. Direct merchants MUST adopt
1312
+ > `authorizeSettlement`. A value-required-route tripwire (deny on missing value) follows as a
1313
+ > hard gate before broad direct-path GA.
1314
+
1315
+ ## [3.2.1] — 2026-06-15 — direct-path step-up fail-closed; F5 guidance
1316
+
1317
+ ### Fixed
1318
+
1319
+ - **Direct-path step-up fail-closed (F2).** The express / nextjs / mcp adapters
1320
+ gated on `identityVerified && policyAllowed` alone. A spending step-up is
1321
+ "allowed-but-escalate" (`policyAllowed: true` + `requiresApproval` /
1322
+ `requiresStepUp`), so the adapter would ADMIT a `[autonomous, hard)`-band
1323
+ purchase the bridge blocks — letting a direct merchant settle it via its own
1324
+ processor. The adapters now also deny when the outcome requires human approval
1325
+ (the approval mechanism is not yet wired), surfacing the
1326
+ `commerce.intent.approval_required` dimension. New shared `approval-gate.ts`;
1327
+ nextjs `/api/*` denials now include `failures[]`.
1328
+ - **Action-mismatch denial guidance (F5).** On the direct path the PDLSS action
1329
+ can resolve to the raw tool name; the guidance now points at configuring a
1330
+ `toolGates` tool→semantic-action mapping instead of telling operators to
1331
+ enumerate transport tool names in the agent's (immutable) PDLSS — which would
1332
+ pollute the semantic vocabulary (the retired-`mcp_invoke` class).
1333
+
1334
+ > Sequencing: direct merchants should adopt **3.2.1 before the backend
1335
+ > promotes**, or a 3.2.0 adapter still admits the step-up — a real-money bypass
1336
+ > window. See the platform spec's promotion gate.
1337
+
1338
+ ## [3.2.0] — 2026-06-12 — access-level band no longer gates; step-up label-truth
1339
+
1340
+ Post-3.1.0 prod-feedback release. Non-breaking minor: the `AccessLevel` band,
1341
+ `RouteAccessConfig.minAccessLevel`, and `hasMinimumAccess` are **retained as
1342
+ informational/deprecated** — but the band **no longer gates** in any adapter.
1343
+
1344
+ ### Changed — the access-level band is informational, not a gate
1345
+
1346
+ - **express / nextjs / mcp adapters:** removed the
1347
+ `hasMinimumAccess(result.accessLevel, route/tool minAccessLevel)` veto from
1348
+ the deny path. Gating is now purely the server-side decision
1349
+ (`identityVerified && policyAllowed`, which already encodes the endpoint
1350
+ trust-floor + PDLSS). The band mapped a trust score into an opaque tier and
1351
+ the per-route comparison duplicated the endpoint policy — and it manufactured
1352
+ the "read-only can't elevate to write" settlement wall. Per-route tightening
1353
+ now uses `route.minTrustScore` (express); explicit per-route/tool
1354
+ condition→constraint rules are the Phase-2 successor.
1355
+ - **MCP toolGate/methodGate:** these still decide _whether_ a tool is gated
1356
+ (any non-`none` tier → verify-access enforced) but **no longer differentiate
1357
+ tiers** — a tool gated above an agent's band is no longer denied on the band.
1358
+ - **`verify.ts` step-up label-truth:** a `step_up_required` recommendation no
1359
+ longer overwrites `accessLevel` to `'read-only'`. That masquerade made a
1360
+ step-up signal look like a low-trust band cap (it caused a downstream
1361
+ misdiagnosis). The real band is preserved; `requiresStepUp` carries the
1362
+ signal.
1363
+ - **Trust score redaction (#2):** agent-visible denial messages are now
1364
+ qualitative — the numeric trust score and the threshold are never embedded
1365
+ in a message returned toward the agent.
1366
+ - **`AstraSyncSDK.hasAccess()`** marked `@deprecated` — explicit opt-in band
1367
+ queries still work, but prefer the server-side policy decision /
1368
+ `minTrustScore`.
1369
+ - **MCP adapter — clear "purpose required" error.** A gated MCP tool call with
1370
+ no resolvable PDLSS purpose (none on the toolGate, no `X-Astra-Purpose`
1371
+ header, none in the body) now fails fast with a `400 PDLSS_PURPOSE_REQUIRED`
1372
+ (dimension `pdlss.purpose`) instead of forwarding an empty purpose to
1373
+ verify-access and surfacing an opaque `verify_access.api_error`. Post-3.2.0,
1374
+ prefer the object toolGate form (`{ purpose, action }`) over the deprecated
1375
+ band string — the band no longer gates and carries no vocabulary.
1376
+
1377
+ ### Fixed — cross-merchant verification cache leak (security)
1378
+
1379
+ - **`verify()` result cache now keys on the merchant `counterpartyId`.** The
1380
+ cache key (`getCacheKey`) was built from the request only; the merchant
1381
+ identity travels via `config.counterpartyId`, so it was absent from the key.
1382
+ Two verifies for the SAME agent/purpose/action/value but DIFFERENT merchants
1383
+ collided — a grant cached at a permissive merchant (low/no trust floor) was
1384
+ served for a stricter merchant, bypassing its trust floor / per-route policy
1385
+ in any long-running consumer (the bridge). Same bug class as the earlier
1386
+ duration-omission fix (F-A1-07). `counterpartyId` now keys the cache.
1387
+ Surfaced by a cross-merchant live probe (a floor-0 grant leaking to a
1388
+ floor-50 merchant). Pre-existing; amplified by the 3.2.0 verify-before-act
1389
+ ordering (which adds a per-merchant `shopping.search` verify).
1390
+
1391
+ ### Migration
1392
+
1393
+ No code change required to upgrade. If you relied on `route.minAccessLevel`
1394
+ to tighten a route, switch to `route.minTrustScore` (a number). Merchants on
1395
+ 3.1.0 keep their band wall until they upgrade — upgrading removes it.
1396
+
1397
+ ## [3.1.0] — 2026-06-11 — canonical PDLSS vocabulary (Bug 14)
1398
+
1399
+ The vocabulary-unification release. Partner prod testing proved **no agent
1400
+ registration shape could transact at an SDK-gated merchant**: the express/
1401
+ nextjs middleware sent `purpose: 'read_data'` (method inference) and
1402
+ `action: 'GET'` (the raw transport verb, with no override surface), while the
1403
+ platform evaluator and docs contract is two-axis — `purpose` = bare category
1404
+ noun (`shopping`), `action` = dotted verb (`shopping.search`). 3.1.0 pins
1405
+ every sender to that contract.
1406
+
1407
+ Retroactive context: 3.0.0's identity-forwarded catalog fetch (#437 B3/B5)
1408
+ moved merchant-side gating onto the bridge checkout critical path — the change
1409
+ that exposed this misalignment and motivates this release.
1410
+
1411
+ ### Changed — middleware emits canonical vocabulary
1412
+
1413
+ - Express/Next.js adapters now resolve a canonical `{purpose, action}` pair.
1414
+ The **pinned method→action table** replaces both gen-1 inferences:
1415
+ GET/HEAD/OPTIONS → `data.read`; POST/PUT/PATCH → `data.write`; DELETE →
1416
+ `data.delete`; unknown → `data.write`. The transport verb is **never** sent
1417
+ as the PDLSS action, and `read_data`/`write_data`/`delete_data`/`general`
1418
+ are no longer emitted as purposes (unmapped fallback purpose is `data`).
1419
+ - A dotted `X-Astra-Purpose` (e.g. `shopping.purchase` — the shape older docs
1420
+ taught) is now normalized: category prefix → `purpose`, whole token →
1421
+ `action` candidate. The colon form (`category:action`) keeps its
1422
+ take-the-prefix behavior; the free-text remainder is still discarded.
1423
+
1424
+ ### Added — config surfaces (both axes, finally symmetric)
1425
+
1426
+ - **Dashboard route send-mapping (the direct-path mechanism):**
1427
+ `RouteAccessConfig` gains optional `purpose` / `action` — the tokens the
1428
+ middleware STAMPS on verify-access calls for a matched route, fetched from
1429
+ `GET /endpoints/:id/routes` like the rest of route policy. Authoritative
1430
+ over agent headers (as MCP toolGates are). Domain merchants (e.g. a trading
1431
+ exchange mapping `/api/orders` → `trading`/`trading.execute`) configure this
1432
+ in the dashboard routes editor — an unmapped route falls to the generic
1433
+ `data`/`data.*` pair, which DENIES domain agents by design.
1434
+ - `X-Astra-Action` header: extracted by express/nextjs (parity with the MCP
1435
+ adapter) and emitted by `setHttpHeaders` alongside the back-compat colon
1436
+ form. `extractAction` middleware option added, symmetric with
1437
+ `extractPurpose`. `ToolGateConfig.action` override added for MCP.
1438
+ - Resolution telemetry: express/nextjs emit `purpose_source`/`action_source`
1439
+ debug lines (same shape as MCP) when `debug: true`.
1440
+
1441
+ ### Breaking-by-policy (platform, not this package's code)
1442
+
1443
+ - **Gen-1 tokens are RETIRED, not aliased**: platform registration now
1444
+ rejects `read_data`/`write_data`/`execute_action`/`delete_data`/`general`
1445
+ with a pointed 400. Existing agents carrying them must retire +
1446
+ re-register (pre-launch posture: no production cohort to migrate).
1447
+ - Registration is now **action-primary**: `allowedActions` required (min 1 —
1448
+ the evaluator is fail-closed on actions), `categories` derived from
1449
+ dotted-action prefixes (explicit entries validated as a bare-noun
1450
+ superset). Dashboard `allowedPurposes` lists must hold bare nouns.
1451
+
1452
+ ### Migration table
1453
+
1454
+ | You were sending/storing | Send/store instead |
1455
+ | --------------------------------------- | --------------------------------------------------------------- |
1456
+ | `purpose: 'read_data'`, `action: 'GET'` | `purpose: 'data'`, `action: 'data.read'` (automatic on upgrade) |
1457
+ | `purpose: 'shopping.purchase'` | `purpose: 'shopping'`, `action: 'shopping.purchase'` |
1458
+ | `categories: ['read_data', ...]` | `allowedActions: ['data.read', ...]` (categories derived) |
1459
+ | `allowedActions: ['GET', 'POST']` | `allowedActions: ['data.read', 'data.write']` or domain verbs |
1460
+ | route `allowedPurposes: ['read_data']` | `allowedPurposes: ['data']` (bare nouns) |
1461
+
1462
+ ### Deprecated (removal in 4.0.0)
1463
+
1464
+ - `x-purpose` header and `?purpose` query param (express) — still resolved,
1465
+ scheduled for removal.
1466
+ - Colon-form `X-Astra-Purpose` (`category:action`) as an action channel — use
1467
+ the dedicated `X-Astra-Action` header.
1468
+
1469
+ ## [3.0.0] — 2026-06-09 — migration guide (2.5 → 3.0)
1470
+
1471
+ Major bump consolidating the consumer-facing breaking changes accumulated across
1472
+ the PR #430 re-test, #437, and this follow-up. **No action is required to keep
1473
+ running 2.5.1** — these only matter when you upgrade or when you read responses
1474
+ from the hosted bridge. Read each item below.
1475
+
1476
+ ### Breaking (SDK) — `buildGuidance` envelope: `registrationUrl` is now an action endpoint
1477
+
1478
+ The no-creds guidance `registrationUrl` was the human dashboard page
1479
+ (`/agents/register`), which an agent cannot use. It is now the **keyless action
1480
+ endpoint** an agent can POST to. The companion `documentationUrl` (the
1481
+ machine-readable contract) is unchanged.
1482
+
1483
+ ```
1484
+ Before (2.5.x): { registrationUrl: ".../agents/register",
1485
+ documentationUrl: ".../docs/agent-access", steps: [...] }
1486
+ After (3.0.0): { registrationUrl: ".../api/agents/request-registration",
1487
+ documentationUrl: ".../docs/agent-access", steps: [...] }
1488
+ ```
1489
+
1490
+ Migration:
1491
+
1492
+ - **POSTed to `registrationUrl` to register?** Keep doing that — it now points at
1493
+ the correct keyless action endpoint instead of an auth-walled HTML page.
1494
+ - **Displayed `registrationUrl` to a user as "where to learn"?** Switch to
1495
+ `documentationUrl`.
1496
+ - `/agents/register` is no longer surfaced by the SDK — it's a human-auth route.
1497
+
1498
+ ### Breaking (hosted bridge, `mcp.astrasync.ai`) — affects callers, not this package's code
1499
+
1500
+ - **`lookup_agent` and `list_agents` removed** (tool count 11 → 9). The backend
1501
+ `GET /agents` and `GET /agents/:id` reject API-key auth (correct security — no
1502
+ cross-account or sibling-agent enumeration), so over the bridge's x-api-key
1503
+ credential these tools could only ever return a 403 deny envelope. Look up your
1504
+ own agent's public metadata via the dashboard or your known ASTRA-id.
1505
+ - **`verify_agent` now requires/defaults a `purpose`.** Pass a PDLSS purpose token
1506
+ (e.g. `shopping.purchase`); it defaults to `identity.verify`. (Previously the
1507
+ bridge omitted it and the backend rejected the call with 400.)
1508
+ - **Upstream failures are classified by status.** A reachable-but-rejecting
1509
+ platform now returns `upstream.validation_failed` (400/422),
1510
+ `upstream.auth_failed` (401), `upstream.access_denied` (403), or
1511
+ `upstream.unexpected_status` (other 4xx). `system.platform_unreachable` is now
1512
+ reserved for genuine reachability failures (5xx/network) — agents that retried
1513
+ on `platform_unreachable` should branch on the new dimensions (fix-input vs
1514
+ re-auth vs escalate).
1515
+ - **`request_registration` rejects secret-shaped fields** (`apiKey`/`privateKey`/
1516
+ `secret`/`token`/…) with a `-32602` validation error instead of silently
1517
+ stripping them.
1518
+ - **Per-email registration rate-limit** returns a structured `429`
1519
+ (`rate_limited` + `retryAfter`) instead of a `202` with a missing `requestId`.
1520
+ - **KYD is enforced at agent approval, per registration path.** API-key
1521
+ registrations on a no-KYD account now create a pending request (approved in the
1522
+ dashboard once KYD is complete) rather than failing at registration; an
1523
+ unapprovable backlog older than 24h returns `owner_kyd_required`.
1524
+
1525
+ ## [2.5.0] — 2026-06-03
1526
+
1527
+ ### Breaking — REST + MCP purpose default
1528
+
1529
+ The SDK no longer injects `'general'` as a default purpose when verify-access
1530
+ requests arrive without an explicit purpose. Undefined purpose now propagates
1531
+ to the backend evaluator, which applies skip-when-undefined per the round-18.6
1532
+ architectural posture.
1533
+
1534
+ Merchants with `allowedPurposes: ['general']` in endpoint policy: the entry
1535
+ continues to match callers that explicitly send `'general'`, but no longer
1536
+ matches callers who send nothing. If you relied on the auto-stamp as a
1537
+ catch-all, declare your intent explicitly or rely on skip-when-undefined.
1538
+
1539
+ ### Breaking — MCP `mcp_invoke` purpose and `mcp:tool/` resource prefix removed
1540
+
1541
+ The MCP middleware no longer stamps transport-coupled defaults on the purpose
1542
+ and resource axes. `mcp_invoke` is removed; `mcp:tool/<name>` is replaced by
1543
+ the HTTP request path (e.g. `/mcp`).
1544
+
1545
+ Use the new `toolGates` object shape to declare per-tool purpose and resource:
1546
+
1547
+ ```typescript
1548
+ toolGates: {
1549
+ list_products: { minAccessLevel: 'none', purpose: 'shopping.search', resource: '/api/catalog' },
1550
+ start_checkout: { minAccessLevel: 'standard', purpose: 'shopping.purchase', resource: '/api/checkout/*' },
1551
+ browse_catalog: 'read-only', // shorthand still works
1552
+ }
1553
+ ```
1554
+
1555
+ See `docs/migration/2.5.0.md` for full before/after behaviour.
1556
+
1557
+ ### Added — `ToolGateConfig` type for per-tool PDLSS mapping
1558
+
1559
+ `toolGates` now accepts both the existing `AccessLevel` shorthand and a new
1560
+ `ToolGateConfig` object with optional `purpose` and `resource` fields. When
1561
+ `purpose` is set on a tool gate, it is authoritative — the agent's
1562
+ `X-Astra-Purpose` header is ignored for that tool.
1563
+
1564
+ ### Added — `/.well-known/agentic-commerce` URL discovery
1565
+
1566
+ The SDK now fetches canonical platform URLs from `/.well-known/agentic-commerce`
1567
+ at initialization (fire-and-forget pre-fetch, lazy await on first verify call).
1568
+ All guidance URLs in denial responses come from this endpoint instead of the
1569
+ previous `apiBaseUrl.replace('/api', '')` string manipulation.
1570
+
1571
+ Cache TTL is 60 minutes with stale-while-revalidate background refresh.
1572
+
1573
+ ### Added — Non-tools/call methods default to `discovery_default` access level
1574
+
1575
+ `resolveMinAccessLevel()` now has an explicit branch for non-tools/call MCP
1576
+ methods (`initialize`, `tools/list`, etc.) returning `'none'` with source
1577
+ `'discovery_default'`. Use `methodGates` to override.
1578
+
1579
+ ### Fixed — `result.guidance` populated on all adapter deny paths
1580
+
1581
+ Adapter-level deny paths (insufficient access level, insufficient trust score,
1582
+ middleware internal error) now backfill `result.guidance` with well-known URLs
1583
+ when the verify-access response didn't include guidance (e.g. success-path
1584
+ results that fail the local access-level gate).
1585
+
1586
+ ### Fixed — `denialReasons` deduplication
1587
+
1588
+ `dedupeFailures()` now deduplicates the legacy `denialReasons` string array
1589
+ alongside the structured `failures` array.
1590
+
1591
+ ## [2.4.0] — 2026-05-11
1592
+
1593
+ ### Added — Agent registration absorbed into the gateway package
1594
+
1595
+ The previously separate agent-side SDK is now part of `@astrasyncai/verification-gateway`. One package, one version, one set of release notes. DevOps brief: stop maintaining two npm releases for what is effectively one platform SDK.
1596
+
1597
+ - **New `/registration` subpath** — `import { AstraSync } from '@astrasyncai/verification-gateway/registration'` exposes the registration client (`register()`, `verify()`, `health()`), config (`AstraSyncConfig`, `RegisterOptions`), error classes (`AstraSyncError`, `KYDRequiredError`, `AuthenticationError`), and the PDLSS declaration types. Class API and shapes are unchanged from the absorbed package — the only change for migrating callers is the import path.
1598
+ - **New `astrasync` CLI binary** — `npx astrasync register --name foo --pdlss ...`, `npx astrasync verify <id>`, `npx astrasync health`. Ships from this package's new `bin` field; no separate install.
1599
+ - **`ethers ^6.13.0` added as a direct dependency.** Powers secp256k1 request signing inside `/registration`. The lazy `await import('ethers')` in the signer is preserved; making ethers a hard dep makes it visible to `npm audit`, dependabot, and SBOM tooling rather than failing at runtime with a missing-module error.
1600
+ - **Subpath-by-role map** lives in `README.md` and `docs/research/adapter-architecture-technical-requirements.md` Appendix C round-8 entry. The round-3/round-7 two-package mnemonic ("verification-gateway = answers, agent-registration = asks") is retired.
1601
+
1602
+ ### Added — `PDLSSConfig` type co-exists across subpaths by design
1603
+
1604
+ `/registration` exports a `PDLSSConfig` for the boundary declaration submitted at register time; `/agent` exports a different-shaped `PDLSSConfig` for the per-request runtime credential injection. Same name, different concept. The root export does not re-export either to keep the umbrella surface unambiguous. Disambiguate via subpath in imports.
1605
+
1606
+ ### Rolled in from round-7 partner feedback (was PR #384 scope)
1607
+
1608
+ All 20 non-rename items from the 6 May onboarding test ship with this release. The package rename to `@astrasyncai/agent-registration` is superseded by the merge — that name was never published to npm.
1609
+
1610
+ - **Backend: typed errors replace opaque 500s.**
1611
+ - `POST /api/agents/register` is now `.strict()` on top-level fields: unknown keys return `400 unsupported_field` with the offending key list (B2). `metadata{}` stays passthrough.
1612
+ - `PUT /api/agents/{id}` detects mutation attempts on `pdlss`, `model`, `framework`, `agentType`, `apiEndpoint` and returns `409 pdlss_immutable` with `immutableFields[]` (B6). `apiEndpoint` removed from `updateAgentSchema`.
1613
+ - `GET /api/agents/{id}/chain-of-custody` resolves ASTRA-id ↔ UUID before querying (root cause of the 500: routes were passing ASTRA-id strings into UUID columns), checks ownership, returns `404`/`403`/typed envelopes (B7). New `agentsService.resolveAgentId()` helper covers both routes.
1614
+ - **Backend: new endpoints + first-class fields.**
1615
+ - `GET /api/agents/request-registration/{requestId}` polling endpoint (Doc5). Persistence reuses `platform_events.eventData` JSONB — no schema migration.
1616
+ - `protocols[]` promoted from `metadata.protocols[]` to a first-class field on `CreateAgentRequest` (Doc4). Backend mirrors top-level → `metadata.protocols` for back-compat.
1617
+ - **Hostname fix.** SDK default `baseUrl` changed from `https://api.kya.astrasync.ai` (NXDOMAIN since the rebrand) → `https://astrasync.ai`. Backend `kydUrl` in 403 responses changed from `https://app.kya.astrasync.ai/developer-profile` → `https://astrasync.ai/developer-profile`. Mechanical sweep across docs, KB articles, frontend doc routes, and the mcp-server agent resources.
1618
+ - **B5 audit confirmed there is NO register-time runtime challenge** — the tester's failure was at first verify-access, not at register. Documented explicitly in `/docs/agent-access`.
1619
+ - **Email template branching (B3).** `agent-outreach` template branches on `recipientHasAccount` — existing accounts get "Sign In to Accept" (links to `/dashboard/agents/pending`); new users get "Create Your Account" (links to `/auth/register`). Subject line, headline, body intro, and CTA all branch.
1620
+ - **Dashboard parity with the registration SDK (Doc8).** Agent-creation form at `/dashboard/agents/register` now exposes `model{}` and `framework{}` field groups in step 2, matching `RegisterOptions` from `/registration`. Dashboard-registered agents now hit the same trust-score baseline as SDK-registered ones.
1621
+ - **Trust score tooltip (D4).** `registration-success-dialog.tsx` adds a tooltip clarifying the score is recomputed at runtime on every request, improves with metadata completeness, and is intentionally opaque (anti-gaming).
1622
+ - **Customer-facing docs.** Six items (D1–D6) added across `/docs/pdlss` and the new `/docs/agent-access` section: anti-drift rationale, PDLSS immutability up-front, trust-score dynamics, self-instantiation clarification, step-up rules table, runtime-challenge timing callout. Plus Doc1 worked register-call example and Doc2 cross-links.
1623
+
1624
+ ### Captured follow-ups (deferred to a separate feature-PR)
1625
+
1626
+ - B1 + B4 (paired): async owner-approval flow on API-key registrations + pending-approval inbox/banner. Needs a persistent approval-request table, magic-link handler, and dashboard inbox.
1627
+ - MFA implementation for API-key step-up.
1628
+ - `app.kya.astrasync.ai` references in env configs / Stripe webhooks / other places the in-repo grep doesn't catch.
1629
+ - Round-6 leftovers: `ENTERPRISE_ADMIN_EMAIL` env on staging, local-override row icon colour, `GET /api/version` endpoint.
1630
+
1631
+ ### Post-merge actions (manual, not done by this PR)
1632
+
1633
+ - Push tag `@astrasyncai/verification-gateway@2.4.0` from `main` at the merge commit → existing `publish-gateway.yml` workflow validates and publishes via Trusted Publishing OIDC.
1634
+ - `npm deprecate "@astrasyncai/sdk@>=0.0.0" "Renamed to @astrasyncai/verification-gateway. See https://www.npmjs.com/package/@astrasyncai/verification-gateway"` — the legacy package on npm from before round-7.
1635
+ - `@astrasyncai/agent-registration` does NOT need deprecation; it was never published.
1636
+
1637
+ ## [2.2.3] — 2026-05-05
1638
+
1639
+ ### Changed — Server is the single source of truth for access decisions
1640
+
1641
+ This is the branch-validation release for the partner-feedback fixes. The same code ships as `2.3.0` on tag-publish once the verification matrix passes against UAT.
1642
+
1643
+ - **`apiBaseUrl` default corrected** (`src/verify.ts`) — was `https://api.astrasync.ai` which matched neither prod nor staging OpenAPI servers. Now `https://astrasync.ai/api`. Always include the `/api` path prefix when overriding; SDK derives registration / docs URLs by stripping it.
1644
+ - **Init self-test on first `verify()` call** — fires a HEAD probe to `${apiBaseUrl}/agents/verify-access` and warns once if the response content-type is `text/html` (the marketing-404 trap). Behind `disableInitChecks?: boolean` for tests.
1645
+ - **`access.accessLevel` read verbatim from server** (`src/verify.ts:418-425`) — the local `determineAccessLevel(...)` call is removed. The server's PDLSS engine resolves the level from endpoint policy + agent's live trust score using canonical thresholds; the SDK no longer diverges. Closes partner feedback #20.
1646
+ - **`minTrustScore` / `minTrustScoreForFull` config fields deprecated** (`src/types.ts`) — setting them no longer affects access decisions. A one-shot `console.warn` fires if either is set on a `verify()` call. To gate access, configure `trust_score_requirement` server-side via the endpoint registration.
1647
+
1648
+ ### Added — Anonymous traffic + canonical-flow contract
1649
+
1650
+ - **Anonymous `verify()` calls forward to the server** — the SDK no longer short-circuits when credentials are missing. The server applies the endpoint's `unverifiedAgentPolicy` (deny / allow_partial / allow_full) and returns `{access, advisory, recommendation}`. Closes partner feedback #1 and #1b.
1651
+ - **`counterpartyId` config field** — your ASTRAE-id, issued at endpoint registration. Forwarded on every `verify-access` call so the server attributes traffic directly by endpoint rather than URL resolution. Closes partner feedback #3.
1652
+ - **Header aliases on `extractCredentials`** (`src/verify.ts:99`) — accepts `X-Astra-Id`, `X-Astra-AgentId`, `x-astra-agent-id`, plus their casing permutations. Closes partner feedback #9a.
1653
+ - **`AccessLevel` docstring rewritten** — references the canonical-flow §21 in the platform spec and lays out semantics for both anonymous (Branch A/B/C) and verified (Branch D) callers.
1654
+
1655
+ ### Added — Webhook signature contract
1656
+
1657
+ - **`@astrasyncai/verification-gateway/webhooks`** (`src/webhooks.ts`) — new module with `verifyAstraSyncWebhook(rawBody, headers, secret, options?)` and `signAstraSyncWebhook(rawBody, secret, nowMs?)`. HMAC-SHA256 over `${unix_timestamp}.${rawBody}`, header format `X-AstraSync-Signature: t=<unix>,v1=<hex>`. Constant-time comparison; 5-minute replay window (configurable via `toleranceSeconds`).
1658
+ - 7/7 unit tests cover round-trip, wrong secret, tampered body, replay window, missing/malformed header, and header-case variants.
1659
+ - Closes partner feedback #4 (signature contract). Outbound webhook delivery system from AstraSync to merchant URLs is a separate engineering effort tracked in the platform spec §22 / §23.
1660
+
1661
+ ### Fixed — Response field-name alignment
1662
+
1663
+ - **`agent.id` → `agent.astraId`, `agent.status` → `agent.agentStatus`, added `agent.blockchainStatus`** — read verbatim from the API response. The SDK type definitions and the OpenAPI schema both already declared the new names; the backend response is now in line. Closes partner feedback #21.
1664
+
1665
+ ### Notes
1666
+
1667
+ - This release is paired with backend v2.9. The dual-perspective activity feed, orphan-identity tracking, Enterprise admin org pattern, and always-emit invariant on verification events all ship server-side; this SDK release is the corresponding client-side contract correction.
1668
+ - Activity-dashboard polish (per-row Step-up display, summary↔detail count consistency) is a NOT BUILT row in the platform spec — cosmetic only; does not affect the SDK contract.
1669
+
1670
+ ## [2.2.0] — 2026-05-03
1671
+
1672
+ ### Added — Ownership-bound agent client + caller metadata
1673
+
1674
+ - **`AgentClient.create()` async factory** (`src/agent/client.ts`) — validates
1675
+ the configured API key's account owns the supplied ASTRA-id by calling
1676
+ `GET /api/agents/:astraId/ownership` before returning a usable client. A
1677
+ stolen ASTRA-id paired with a different valid API key now fails closed at
1678
+ initialisation. Bypass in test environments with
1679
+ `ASTRASYNC_SKIP_OWNERSHIP_CHECK=true`.
1680
+ - **`OwnershipMismatchError`** (`src/agent/errors.ts`, exported from
1681
+ `src/agent/index.ts`) — typed error surfaced when ownership binding fails
1682
+ or no API key is configured. Extends a new base `AstraSyncSdkError` with a
1683
+ stable `code` field for downstream handling.
1684
+ - **`callerMetadata` on `verify()` requests** (`src/verify.ts`) — structured
1685
+ bag for `sourceIp`, `userAgent`, `referer`, `host`, `forwardedFor`, and
1686
+ `agentCardUrl`. Express + Next.js adapters now populate it from request
1687
+ headers (X-Forwarded-For first-hop resolution, `x-astrasync-agent-card`).
1688
+ Legacy top-level `clientIp` / `userAgent` fields continue to work.
1689
+
1690
+ ### Changed
1691
+
1692
+ - Express + Next.js adapters forward the original client IP (first entry of
1693
+ `X-Forwarded-For`, falling back to `req.ip`) instead of the immediate
1694
+ proxy hop.
1695
+ - Internal formatting pass (Prettier) across adapters and `verify.ts` — no
1696
+ behavioural changes.
1697
+
1698
+ ### Tests
1699
+
1700
+ - New `src/agent/__tests__/ownership.test.ts` covering the ownership-binding
1701
+ factory: success, `owned=false`, non-2xx, missing API key, and the
1702
+ `ASTRASYNC_SKIP_OWNERSHIP_CHECK` bypass.
1703
+
1704
+ ## [2.1.0] — 2026-04-18
1705
+
1706
+ ### Added — Commerce Transport Verification Engine (spec v2.7)
1707
+
1708
+ Runtime verification layer covering all 10 registered commerce/agent protocols.
1709
+ Library-first design; no hand-rolled cryptography.
1710
+
1711
+ **New `src/transport/` modules (21 source files):**
1712
+
1713
+ - **Extractors** — `vi.ts` (VI SD-JWT via `@sd-jwt/decode`), `ap2.ts` (AP2 mandate
1714
+ triple), `ucp.ts` (checkout session + AJV manifest), `acp.ts` (multi-header parse
1715
+ - payment-token prefix detection), `rfc9421.ts` (`structured-headers`), `mpp.ts`
1716
+ (Challenge/Credential/Receipt via `mppx`), `x402.ts` (v1 + v2 via `@x402/core`).
1717
+ - **Verifiers** — `vi-verify.ts` (3-layer SD-JWT chain + RFC 7638 JWK thumbprint
1718
+ bindings + VI constraint type 8 hash binding), `ap2-verify.ts` (mandate chain
1719
+ cross-ref + totals monotonicity), `acp-verify.ts` (detached JSON sig, Ed25519 +
1720
+ ES256 candidates, Timestamp freshness), `rfc9421-verify.ts` (wraps
1721
+ `http-message-signatures` + `RegistryResolver`), `mpp-verify.ts`
1722
+ (expiry + BodyDigest + source extraction), `stripe-webhook.ts` (inline
1723
+ HMAC-SHA256 via `node:crypto`).
1724
+ - **Registry resolvers** — `registry/visa.ts` (JWKS at `mcp.visa.com/.well-known/
1725
+ jwks` via `jose.createRemoteJWKSet`), `registry/mastercard.ts` (partnership-gated
1726
+ stub), `registry/web-bot-auth.ts` (IETF `draft-meunier-web-bot-auth-architecture-05`
1727
+ directory fetch, Ed25519).
1728
+ - **Orchestration** — `commerce-pipeline.ts` (extract→verify→identity-bind→
1729
+ constraint-eval→CommerceContext with hard-deny/trust-signal policy +
1730
+ per-phase timings), `identity-binding.ts` (cross-protocol agent identity
1731
+ mapping), `constraint-eval.ts` (VI types 1/2/3/4 + MPP/x402 payment-method
1732
+ allowlist + spending-limit), `extractor-registry.ts`
1733
+ (`registerTransportExtractor()` SDK hook for Lambda adapters),
1734
+ `purpose-mapping.ts` (20 commerce purpose mappings), `transaction-value.ts`
1735
+ (per-protocol amount normalization).
1736
+
1737
+ **New peer-dep-compatible runtime dependencies:**
1738
+
1739
+ - `@sd-jwt/core` ^0.19.0 + `@sd-jwt/decode` + `@sd-jwt/utils` (Apache-2.0)
1740
+ - `http-message-signatures` ^1.0.5 (ISC)
1741
+ - `mppx` 0.5.13 pinned (MIT, pre-1.0 API churn expected — wrapped in adapter)
1742
+ - `@x402/core` ^2.10.0 (Apache-2.0)
1743
+ - `web-bot-auth` ^0.1.3 (Apache-2.0)
1744
+ - `jose` ^5.9.6 (MIT)
1745
+ - `ajv` ^8.17.1 (MIT)
1746
+ - `structured-headers` ^2.0.1 (MIT)
1747
+
1748
+ **Exports added** — import from `@astrasyncai/verification-gateway`:
1749
+
1750
+ ```ts
1751
+ import {
1752
+ transport, // namespace containing all new modules
1753
+ } from '@astrasyncai/verification-gateway';
1754
+
1755
+ // Or from the transport subpath:
1756
+ import {
1757
+ // Extractors
1758
+ extractVIClaims,
1759
+ extractAP2Mandates,
1760
+ extractUCPContext,
1761
+ extractACPContext,
1762
+ parseRFC9421,
1763
+ extractMPPContext,
1764
+ extractX402Context,
1765
+ // Verifiers
1766
+ verifyVIChain,
1767
+ verifyAP2Chain,
1768
+ verifyACPSignature,
1769
+ verifyRFC9421,
1770
+ verifyMPP,
1771
+ verifyStripeWebhook,
1772
+ // Registry resolvers
1773
+ createVisaRegistry,
1774
+ createMastercardRegistry,
1775
+ createWebBotAuthRegistry,
1776
+ // Orchestration
1777
+ runCommercePipeline,
1778
+ evaluateVIConstraints,
1779
+ evaluatePaymentMethodAllowlist,
1780
+ evaluateSpendingLimit,
1781
+ bindIdentity,
1782
+ registerTransportExtractor,
1783
+ // Types
1784
+ type CommerceContext,
1785
+ type CommercePipelineInput,
1786
+ type VIExtractedClaims,
1787
+ type AP2MandateTriple,
1788
+ type ACPRequestContext,
1789
+ type MPPRequestContext,
1790
+ type X402RequestContext,
1791
+ type UCPCheckoutContext,
1792
+ type RegistryResolver,
1793
+ type IdentityClaim,
1794
+ type TransactionValueContext,
1795
+ } from '@astrasyncai/verification-gateway/transport';
1796
+ ```
1797
+
1798
+ **Tests** — 545 unit tests across 42 test files in this package. 13 additional
1799
+ integration tests in the backend exercise the full pipeline end-to-end via
1800
+ `POST /agents/verify-access` with a `commerceArtifacts` payload.
1801
+
1802
+ ### Added — Publishing hygiene
1803
+
1804
+ - `prepublishOnly` script runs `clean` + `typecheck` + `build` before any
1805
+ `npm publish`, so no one can publish from a stale dist or without a
1806
+ passing typecheck.
1807
+
1808
+ ### Notes
1809
+
1810
+ - Nothing is removed or renamed from the `2.0.x` API surface. Existing
1811
+ middleware (`createMiddleware` on Express / Next.js), the agent-side client
1812
+ (`AgentClient`), `verify()`, `quickVerify()`, `extractCredentials()`, and
1813
+ all adapter registries continue to work unchanged.
1814
+ - The orphaned `2.0.1` publish (2026-03-09) was a snapshot published directly
1815
+ from a local working copy without a matching git commit. Its code surface
1816
+ is a strict subset of `2.0.0`'s source-with-fixes and has since been
1817
+ superseded by content in `main`. All runtime behaviour from `2.0.1` is
1818
+ preserved in `2.1.0`.
1819
+
1820
+ ## [2.0.1] — 2026-03-09 (orphaned)
1821
+
1822
+ Published directly from a local working copy. No matching git commit or
1823
+ tag. Code surface is a strict subset of `2.1.0`. Included early SDK fixes
1824
+ (`durationRequired` forwarding, unregistered-attempt + counterparty
1825
+ pre-check activity reporting) plus `browser/background` and `cursor/extension`
1826
+ dist chunks; all of these are now in `2.1.0`.
1827
+
1828
+ ## [2.0.0] — 2026-03-08
1829
+
1830
+ Universal Verification Gateway launch:
1831
+
1832
+ - SDK handshake + PDLSS pre-check
1833
+ - Agent-side SDK: `AgentClient`, transport adapters (HTTP / A2A / MCP),
1834
+ challenge handler
1835
+ - Counterparty-side SDK: `verify()`, `quickVerify()`, `verifyAndRecord()`,
1836
+ `recordDecision()`
1837
+ - Adapter exports: `/express`, `/nextjs`, `/sdk`, `/agent`, `/transport`,
1838
+ `/gateway`, `/local-evaluator`, `/adapter-interface`, `/cli`, `/cursor`,
1839
+ `/browser`, `/git-trigger`
1840
+ - Local PDLSS evaluator (YAML + constraint checker)
1841
+ - AstraSyncGateway class (online / local / hybrid modes)
1842
+ - Hybrid sync queue (disk-backed, retry, status visibility)
1843
+ - Layer-4 adapters: OpenClaw CLI, Cursor, Browser
1844
+
1845
+ ## [1.1.0] — 2026-03-02
1846
+
1847
+ ## [1.0.0] — 2026-03-02
1848
+
1849
+ Initial release.