W0: add regulatory profile baseline to checkout and ID flows
Some checks are pending
check / secretapi (push) Waiting to run

This commit is contained in:
Joshua 2026-02-19 18:13:05 -08:00
parent c80b1db18b
commit a3a53992bd
14 changed files with 177 additions and 77 deletions

View File

@ -153,6 +153,7 @@ The envelope is pre-execution pricing metadata and is authoritative for checkout
- `SECRET_API_QUOTE_TTL_SECONDS` (default `900`) - `SECRET_API_QUOTE_TTL_SECONDS` (default `900`)
- `SECRET_API_WALLET_SESSION_TTL_SECONDS` (default `2592000`) - `SECRET_API_WALLET_SESSION_TTL_SECONDS` (default `2592000`)
- `SECRET_API_REQUIRE_WALLET_SESSION` (default `true`; set `false` only for controlled local harness/debug usage) - `SECRET_API_REQUIRE_WALLET_SESSION` (default `true`; set `false` only for controlled local harness/debug usage)
- `SECRET_API_REGULATORY_PROFILE_ID` (`us_general_2026` default, `eu_ai_act_2026_baseline` supported)
- `SECRET_API_DOMAIN_NAME` - `SECRET_API_DOMAIN_NAME`
- `SECRET_API_VERIFYING_CONTRACT` - `SECRET_API_VERIFYING_CONTRACT`
- `SECRET_API_MEMBERSHIP_CONTRACT` - `SECRET_API_MEMBERSHIP_CONTRACT`

View File

@ -467,6 +467,7 @@ func (a *app) handleMembershipQuote(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, membershipQuoteResponse{ writeJSON(w, http.StatusOK, membershipQuoteResponse{
QuoteID: quote.QuoteID, QuoteID: quote.QuoteID,
ChainID: quote.ChainID, ChainID: quote.ChainID,
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
Currency: quote.Currency, Currency: quote.Currency,
AmountAtomic: quote.AmountAtomic, AmountAtomic: quote.AmountAtomic,
Decimals: quote.Decimals, Decimals: quote.Decimals,
@ -602,6 +603,7 @@ func (a *app) handleMembershipConfirm(w http.ResponseWriter, r *http.Request) {
Status: "membership_active", Status: "membership_active",
DesignationCode: rec.Code, DesignationCode: rec.Code,
DisplayToken: rec.DisplayToken, DisplayToken: rec.DisplayToken,
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
TxHash: strings.ToLower(req.TxHash), TxHash: strings.ToLower(req.TxHash),
ActivatedAt: now.Format(time.RFC3339Nano), ActivatedAt: now.Format(time.RFC3339Nano),
IdentityAssurance: rec.IdentityAssurance, IdentityAssurance: rec.IdentityAssurance,
@ -639,7 +641,7 @@ func (a *app) handleMembershipStatus(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
if errors.Is(err, errNotFound) { if errors.Is(err, errNotFound) {
writeJSON(w, http.StatusOK, membershipStatusResponse{Status: "none"}) writeJSON(w, http.StatusOK, membershipStatusResponse{Status: "none", RegulatoryProfileID: a.cfg.RegulatoryProfileID})
return return
} }
writeError(w, http.StatusInternalServerError, "failed to resolve membership status") writeError(w, http.StatusInternalServerError, "failed to resolve membership status")
@ -654,6 +656,7 @@ func (a *app) handleMembershipStatus(w http.ResponseWriter, r *http.Request) {
Status: status, Status: status,
Wallet: rec.Address, Wallet: rec.Address,
DesignationCode: rec.Code, DesignationCode: rec.Code,
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
IdentityAssurance: normalizeAssuranceLevel(rec.IdentityAssurance), IdentityAssurance: normalizeAssuranceLevel(rec.IdentityAssurance),
IdentityAttestedBy: strings.TrimSpace(rec.IdentityAttestedBy), IdentityAttestedBy: strings.TrimSpace(rec.IdentityAttestedBy),
IdentityAttestationID: strings.TrimSpace(rec.IdentityAttestationID), IdentityAttestationID: strings.TrimSpace(rec.IdentityAttestationID),

View File

@ -89,6 +89,7 @@ func TestMembershipDistinctPayerProof(t *testing.T) {
t.Fatalf("tx.from mismatch: %+v", quote.Tx) t.Fatalf("tx.from mismatch: %+v", quote.Tx)
} }
assertQuoteCostEnvelope(t, quote.CostEnvelope, quote.Currency, quote.Decimals, quote.AmountAtomic) assertQuoteCostEnvelope(t, quote.CostEnvelope, quote.Currency, quote.Decimals, quote.AmountAtomic)
assertRegulatoryProfileID(t, quote.RegulatoryProfileID, cfg.RegulatoryProfileID)
} }
func TestMembershipCompanySponsorWithoutOwnerProof(t *testing.T) { func TestMembershipCompanySponsorWithoutOwnerProof(t *testing.T) {
@ -1095,6 +1096,7 @@ func TestMarketplaceCheckoutBundlesMembershipAndMintsEntitlement(t *testing.T) {
t.Fatalf("expected default merchant id, got %+v", quote) t.Fatalf("expected default merchant id, got %+v", quote)
} }
assertQuoteCostEnvelope(t, quote.CostEnvelope, quote.Currency, quote.Decimals, quote.TotalAmountAtomic) assertQuoteCostEnvelope(t, quote.CostEnvelope, quote.Currency, quote.Decimals, quote.TotalAmountAtomic)
assertRegulatoryProfileID(t, quote.RegulatoryProfileID, cfg.RegulatoryProfileID)
confirm := postJSONExpect[marketplaceCheckoutConfirmResponse](t, a, "/marketplace/checkout/confirm", marketplaceCheckoutConfirmRequest{ confirm := postJSONExpect[marketplaceCheckoutConfirmResponse](t, a, "/marketplace/checkout/confirm", marketplaceCheckoutConfirmRequest{
QuoteID: quote.QuoteID, QuoteID: quote.QuoteID,
@ -1109,11 +1111,13 @@ func TestMarketplaceCheckoutBundlesMembershipAndMintsEntitlement(t *testing.T) {
if confirm.Status != "entitlement_active" || confirm.EntitlementID == "" { if confirm.Status != "entitlement_active" || confirm.EntitlementID == "" {
t.Fatalf("unexpected confirm response: %+v", confirm) t.Fatalf("unexpected confirm response: %+v", confirm)
} }
assertRegulatoryProfileID(t, confirm.RegulatoryProfileID, cfg.RegulatoryProfileID)
status := getJSONExpect[membershipStatusResponse](t, a, "/secret/membership/status?wallet="+ownerAddr, http.StatusOK) status := getJSONExpect[membershipStatusResponse](t, a, "/secret/membership/status?wallet="+ownerAddr, http.StatusOK)
if status.Status != "active" { if status.Status != "active" {
t.Fatalf("expected active membership after bundled checkout, got %+v", status) t.Fatalf("expected active membership after bundled checkout, got %+v", status)
} }
assertRegulatoryProfileID(t, status.RegulatoryProfileID, cfg.RegulatoryProfileID)
entitlements := getJSONExpect[marketplaceEntitlementsResponse](t, a, "/marketplace/entitlements?wallet="+ownerAddr, http.StatusOK) entitlements := getJSONExpect[marketplaceEntitlementsResponse](t, a, "/marketplace/entitlements?wallet="+ownerAddr, http.StatusOK)
if len(entitlements.Entitlements) == 0 { if len(entitlements.Entitlements) == 0 {
@ -2070,6 +2074,13 @@ func assertQuoteCostEnvelope(t *testing.T, got quoteCostEnvelope, wantCurrency s
} }
} }
func assertRegulatoryProfileID(t *testing.T, got string, want string) {
t.Helper()
if normalizeRegulatoryProfileID(got) != normalizeRegulatoryProfileID(want) {
t.Fatalf("regulatory profile mismatch: got=%s want=%s", got, want)
}
}
func signTypedData(t *testing.T, key *ecdsa.PrivateKey, typedData apitypes.TypedData) string { func signTypedData(t *testing.T, key *ecdsa.PrivateKey, typedData apitypes.TypedData) string {
t.Helper() t.Helper()
hash, _, err := apitypes.TypedDataAndHash(typedData) hash, _, err := apitypes.TypedDataAndHash(typedData)

View File

@ -14,6 +14,7 @@ type Config struct {
DBPath string DBPath string
AllowedOrigin string AllowedOrigin string
DeploymentClass string DeploymentClass string
RegulatoryProfileID string
MemberPollIntervalSec int MemberPollIntervalSec int
IntentTTL time.Duration IntentTTL time.Duration
QuoteTTL time.Duration QuoteTTL time.Duration
@ -47,6 +48,7 @@ func loadConfig() Config {
DBPath: env("SECRET_API_DB_PATH", "./secret.db"), DBPath: env("SECRET_API_DB_PATH", "./secret.db"),
AllowedOrigin: env("SECRET_API_ALLOWED_ORIGIN", "https://edut.ai"), AllowedOrigin: env("SECRET_API_ALLOWED_ORIGIN", "https://edut.ai"),
DeploymentClass: normalizeDeploymentClass(env("SECRET_API_DEPLOYMENT_CLASS", "development")), DeploymentClass: normalizeDeploymentClass(env("SECRET_API_DEPLOYMENT_CLASS", "development")),
RegulatoryProfileID: normalizeRegulatoryProfileID(env("SECRET_API_REGULATORY_PROFILE_ID", defaultRegulatoryProfileID)),
MemberPollIntervalSec: envInt("SECRET_API_MEMBER_POLL_INTERVAL_SECONDS", 30), MemberPollIntervalSec: envInt("SECRET_API_MEMBER_POLL_INTERVAL_SECONDS", 30),
IntentTTL: time.Duration(envInt("SECRET_API_INTENT_TTL_SECONDS", 900)) * time.Second, IntentTTL: time.Duration(envInt("SECRET_API_INTENT_TTL_SECONDS", 900)) * time.Second,
QuoteTTL: time.Duration(envInt("SECRET_API_QUOTE_TTL_SECONDS", 900)) * time.Second, QuoteTTL: time.Duration(envInt("SECRET_API_QUOTE_TTL_SECONDS", 900)) * time.Second,
@ -79,6 +81,9 @@ func (c Config) Validate() error {
if c.ChainID <= 0 { if c.ChainID <= 0 {
return fmt.Errorf("SECRET_API_CHAIN_ID must be positive") return fmt.Errorf("SECRET_API_CHAIN_ID must be positive")
} }
if !isKnownRegulatoryProfileID(c.RegulatoryProfileID) {
return fmt.Errorf("SECRET_API_REGULATORY_PROFILE_ID must be %s or %s", regulatoryProfileUSGeneral2026, regulatoryProfileEUAIBaseline2026)
}
currency := strings.ToUpper(strings.TrimSpace(c.MintCurrency)) currency := strings.ToUpper(strings.TrimSpace(c.MintCurrency))
switch currency { switch currency {
case "USDC": case "USDC":

View File

@ -115,3 +115,21 @@ func TestConfigValidateRejectsProductionWhenStrictVerificationDisabled(t *testin
t.Fatalf("expected production config validation failure when strict onchain verification disabled") t.Fatalf("expected production config validation failure when strict onchain verification disabled")
} }
} }
func TestConfigValidateAllowsKnownRegulatoryProfiles(t *testing.T) {
cfg := loadConfigIsolated(t)
for _, profileID := range []string{regulatoryProfileUSGeneral2026, regulatoryProfileEUAIBaseline2026} {
cfg.RegulatoryProfileID = profileID
if err := cfg.Validate(); err != nil {
t.Fatalf("expected regulatory profile %s to validate, got %v", profileID, err)
}
}
}
func TestConfigValidateRejectsUnknownRegulatoryProfile(t *testing.T) {
cfg := loadConfigIsolated(t)
cfg.RegulatoryProfileID = "unknown_profile"
if err := cfg.Validate(); err == nil {
t.Fatalf("expected regulatory profile validation failure")
}
}

View File

@ -489,6 +489,7 @@ func (a *app) handleMarketplaceCheckoutQuote(w http.ResponseWriter, r *http.Requ
writeJSON(w, http.StatusOK, marketplaceCheckoutQuoteResponse{ writeJSON(w, http.StatusOK, marketplaceCheckoutQuoteResponse{
QuoteID: quote.QuoteID, QuoteID: quote.QuoteID,
MerchantID: quote.MerchantID, MerchantID: quote.MerchantID,
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
Wallet: quote.Wallet, Wallet: quote.Wallet,
PayerWallet: quote.PayerWallet, PayerWallet: quote.PayerWallet,
OfferID: quote.OfferID, OfferID: quote.OfferID,
@ -609,6 +610,7 @@ func (a *app) handleMarketplaceCheckoutConfirm(w http.ResponseWriter, r *http.Re
Status: "entitlement_active", Status: "entitlement_active",
EntitlementID: existing.EntitlementID, EntitlementID: existing.EntitlementID,
MerchantID: existing.MerchantID, MerchantID: existing.MerchantID,
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
OfferID: existing.OfferID, OfferID: existing.OfferID,
OrgRootID: existing.OrgRootID, OrgRootID: existing.OrgRootID,
PrincipalID: existing.PrincipalID, PrincipalID: existing.PrincipalID,
@ -712,6 +714,7 @@ func (a *app) handleMarketplaceCheckoutConfirm(w http.ResponseWriter, r *http.Re
Status: "entitlement_active", Status: "entitlement_active",
EntitlementID: ent.EntitlementID, EntitlementID: ent.EntitlementID,
MerchantID: ent.MerchantID, MerchantID: ent.MerchantID,
RegulatoryProfileID: a.cfg.RegulatoryProfileID,
OfferID: ent.OfferID, OfferID: ent.OfferID,
OrgRootID: ent.OrgRootID, OrgRootID: ent.OrgRootID,
PrincipalID: ent.PrincipalID, PrincipalID: ent.PrincipalID,

View File

@ -67,6 +67,7 @@ type marketplaceQuoteLineItem struct {
type marketplaceCheckoutQuoteResponse struct { type marketplaceCheckoutQuoteResponse struct {
QuoteID string `json:"quote_id"` QuoteID string `json:"quote_id"`
MerchantID string `json:"merchant_id,omitempty"` MerchantID string `json:"merchant_id,omitempty"`
RegulatoryProfileID string `json:"regulatory_profile_id"`
Wallet string `json:"wallet"` Wallet string `json:"wallet"`
PayerWallet string `json:"payer_wallet,omitempty"` PayerWallet string `json:"payer_wallet,omitempty"`
OfferID string `json:"offer_id"` OfferID string `json:"offer_id"`
@ -107,6 +108,7 @@ type marketplaceCheckoutConfirmResponse struct {
Status string `json:"status"` Status string `json:"status"`
EntitlementID string `json:"entitlement_id"` EntitlementID string `json:"entitlement_id"`
MerchantID string `json:"merchant_id,omitempty"` MerchantID string `json:"merchant_id,omitempty"`
RegulatoryProfileID string `json:"regulatory_profile_id"`
OfferID string `json:"offer_id"` OfferID string `json:"offer_id"`
OrgRootID string `json:"org_root_id,omitempty"` OrgRootID string `json:"org_root_id,omitempty"`
PrincipalID string `json:"principal_id,omitempty"` PrincipalID string `json:"principal_id,omitempty"`

View File

@ -70,6 +70,7 @@ type membershipQuoteRequest struct {
type membershipQuoteResponse struct { type membershipQuoteResponse struct {
QuoteID string `json:"quote_id"` QuoteID string `json:"quote_id"`
ChainID int64 `json:"chain_id"` ChainID int64 `json:"chain_id"`
RegulatoryProfileID string `json:"regulatory_profile_id"`
Currency string `json:"currency"` Currency string `json:"currency"`
AmountAtomic string `json:"amount_atomic"` AmountAtomic string `json:"amount_atomic"`
Decimals int `json:"decimals"` Decimals int `json:"decimals"`
@ -101,6 +102,7 @@ type membershipConfirmResponse struct {
Status string `json:"status"` Status string `json:"status"`
DesignationCode string `json:"designation_code"` DesignationCode string `json:"designation_code"`
DisplayToken string `json:"display_token"` DisplayToken string `json:"display_token"`
RegulatoryProfileID string `json:"regulatory_profile_id"`
TxHash string `json:"tx_hash"` TxHash string `json:"tx_hash"`
ActivatedAt string `json:"activated_at"` ActivatedAt string `json:"activated_at"`
IdentityAssurance string `json:"identity_assurance_level"` IdentityAssurance string `json:"identity_assurance_level"`
@ -112,6 +114,7 @@ type membershipStatusResponse struct {
Status string `json:"status"` Status string `json:"status"`
Wallet string `json:"wallet,omitempty"` Wallet string `json:"wallet,omitempty"`
DesignationCode string `json:"designation_code,omitempty"` DesignationCode string `json:"designation_code,omitempty"`
RegulatoryProfileID string `json:"regulatory_profile_id"`
IdentityAssurance string `json:"identity_assurance_level,omitempty"` IdentityAssurance string `json:"identity_assurance_level,omitempty"`
IdentityAttestedBy string `json:"identity_attested_by,omitempty"` IdentityAttestedBy string `json:"identity_attested_by,omitempty"`
IdentityAttestationID string `json:"identity_attestation_id,omitempty"` IdentityAttestationID string `json:"identity_attestation_id,omitempty"`

View File

@ -0,0 +1,30 @@
package main
import "strings"
const (
regulatoryProfileUSGeneral2026 = "us_general_2026"
regulatoryProfileEUAIBaseline2026 = "eu_ai_act_2026_baseline"
defaultRegulatoryProfileID = regulatoryProfileUSGeneral2026
)
func normalizeRegulatoryProfileID(raw string) string {
value := strings.ToLower(strings.TrimSpace(raw))
switch value {
case "", "us", "us_general", regulatoryProfileUSGeneral2026:
return regulatoryProfileUSGeneral2026
case "eu", "eu_ai_act", regulatoryProfileEUAIBaseline2026:
return regulatoryProfileEUAIBaseline2026
default:
return value
}
}
func isKnownRegulatoryProfileID(value string) bool {
switch normalizeRegulatoryProfileID(value) {
case regulatoryProfileUSGeneral2026, regulatoryProfileEUAIBaseline2026:
return true
default:
return false
}
}

View File

@ -58,6 +58,7 @@ Success (`200`):
```json ```json
{ {
"quote_id": "cq_01HZZXFQ27ZP6MP0V2R9M6V3KX", "quote_id": "cq_01HZZXFQ27ZP6MP0V2R9M6V3KX",
"regulatory_profile_id": "us_general_2026",
"wallet": "0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5", "wallet": "0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5",
"payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11", "payer_wallet": "0x2299547f6fA9A8f9b6d9aEA9F9D8A4B53C8A0e11",
"offer_id": "edut.workspace.core", "offer_id": "edut.workspace.core",
@ -157,6 +158,7 @@ Success (`200`):
{ {
"status": "entitlement_active", "status": "entitlement_active",
"entitlement_id": "ent:8453:0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5:000001", "entitlement_id": "ent:8453:0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5:000001",
"regulatory_profile_id": "us_general_2026",
"offer_id": "edut.workspace.core", "offer_id": "edut.workspace.core",
"org_root_id": "org.acme.root", "org_root_id": "org.acme.root",
"principal_id": "human.joshua", "principal_id": "human.joshua",

View File

@ -159,6 +159,7 @@ Success (`200`):
{ {
"quote_id": "mq_01HZZX4F8VQXJ6A57R8P3SCB2W", "quote_id": "mq_01HZZX4F8VQXJ6A57R8P3SCB2W",
"chain_id": 8453, "chain_id": 8453,
"regulatory_profile_id": "us_general_2026",
"currency": "USDC", "currency": "USDC",
"amount_atomic": "100000000", "amount_atomic": "100000000",
"decimals": 6, "decimals": 6,
@ -238,6 +239,7 @@ Success (`200`):
"status": "membership_active", "status": "membership_active",
"designation_code": "0217073045482", "designation_code": "0217073045482",
"display_token": "0217-0730-4548-2", "display_token": "0217-0730-4548-2",
"regulatory_profile_id": "us_general_2026",
"tx_hash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "tx_hash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"activated_at": "2026-02-17T07:33:09Z", "activated_at": "2026-02-17T07:33:09Z",
"identity_assurance_level": "onramp_attested", "identity_assurance_level": "onramp_attested",
@ -269,6 +271,7 @@ Success (`200`):
"status": "active", "status": "active",
"wallet": "0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5", "wallet": "0x3ea6cbf98d23e2cf7b6f4f9bb1fb4f50b710f2d5",
"designation_code": "0217073045482", "designation_code": "0217073045482",
"regulatory_profile_id": "us_general_2026",
"identity_assurance_level": "onramp_attested", "identity_assurance_level": "onramp_attested",
"identity_attested_by": "moonpay", "identity_attested_by": "moonpay",
"identity_attestation_id": "mp_session_01JAA..." "identity_attestation_id": "mp_session_01JAA..."

View File

@ -222,12 +222,15 @@ components:
description: If true, quote may bundle first-time membership fee into total. description: If true, quote may bundle first-time membership fee into total.
CheckoutQuoteResponse: CheckoutQuoteResponse:
type: object type: object
required: [quote_id, wallet, offer_id, currency, amount_atomic, total_amount_atomic, decimals, cost_envelope, policy_hash, expires_at] required: [quote_id, regulatory_profile_id, wallet, offer_id, currency, amount_atomic, total_amount_atomic, decimals, cost_envelope, policy_hash, expires_at]
properties: properties:
quote_id: quote_id:
type: string type: string
merchant_id: merchant_id:
type: string type: string
regulatory_profile_id:
type: string
enum: [us_general_2026, eu_ai_act_2026_baseline]
wallet: wallet:
type: string type: string
payer_wallet: payer_wallet:
@ -378,7 +381,7 @@ components:
type: integer type: integer
CheckoutConfirmResponse: CheckoutConfirmResponse:
type: object type: object
required: [status, entitlement_id, offer_id, wallet, tx_hash] required: [status, entitlement_id, regulatory_profile_id, offer_id, wallet, tx_hash]
properties: properties:
status: status:
type: string type: string
@ -387,6 +390,9 @@ components:
type: string type: string
merchant_id: merchant_id:
type: string type: string
regulatory_profile_id:
type: string
enum: [us_general_2026, eu_ai_act_2026_baseline]
offer_id: offer_id:
type: string type: string
org_root_id: org_root_id:

View File

@ -355,12 +355,15 @@ components:
type: string type: string
MembershipQuoteResponse: MembershipQuoteResponse:
type: object type: object
required: [quote_id, chain_id, currency, amount_atomic, decimals, cost_envelope, deadline, contract_address] required: [quote_id, chain_id, regulatory_profile_id, currency, amount_atomic, decimals, cost_envelope, deadline, contract_address]
properties: properties:
quote_id: quote_id:
type: string type: string
chain_id: chain_id:
type: integer type: integer
regulatory_profile_id:
type: string
enum: [us_general_2026, eu_ai_act_2026_baseline]
currency: currency:
type: string type: string
enum: [USDC] enum: [USDC]
@ -469,7 +472,7 @@ components:
description: Optional provider attestation reference id. description: Optional provider attestation reference id.
MembershipConfirmResponse: MembershipConfirmResponse:
type: object type: object
required: [status, designation_code, display_token, tx_hash, activated_at, identity_assurance_level] required: [status, designation_code, display_token, regulatory_profile_id, tx_hash, activated_at, identity_assurance_level]
properties: properties:
status: status:
type: string type: string
@ -478,6 +481,9 @@ components:
type: string type: string
display_token: display_token:
type: string type: string
regulatory_profile_id:
type: string
enum: [us_general_2026, eu_ai_act_2026_baseline]
tx_hash: tx_hash:
type: string type: string
activated_at: activated_at:
@ -492,7 +498,7 @@ components:
type: string type: string
MembershipStatusResponse: MembershipStatusResponse:
type: object type: object
required: [status] required: [status, regulatory_profile_id]
properties: properties:
status: status:
type: string type: string
@ -501,6 +507,9 @@ components:
type: string type: string
designation_code: designation_code:
type: string type: string
regulatory_profile_id:
type: string
enum: [us_general_2026, eu_ai_act_2026_baseline]
identity_assurance_level: identity_assurance_level:
type: string type: string
enum: [none, crypto_direct_unattested, sponsored_unattested, onramp_attested] enum: [none, crypto_direct_unattested, sponsored_unattested, onramp_attested]

View File

@ -258,6 +258,7 @@ Response:
{ {
"quote_id": "mq_...", "quote_id": "mq_...",
"chain_id": 8453, "chain_id": 8453,
"regulatory_profile_id": "us_general_2026",
"currency": "USDC", "currency": "USDC",
"amount_atomic": "100000000", "amount_atomic": "100000000",
"decimals": 6, "decimals": 6,
@ -314,11 +315,14 @@ Response:
"status": "membership_active", "status": "membership_active",
"designation_code": "0217073045482", "designation_code": "0217073045482",
"display_token": "0217-0730-4548-2", "display_token": "0217-0730-4548-2",
"regulatory_profile_id": "us_general_2026",
"tx_hash": "0x...", "tx_hash": "0x...",
"activated_at": "2026-02-17T07:33:09Z" "activated_at": "2026-02-17T07:33:09Z"
} }
``` ```
`regulatory_profile_id` binds quote/activation responses to deployment policy posture (`us_general_2026` or `eu_ai_act_2026_baseline`).
## Security Controls ## Security Controls
1. Intent TTL and one-time nonce consumption. 1. Intent TTL and one-time nonce consumption.