{"record":{"id":"b359ea7cc03f71bd","repo":"micro/go-micro","slug":"ap2-mandate-kind-is-required","errorCode":null,"errorMessage":"ap2: mandate kind is required","messagePattern":"ap2: mandate kind is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gateway/a2a/ap2.go","lineNumber":71,"sourceCode":"type AP2Verification struct {\n\tMandateID string `json:\"mandateId\"`\n\tKind      string `json:\"kind\"`\n\tVerified  bool   `json:\"verified\"`\n\tError     string `json:\"error,omitempty\"`\n}\n\n// NewAP2Keypair returns an Ed25519 keypair suitable for tests or local demos.\nfunc NewAP2Keypair() (ed25519.PublicKey, ed25519.PrivateKey, error) {\n\treturn ed25519.GenerateKey(rand.Reader)\n}\n\n// SignAP2Mandate signs a mandate as a verifiable AP2 credential.\nfunc SignAP2Mandate(m AP2Mandate, keyID string, private ed25519.PrivateKey) (AP2SignedMandate, error) {\n\tif m.ID == \"\" {\n\t\treturn AP2SignedMandate{}, errors.New(\"ap2: mandate id is required\")\n\t}\n\tif m.Kind == \"\" {\n\t\treturn AP2SignedMandate{}, errors.New(\"ap2: mandate kind is required\")\n\t}\n\tif m.IssuedAt.IsZero() {\n\t\tm.IssuedAt = time.Now().UTC()\n\t}\n\tpayload, err := ap2Payload(m)\n\tif err != nil {\n\t\treturn AP2SignedMandate{}, err\n\t}\n\treturn AP2SignedMandate{Mandate: m, KeyID: keyID, Signature: base64.RawURLEncoding.EncodeToString(ed25519.Sign(private, payload))}, nil\n}\n\n// VerifyAP2Mandate verifies a signed mandate credential.\nfunc VerifyAP2Mandate(s AP2SignedMandate, public ed25519.PublicKey) error {\n\tsig, err := base64.RawURLEncoding.DecodeString(s.Signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"ap2: invalid signature encoding: %w\", err)\n\t}\n\tpayload, err := ap2Payload(s.Mandate)","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/gateway/a2a/ap2.go#L53-L89","documentation":"SignAP2Mandate requires the mandate's Kind field to be non-empty because the kind (e.g. checkout vs payment mandate) is part of the signed payload and tells verifiers what type of AP2 credential they are checking. An empty Kind would produce an ambiguous, unverifiable credential, so signing is refused.","triggerScenarios":"Calling SignAP2Mandate with an AP2Mandate where ID is set but Kind is the empty string — the second guard in the validation chain.","commonSituations":"Constructing mandates without knowing which kind to use; copying struct literals between checkout and payment flows and dropping the Kind; JSON inputs lacking the kind key.","solutions":["Set Kind to the appropriate value (e.g. \"checkout\" or \"payment\") before signing.","Derive the kind from the calling context (checkout flow vs payment/x402 rail) instead of leaving it unset.","Validate the kind at the API boundary that accepts mandate input."],"exampleFix":"// before\nm := ap2.AP2Mandate{ID: id} // Kind empty\nsigned, err := ap2.SignAP2Mandate(m, keyID, priv)\n\n// after\nm := ap2.AP2Mandate{ID: id, Kind: \"checkout\"}\nsigned, err := ap2.SignAP2Mandate(m, keyID, priv)","handlingStrategy":"validation","validationCode":"validKinds := map[string]bool{\"checkout\": true, \"payment\": true}\nif !validKinds[m.Kind] {\n\treturn fmt.Errorf(\"cannot sign mandate: Kind must be one of checkout|payment\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := ap2.SignAP2Mandate(m, keyID, priv); err != nil {\n\tif strings.Contains(err.Error(), \"mandate kind is required\") {\n\t\t// set the kind based on flow and retry once\n\t}\n}","preventionTips":["Model Kind as a typed enum/constant set instead of a free-form string.","Derive Kind from the calling flow rather than leaving it to the caller.","Reject empty kind in JSON unloading with custom UnmarshalJSON validation."],"tags":["ap2","validation","missing-field","signing"],"backgroundTag":"missing-required-argument","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}