@astrasyncai/verification-gateway 5.17.2 → 5.18.1

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