intent reference generation + propagation

This commit is contained in:
Stephan D
2026-02-26 18:43:44 +01:00
parent 0f95f898a8
commit 7661038868
18 changed files with 816 additions and 24 deletions

View File

@@ -205,7 +205,7 @@ func (s *svc) resolveAndPlan(ctx context.Context, requestCtx *reqval.Ctx) (*qsna
IntentRef: requestCtx.IntentRef,
})
if err != nil {
return nil, nil, err
return nil, nil, remapResolveError(err)
}
graph, err := s.planner.Compile(xplan.Input{
IntentSnapshot: resolved.IntentSnapshot,
@@ -269,6 +269,15 @@ func remapIdempotencyError(err error) error {
return err
}
func remapResolveError(err error) error {
switch {
case errors.Is(err, qsnap.ErrIntentRefRequired), errors.Is(err, qsnap.ErrIntentRefNotFound):
return merrors.InvalidArgument(err.Error())
default:
return err
}
}
func mustFingerprint(idemSvc idem.Service, requestCtx *reqval.Ctx) string {
if idemSvc == nil || requestCtx == nil {
return ""

View File

@@ -106,6 +106,27 @@ func TestExecutePayment_IdempotencyMismatch(t *testing.T) {
}
}
func TestExecutePayment_BatchQuoteRequiresIntentRef(t *testing.T) {
env := newTestEnv(t, func(_ string, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
step := req.StepExecution
step.State = agg.StepStateCompleted
return &sexec.ExecuteOutput{StepExecution: step}, nil
})
env.quotes.Put(newExecutableBatchQuote(env.orgID, "quote-batch", []string{"intent-a", "intent-b"}, buildLedgerRoute()))
_, err := env.svc.ExecutePayment(context.Background(), &orchestrationv2.ExecutePaymentRequest{
Meta: testMeta(env.orgID, "idem-batch"),
QuotationRef: "quote-batch",
ClientPaymentRef: "client-batch",
})
if !errors.Is(err, merrors.ErrInvalidArg) {
t.Fatalf("expected invalid argument for missing intent_ref, got %v", err)
}
if got := err.Error(); !strings.Contains(got, "intent_ref is required for batch quotation") {
t.Fatalf("unexpected error message: %q", got)
}
}
func TestExecutePayment_RetryThenSuccess(t *testing.T) {
env := newTestEnv(t, func(_ string, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
if req.StepExecution.Attempt == 1 {
@@ -627,6 +648,39 @@ func newExecutableQuote(orgRef bson.ObjectID, quoteRef, intentRef string, route
}
}
func newExecutableBatchQuote(orgRef bson.ObjectID, quoteRef string, intentRefs []string, route *paymenttypes.QuoteRouteSpecification) *model.PaymentQuoteRecord {
now := time.Now().UTC()
items := make([]*model.PaymentQuoteItemV2, 0, len(intentRefs))
for _, intentRef := range intentRefs {
items = append(items, &model.PaymentQuoteItemV2{
Intent: &model.PaymentIntent{
Ref: intentRef,
Kind: model.PaymentKindPayout,
Source: testLedgerEndpoint("ledger-src"),
Destination: testLedgerEndpoint("ledger-dst"),
Amount: &paymenttypes.Money{Amount: "10", Currency: "USD"},
SettlementCurrency: "USD",
},
Quote: &model.PaymentQuoteSnapshot{
QuoteRef: quoteRef,
DebitAmount: &paymenttypes.Money{Amount: "10", Currency: "USD"},
Route: route,
},
Status: &model.QuoteStatusV2{State: model.QuoteStateExecutable},
})
}
return &model.PaymentQuoteRecord{
Base: modelBase(now),
OrganizationBoundBase: pm.OrganizationBoundBase{
OrganizationRef: orgRef,
},
QuoteRef: quoteRef,
RequestShape: model.QuoteRequestShapeBatch,
Items: items,
ExpiresAt: now.Add(1 * time.Hour),
}
}
func buildLedgerRoute() *paymenttypes.QuoteRouteSpecification {
return &paymenttypes.QuoteRouteSpecification{
Hops: []*paymenttypes.QuoteRouteHop{