Skip to content

2.0.0 — wallet-auth types + AgentScoreError.details + createSession args + drop verifyWebhookSignature#21

Merged
vvillait88 merged 28 commits intomainfrom
wallet-auth-hardening
Apr 29, 2026
Merged

2.0.0 — wallet-auth types + AgentScoreError.details + createSession args + drop verifyWebhookSignature#21
vvillait88 merged 28 commits intomainfrom
wallet-auth-hardening

Conversation

@vvillait88
Copy link
Copy Markdown
Contributor

@vvillait88 vvillait88 commented Apr 24, 2026

Summary

@agent-score/sdk@2.0.0 — wallet-auth hardening types + structured error access + speculative-helper drop. Breaking because of the webhook removal.

New (additive)

  • DenialCode union extended: wallet_signer_mismatch, wallet_auth_requires_wallet_signing, token_expired, invalid_credential.
  • NextStepsAction extended: send_existing_identity, mint_new_credential, use_operator_token, regenerate_payment_from_linked_wallet, deliver_verify_url_and_poll, switch_token_or_restart_session.
  • New types: AgentMemoryHint, WalletSignerMismatchBody, WalletAuthRequiresSigningBody + per-shape NextSteps discriminator unions.
  • AssessResponse.linked_wallets?: string[] on allow responses (cap 100).
  • SessionCreateResponse.agent_memory? + CredentialCreateResponse.agent_memory? on bootstrap paths.
  • CredentialListItem.label / .expires_at are now nullable to match the API.
  • AgentScoreError.details: Record<string, unknown> — carries response-body fields beyond {code, message} so consumers can branch on verify_url, linked_wallets, claimed_operator, actual_signer, expected_signer, reasons, agent_memory without parsing the response a second time. The mcp tools depend on this.
  • createSession({address?, operator_token?}) — optional pre-association lets a caller refresh KYC for an existing opc_... or pin a session to a known wallet.

Removed (BREAKING)

  • verifyWebhookSignature removed. Audit found zero outbound webhook emitter in core/api and zero internal consumers. When AgentScore eventually emits events, the right move is the official standardwebhooks (Svix) lib, not a hand-rolled HMAC verifier — exporting one preemptively was speculative surface.

Versioning

Bumped to 2.0.0. The webhook removal is a breaking change in the published surface even though no real consumer exercised it; honest semver.

Test plan

  • 115 tests passing
  • Type compilation (tsc) green
  • Lint clean
  • Tag v2.0.0 after merge → publish workflow fires (OIDC trusted publishing on npm)

Coordinated release

Plan doc: core/internal_docs/wallet-auth-hardening-plan.md.

Sibling PRs: python-sdk #16 (mirror), mcp #24 (consumes this), martin-estate #44 (consumes via @agent-score/commerce), core #190 (server side). Merge order: this + python-sdk + core land first → tag v2.0.0 → publish → commerce 1.0.0 publishes → mcp + martin-estate auto-clear and merge.

🤖 Generated with Claude Code

vvillait88 and others added 27 commits April 23, 2026 18:35
New types for the wallet-auth hardening effort:

- DenialCode union with wallet_signer_mismatch,
  wallet_auth_requires_wallet_signing (TEC-226) and token_expired,
  token_revoked (TEC-218) added; existing codes preserved
- NextStepsAction union with send_existing_identity,
  mint_new_credential, use_operator_token,
  regenerate_payment_from_linked_wallet
- WalletSignerMismatchBody, WalletAuthRequiresSigningBody — typed
  error shapes the gate returns for TEC-226 denials
- AgentMemoryHint — structured cross-merchant pattern hint agents
  persist to memory (TEC-227); gate emits on bootstrap paths
- AgentScoreErrorBody + SessionCreateResponse gain optional
  agent_memory field

Pure type surface; no runtime behavior change. Bumps to 1.9.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SDK type gap — /v1/assess emits linked_wallets on allow responses but
AssessResponse didn't declare the field. Consumers reading the response
fall back to `any` for that field. Adds the optional field with TEC-226
docstring explaining it's the same-operator wallet set plus the deny-guard
semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Strip ticket IDs and version-introduction annotations from comments
  (commit/PR/CHANGELOG carry that context; source comments should
  describe invariants, not origin).
- CredentialListItem: allow null on `label` and `expires_at` — API can
  return null per schema; Python SDK was already correct.
- CredentialCreateResponse: add optional `agent_memory` field so type
  matches API behavior (emitted on first-credential mint).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Identity-model section now calls out linked_wallets[] +
resolved_operator on assess responses, agent_memory on createSession
and createCredential responses, and the next_steps.action enum on
pollSession — so future sessions start with an accurate mental
model of each method's response shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gate-emitted denials now ship these action codes inside agent_instructions:
probe_identity_then_session, resign_or_switch_to_operator_token,
switch_to_operator_token, and deliver_verify_url_and_poll (POST /v1/sessions).
Add them to the NextStepsAction union plus the GET-poll session state actions
so TypeScript agents see recognized enum members when they destructure
next_steps.action / the parsed agent_instructions.

Rename: drop the stale send_existing_identity (never implemented server-side;
the current contract is probe_identity_then_session which supersedes it).

Test: types.test.ts asserts all new values are accepted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s flows

README now calls out that createSession returns structured next_steps (with
action=deliver_verify_url_and_poll) and agent_memory; that pollSession
returns a typed next_steps.action from the NextStepsAction enum; and that
assess responses include resolved_operator + linked_wallets[] for
cross-merchant same-operator resolution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…shapes

The WalletSignerMismatchBody and WalletAuthRequiresSigningBody types required
a specific next_steps.action literal (regenerate_payment_from_linked_wallet,
use_operator_token). That only describes the merchant-override shape (like
martin-estate's custom 403). Merchants using the gate's default denial
marshaller instead emit agent_instructions (JSON-encoded string) with
resign_or_switch_to_operator_token / switch_to_operator_token actions.

Update both types to reflect reality:
- next_steps optional (not required)
- next_steps.action broadened to NextStepsAction (not a single literal)
- Add optional agent_instructions: string

Also add agent_instructions to AgentScoreErrorBody so the generic error
body type covers gate-emitted denials.

Also wire up structured next_steps on SessionCreateResponse (POST /v1/sessions
returns action=deliver_verify_url_and_poll plus poll_interval_seconds +
steps) — previously undocumented in the typed response.

Tighten SessionPollNextSteps.action and CredentialCreateErrorResponse
next_steps.action from string to NextStepsAction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ures

The API emits agent_memory on POST /v1/credentials/wallets when first_seen
is true (first capture of a (credential, wallet, network) tuple) —
previously undocumented in the typed response, so SDK consumers couldn't
forward it to agents.

Field is optional; absent on subsequent captures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ain)

Was pointing to https://docs.agentscore.sh which is the docs-only
subdomain. The canonical product URL is agentscore.sh. Aligned with
python-sdk, node-gate, python-gate, and mcp in the same commit series.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The API unifies revoked + TTL-expired credentials under a single
token_expired 401 — deliberate to avoid leaking the user's revoke intent.
Agents only ever see token_expired; the SDK type reflects that.

Updated JSDoc describing token_expired to note it covers both cases and
now carries an auto-minted session in the 401 body for user-in-loop
recovery (no API key needed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The API no longer emits this action. token_expired (which used to point
to mint_new_credential) now points to deliver_verify_url_and_poll since
the 401 body carries an auto-minted session. Removing from the union
prevents agents from type-checking against a value they'll never see.

Test count updated (12 → 11).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…|| true

Mirrors the cleanup landed in agentscore/pay (commit 8c3be79):

- actions/cache@v5: Node 24 runtime, satisfied by GitHub-hosted +
  Blacksmith runners.
- osv-scanner v2.3.5: 3 patch releases since 2.3.2; switched to the
  modern `scan source` subcommand.
- Removed `|| true` after the dependency-scan steps. The suppression
  silently masked osv-scanner crash exits (network failure, corrupted
  binary) along with any vulnerability findings. Verified locally that
  every lockfile in this repo returns 0 issues, so CI passes today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…are ARM)

The previous _linux_amd64 download silently exec-failed; the prior
`|| true` masked it. Now that the swallow is removed, the architecture
mismatch surfaces. Blacksmith pool is ARM, osv-scanner publishes both
_amd64 and _arm64 binaries — switching to the right one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
osv-scanner v2.3.5 (now strict, since `|| true` was dropped) flagged
transitive vulnerabilities. `bun update` was a no-op because the
lockfile was already maximal under the recorded resolutions; a fresh
`rm bun.lock && bun install` re-resolved transitives and picked the
patched versions.

Verified locally: 0 osv findings, typecheck clean, all tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original request's timeout would keep running through the retry-after
sleep. If it fired before the retry started, the retry's fetch saw an
already-aborted signal and failed silently as a timeout instead of
completing rate-limit recovery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes audit item #12. Generic HMAC-SHA256 webhook signature verifier,
Stripe-pattern (`t=<unix>,v1=<hex>` header). Useful both when AgentScore
eventually ships outbound webhooks and as a generic helper for merchants
verifying any HMAC-signed webhook source (Stripe, GitHub, etc.).

Returns a structured result with `reason` set on failure
(no_signatures / no_timestamp / timestamp_too_old / timestamp_in_future /
signature_mismatch / malformed_header) so callers can differentiate
transient vs permanent failures.

Tolerance defaults to 300s (Stripe convention); set to 0 to disable
timestamp checking for raw HMAC use cases.

Also adds @types/node to devDependencies + tsconfig types: ["node"] —
needed for the Buffer + crypto imports the helper uses. Existing modules
were getting away without this because they didn't use Node-specific
types at the source level (they slipped through DTS generation).

10 new tests covering all paths + multi-signature header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the same content surfaced in core/docs/integrations/typescript.mdx
so README readers see the helper alongside the existing AgentScore API
methods.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single canonical home for the seven reserved AgentScore EVM test fixtures
(0x0000…0001 through 0x0000…0007). Lives in src/test-mode.ts and is
re-exported from the package root.

@agent-score/pay and @agent-score/mcp had near-identical copies of the
same constants — they now re-export from here so the address list stays in
sync with the AgentScore API spec across pay, mcp, commerce, and the SDKs
themselves. Python parity ships in agentscore-py via agentscore.test_mode.

8 new tests, lint + typecheck + build all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Function coverage was 92.3% (12/13) in src/index.ts because the
retry-timer abort callback (line 181) was never exercised — existing
429 tests use a successful retry response. Adds a test where the
retry itself hangs until the retry-timeout fires, exercising the
retry AbortController path and bringing function coverage to 100%.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d alternative'

The denial-body type comments described two NextStepsAction values
(regenerate_payment_from_linked_wallet, use_operator_token) as 'legacy
merchant override' — but these are alternatives to the gate default,
not legacy holdovers. Nothing has shipped yet that could have made one
the predecessor of the other.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…entialItem for python parity

Pure additive (plus one rename):
- Network type promoted to a top-level export ('evm' | 'solana')
- PolicyCheck, PolicyResult, PolicyExplanation extracted from inline AssessResponse shapes
- SessionCreateNextSteps extracted from inline SessionCreateResponse.next_steps
- CredentialCreateErrorNextSteps extracted from inline CredentialCreateErrorResponse.next_steps
- CredentialListItem → CredentialItem (matches python-sdk; also updates the
  CredentialListResponse.credentials[] reference)

Brings name-level parity with python-sdk so polyglot vendors don't have to
remember two names for the same shape. No behavior change. No downstream
consumers reference the old CredentialListItem name (verified across all repos).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-commit eslint + pre-push typecheck were configured in lefthook.yml but
contributors had to manually run \`bunx lefthook install\` per clone. Adding
lefthook to devDeps + a \`prepare\` script means \`bun install\` wires the
hooks automatically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…identity hints on createSession

Two non-breaking additions that unblock SDK consumption from mcp's tool layer:

1. AgentScoreError now carries a `details: Record<string, unknown>` field
   populated from non-`error` keys of the response body. Consumers can branch
   on `verify_url`, `linked_wallets`, `claimed_operator`, `actual_signer`,
   `reasons`, etc. for granular denial recovery — previously the SDK dropped
   them and only surfaced `code` + `message`. Defaults to `{}` so existing
   constructor calls keep working.

2. SessionCreateOptions accepts optional `address` + `operator_token` so a
   session can be pre-associated with a known wallet or be a KYC refresh for
   an existing `opc_...`. The `/v1/sessions` API has accepted these all along;
   the SDK was just not forwarding them.

Coverage stays at 96.5/91.86/100/98.27 (Tier A bar 95/90/95/95).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the speculative HMAC-SHA256 webhook verifier (Stripe-pattern):
- src/webhooks.ts (113 LOC) — deleted
- tests/webhooks.test.ts — deleted
- src/index.ts — webhook re-exports removed
- README.md — webhook section scrubbed

Audit findings: zero outbound webhook emitter in core/api, zero internal
consumers across mcp/pay/commerce/martin-estate, no API endpoint signs
anything. The only webhook code in the codebase is the inbound-from-Stripe
Identity handler in core/website (uses stripe.webhooks.constructEvent, not
this lib).

Removing now is risk-free: SDKs haven't published 1.9.0 yet, so no external
consumers can have adopted this. When AgentScore eventually ships outbound
events (score-changed, KYC-completed, etc.), the right move is the official
Standard Webhooks lib (standardwebhooks on npm + PyPI — Svix interop spec)
rather than re-rolling our own format.

Marked breaking (!) since the public surface shrinks, but practically this
is a no-op for anyone tracking the unpublished 1.9.0 line.

Coverage stays at 97.16/91.22/100/98.85 (Tier A bar 95/90/95/95).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ty hints

Two unstaged additions from this session were missing README coverage:

1. AgentScoreError.details — node-sdk + python-sdk parity. Carries response-
   body fields beyond {code, message} (verify_url, linked_wallets,
   claimed_operator, actual_signer, expected_signer, reasons, agent_memory).
   New example shows branching on wallet_signer_mismatch and token_expired.

2. createSession({address, operator_token}) — optional pre-association of
   the session with a known wallet or existing opc_... (KYC refresh).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The wallet-auth-hardening line ships with breaking surface changes:
- verifyWebhookSignature removed
- (other coordinated trims documented in commit history)

Honest semver — bumping from 1.8.0 to 2.0.0 instead of 1.9.0.

Test describe block renamed away from a version-coupled name; backward-compat
comments scrubbed of the 1.9.0 reference (per house style: don't tag code
with "added in vX.Y" — version history lives in git/CHANGELOG).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vvillait88 vvillait88 changed the title 1.9.0 — wallet-auth hardening types (signer binding + granular denials + agent_memory) 2.0.0 — wallet-auth hardening types (signer binding + granular denials + agent_memory) Apr 27, 2026
@vvillait88 vvillait88 changed the title 2.0.0 — wallet-auth hardening types (signer binding + granular denials + agent_memory) 2.0.0 — wallet-auth types + AgentScoreError.details + createSession args + drop verifyWebhookSignature Apr 27, 2026
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vvillait88 vvillait88 merged commit b56e628 into main Apr 29, 2026
5 checks passed
@vvillait88 vvillait88 deleted the wallet-auth-hardening branch April 29, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant