# GENERATED copy of tools/openapi/arazzo.yaml - edit that file and run `npm run openapi`.
arazzo: 1.1.0
info:
  title: Verdant Dock - main flows
  summary: Executable workflows for the Verdant Dock demonstration store (software-testing practice; nothing is real).
  description: |
    Each workflow chains real API operations (by operationId) and passes values between them with runtime
    expressions: email codes come from the test mailbox (getLatestMail), TOTP codes from getTestTotp, ids and
    tokens from earlier responses. `fullJourney` runs them all in order for a brand-new customer and staff user.

    Before running: pick fresh addresses at the stack's mail domain (no inbox needed), for example
    `arazzo.<timestamp>@verdant-v1.testthis.now` and `arazzo.staff.<timestamp>@verdant-v1.testthis.now`.
    Staff only see customers created from the same network, so run everything from one machine (or send the
    same X-Test-Partition header on every request).

    Not covered here: claim photo uploads (a multipart POST to the S3 URL returned by createClaimUpload is not
    an API operation), so the claims part stops at the claim form. Step-by-step guide: /api-guide.md.
  version: 1.0.0
sourceDescriptions:
  - name: verdantDock
    url: openapi-3.2.yaml
    type: openapi

components:
  inputs:
    customer:
      type: object
      required: [email]
      properties:
        email:
          type: string
          description: A new email address, e.g. arazzo.1760000000@verdant-v1.testthis.now (read through the test mailbox)
        password:
          type: string
          default: Plant3r!Dock2026
          description: 12+ characters with upper, lower, digit and symbol
        givenName: { type: string, default: Arazzo }
        familyName: { type: string, default: Tester }
        mobile: { type: string, default: 0412 345 678 }
    signedIn:
      type: object
      required: [accessToken]
      properties:
        accessToken: { type: string, description: Customer access token (15 minutes) }
  failureActions:
    waitForMail:
      name: waitForMail
      type: retry
      retryAfter: 3
      retryLimit: 5
      criteria:
        - condition: $statusCode == 404

workflows:
  # ------------------------------------------------------------------ customer sign-up
  - workflowId: customerSignUp
    summary: Sign up a new customer - email code from the test mailbox, TOTP enrolment
    description: signup -> getLatestMail (code) -> verifyEmail -> startMfaEnrolment -> getTestTotp -> activateMfa -> getMe.
    inputs:
      $ref: '#/components/inputs/customer'
    steps:
      - stepId: signUp
        operationId: signup
        requestBody:
          contentType: application/json
          payload:
            email: $inputs.email
            password: $inputs.password
            givenName: $inputs.givenName
            familyName: $inputs.familyName
            mobile: $inputs.mobile
            acceptTerms: true
        successCriteria:
          - condition: $statusCode == 202
      - stepId: readVerificationCode
        operationId: getLatestMail
        parameters:
          - { name: to, in: query, value: $inputs.email }
          - { name: subject, in: query, value: verify }
          - { name: wait, in: query, value: 10 }
        successCriteria:
          - condition: $statusCode == 200
        onFailure:
          - reference: $components.failureActions.waitForMail
        outputs:
          code: $response.body#/code
      - stepId: verifyEmail
        operationId: verifyEmail
        requestBody:
          payload:
            email: $inputs.email
            code: $steps.readVerificationCode.outputs.code
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == 'MFA_ENROLMENT_REQUIRED'
        outputs:
          enrolmentToken: $response.body#/enrolmentToken
      - stepId: startMfaEnrolment
        operationId: startMfaEnrolment
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.verifyEmail.outputs.enrolmentToken}' }
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          secret: $response.body#/secret
      - stepId: computeTotp
        description: The current TOTP code for the new secret (RFC 6238; any TOTP library gives the same code).
        operationId: getTestTotp
        parameters:
          - { name: secret, in: query, value: $steps.startMfaEnrolment.outputs.secret }
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          code: $response.body#/code
      - stepId: activateMfa
        operationId: activateMfa
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.verifyEmail.outputs.enrolmentToken}' }
        requestBody:
          payload:
            code: $steps.computeTotp.outputs.code
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == 'ACTIVE'
        outputs:
          accessToken: $response.body#/accessToken
          refreshToken: $response.body#/refreshToken
          recoveryCode: $response.body#/recoveryCodes/0
      - stepId: readProfile
        operationId: getMe
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.activateMfa.outputs.accessToken}' }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/mfaEnabled == true
        outputs:
          userId: $response.body#/userId
    outputs:
      accessToken: $steps.activateMfa.outputs.accessToken
      refreshToken: $steps.activateMfa.outputs.refreshToken
      totpSecret: $steps.startMfaEnrolment.outputs.secret
      recoveryCode: $steps.activateMfa.outputs.recoveryCode
      userId: $steps.readProfile.outputs.userId

  # ------------------------------------------------------------------ sign in
  - workflowId: customerSignIn
    summary: Sign in with password and TOTP, then rotate the refresh token
    description: |
      login -> getTestTotp -> loginMfa -> refreshToken. Uses `nextCode` (the code for the next 30-second step,
      which the API also accepts) so this can follow straight after sign-up without reusing a code.
    inputs:
      type: object
      required: [email, password, totpSecret]
      properties:
        email: { type: string }
        password: { type: string }
        totpSecret: { type: string, description: The base32 secret from startMfaEnrolment }
    steps:
      - stepId: password
        operationId: login
        requestBody:
          payload:
            email: $inputs.email
            password: $inputs.password
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == 'MFA_REQUIRED'
        outputs:
          mfaToken: $response.body#/mfaToken
      - stepId: signInTotp
        operationId: getTestTotp
        parameters:
          - { name: secret, in: query, value: $inputs.totpSecret }
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          nextCode: $response.body#/nextCode
      - stepId: secondFactor
        operationId: loginMfa
        requestBody:
          payload:
            mfaToken: $steps.password.outputs.mfaToken
            code: $steps.signInTotp.outputs.nextCode
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == 'AUTHENTICATED'
        outputs:
          refreshToken: $response.body#/refreshToken
      - stepId: rotateRefreshToken
        operationId: refreshToken
        requestBody:
          payload:
            refreshToken: $steps.secondFactor.outputs.refreshToken
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          accessToken: $response.body#/accessToken
          refreshToken: $response.body#/refreshToken
    outputs:
      accessToken: $steps.rotateRefreshToken.outputs.accessToken
      refreshToken: $steps.rotateRefreshToken.outputs.refreshToken

  # ------------------------------------------------------------------ truck order
  - workflowId: truckOrder
    summary: Buy an insured AutoPlanter and matching plant, delivered by truck; check the invoice email
    description: |
      putCartItem x2 -> createAddress -> quoteCheckout -> listDeliverySlots (firstAvailable) -> placeOrder ->
      getOrder -> getInvoicePdf -> getLatestMail (confirmation) -> getMailAttachment (invoice text).
      Insuring both a planter and a plant earns a free expert check-in.
    inputs:
      type: object
      required: [accessToken, email]
      properties:
        accessToken: { type: string }
        email: { type: string, description: The customer's address (to read the confirmation from the test mailbox) }
        cardNumber: { type: string, default: 4111 1111 1111 1111 }
        cvv: { type: string, default: '123', description: A CVV ending in 0 is declined }
    steps:
      - stepId: addPlanter
        operationId: putCartItem
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: sku, in: path, value: VD-AP-M }
        requestBody:
          payload: { quantity: 1, coverPlan: VC-2Y }
        successCriteria:
          - condition: $statusCode == 200
      - stepId: addPlant
        operationId: putCartItem
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: sku, in: path, value: VD-PL-MT-M }
        requestBody:
          payload: { quantity: 1, coverPlan: TC-1Y }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/benefits/0/code == 'EXPERT_CHECKIN'
      - stepId: addAddress
        operationId: createAddress
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        requestBody:
          payload:
            label: Home
            line1: 12 Fern Street
            suburb: Glen Waverley
            state: VIC
            postcode: '3150'
            phone: 0412 345 678
            siteAccess: GROUND
            instructions: Side gate, code 4821
        successCriteria:
          - condition: $statusCode == 201
          - condition: $response.body#/serviceArea/serviceable == true
        outputs:
          addressId: $response.body#/addressId
      - stepId: quote
        operationId: quoteCheckout
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        requestBody:
          payload:
            addressId: $steps.addAddress.outputs.addressId
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/canCheckout == true
          - condition: $response.body#/delivery/method == 'TRUCK'
        outputs:
          cartVersion: $response.body#/cartVersion
          totalCents: $response.body#/totals/totalCents
      - stepId: findSlot
        operationId: listDeliverySlots
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: addressId, in: query, value: $steps.addAddress.outputs.addressId }
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          date: $response.body#/firstAvailable/date
          window: $response.body#/firstAvailable/window
      - stepId: placeOrder
        operationId: placeOrder
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: Idempotency-Key, in: header, value: 'order-{$steps.addAddress.outputs.addressId}' }
        requestBody:
          payload:
            addressId: $steps.addAddress.outputs.addressId
            cartVersion: $steps.quote.outputs.cartVersion
            expectedTotalCents: $steps.quote.outputs.totalCents
            delivery:
              date: $steps.findSlot.outputs.date
              window: $steps.findSlot.outputs.window
            payment:
              nameOnCard: Arazzo Tester
              cardNumber: $inputs.cardNumber
              expiryMonth: '12'
              expiryYear: '2030'
              cvv: $inputs.cvv
        successCriteria:
          - condition: $statusCode == 201
          - condition: $response.body#/status == 'CONFIRMED'
        outputs:
          orderId: $response.body#/orderId
          orderNumber: $response.body#/orderNumber
          invoiceNumber: $response.body#/invoice/invoiceNumber
          deliveryDate: $response.body#/delivery/date
      - stepId: readOrder
        operationId: getOrder
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: orderId, in: path, value: $steps.placeOrder.outputs.orderId }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/checkin/status == 'NOT_ACTIVATED'
      - stepId: downloadInvoice
        operationId: getInvoicePdf
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: orderId, in: path, value: $steps.placeOrder.outputs.orderId }
        successCriteria:
          - condition: $statusCode == 200
          - context: $response.header.Content-Type
            condition: ^application/pdf
            type: regex
      - stepId: readConfirmationEmail
        operationId: getLatestMail
        parameters:
          - { name: to, in: query, value: $inputs.email }
          - { name: subject, in: query, value: confirmed }
          - { name: wait, in: query, value: 10 }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/attachments/0/contentType == 'application/pdf'
        onFailure:
          - reference: $components.failureActions.waitForMail
        outputs:
          messageId: $response.body#/messageId
      - stepId: readInvoiceText
        operationId: getMailAttachment
        parameters:
          - { name: to, in: query, value: $inputs.email }
          - { name: messageId, in: query, value: $steps.readConfirmationEmail.outputs.messageId }
          - { name: index, in: query, value: 0 }
          - { name: format, in: query, value: text }
        successCriteria:
          - condition: $statusCode == 200
          - context: $response.body#/text
            condition: NOT A REAL TAX INVOICE
            type: regex
          - context: $response.body#/text
            condition: GST \(10%\)
            type: regex
    outputs:
      orderId: $steps.placeOrder.outputs.orderId
      orderNumber: $steps.placeOrder.outputs.orderNumber
      invoiceNumber: $steps.placeOrder.outputs.invoiceNumber
      addressId: $steps.addAddress.outputs.addressId
      deliveryDate: $steps.placeOrder.outputs.deliveryDate

  # ------------------------------------------------------------------ declined card
  - workflowId: declinedPayment
    summary: A CVV ending in 0 is declined - 402, nothing charged, truck booking released
    inputs:
      type: object
      required: [accessToken, addressId]
      properties:
        accessToken: { type: string }
        addressId: { type: string, description: A truck-serviceable address (e.g. from truckOrder) }
    steps:
      - stepId: addSmallPlanter
        operationId: putCartItem
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: sku, in: path, value: VD-AP-S }
        requestBody:
          payload: { quantity: 1 }
        successCriteria:
          - condition: $statusCode == 200
      - stepId: findSlotForDeclined
        operationId: listDeliverySlots
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: addressId, in: query, value: $inputs.addressId }
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          date: $response.body#/firstAvailable/date
          window: $response.body#/firstAvailable/window
      - stepId: placeDeclinedOrder
        operationId: placeOrder
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: Idempotency-Key, in: header, value: 'declined-{$inputs.addressId}' }
        requestBody:
          payload:
            addressId: $inputs.addressId
            delivery:
              date: $steps.findSlotForDeclined.outputs.date
              window: $steps.findSlotForDeclined.outputs.window
            payment: { nameOnCard: Arazzo Tester, cardNumber: 4111 1111 1111 1111, expiryMonth: '12', expiryYear: '2030', cvv: '120' }
        successCriteria:
          - condition: $statusCode == 402
          - condition: $response.body#/code == 'PAYMENT_DECLINED'
      - stepId: emptyCart
        operationId: clearCart
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        successCriteria:
          - condition: $statusCode == 204 || $statusCode == 200

  # ------------------------------------------------------------------ courier order
  - workflowId: courierOrder
    summary: Books and accessories only - $25 courier anywhere in Australia, no slot; cancel before dispatch
    inputs:
      $ref: '#/components/inputs/signedIn'
    steps:
      - stepId: addBook
        operationId: putCartItem
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: sku, in: path, value: VD-BK-CT }
        requestBody:
          payload: { quantity: 1 }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/deliveryMethod == 'COURIER'
      - stepId: addPerthAddress
        operationId: createAddress
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        requestBody:
          payload: { label: Perth office, line1: 10 Hay Street, suburb: Perth, state: WA, postcode: '6000' }
        successCriteria:
          - condition: $statusCode == 201
        outputs:
          addressId: $response.body#/addressId
      - stepId: courierQuote
        operationId: quoteCheckout
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        requestBody:
          payload:
            addressId: $steps.addPerthAddress.outputs.addressId
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/delivery/method == 'COURIER'
          - condition: $response.body#/totals/deliveryCents == 2500
      - stepId: placeCourierOrder
        operationId: placeOrder
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: Idempotency-Key, in: header, value: 'courier-{$steps.addPerthAddress.outputs.addressId}' }
        requestBody:
          payload:
            addressId: $steps.addPerthAddress.outputs.addressId
            payment: { nameOnCard: Arazzo Tester, cardNumber: 5555 5555 5555 4444, expiryMonth: '11', expiryYear: '2031', cvv: '321' }
        successCriteria:
          - condition: $statusCode == 201
          - condition: $response.body#/delivery/method == 'COURIER'
          - context: $response.body#/delivery/trackingNumber
            condition: ^SPC[0-9A-Z]{10}$
            type: regex
        outputs:
          orderId: $response.body#/orderId
          trackingNumber: $response.body#/delivery/trackingNumber
      - stepId: cancelBeforeDispatch
        operationId: cancelOrder
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: orderId, in: path, value: $steps.placeCourierOrder.outputs.orderId }
        requestBody:
          payload: { reason: Ordered by mistake }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == 'CANCELLED'
    outputs:
      orderId: $steps.placeCourierOrder.outputs.orderId
      trackingNumber: $steps.placeCourierOrder.outputs.trackingNumber

  # ------------------------------------------------------------------ test clock and cover
  - workflowId: testClockAndCover
    summary: Move this session's business date past delivery - projected DELIVERED order, active cover, claims tab
    description: Nothing projected is saved or emailed; resetTestClock (or signing in again) returns to the real date.
    inputs:
      type: object
      required: [accessToken, orderId]
      properties:
        accessToken: { type: string }
        orderId: { type: string, description: An order with insured items (e.g. from truckOrder) }
        offsetDays: { type: integer, default: 40, description: Days ahead; must be past the delivery date }
    steps:
      - stepId: setClock
        operationId: setTestClock
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        requestBody:
          payload:
            offsetDays: $inputs.offsetDays
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/simulated == true
      - stepId: projectedOrder
        operationId: getOrder
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: orderId, in: path, value: $inputs.orderId }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == 'DELIVERED'
          - condition: $response.body#/projected == true
      - stepId: cover
        operationId: listPolicies
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/summary/active == 2
      - stepId: claimsTab
        operationId: listClaims
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/showTab == true
        outputs:
          policyId: $response.body#/policies/0/policyId
      - stepId: claimForm
        operationId: getClaimForm
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: policyId, in: path, value: $steps.claimsTab.outputs.policyId }
        successCriteria:
          - condition: $statusCode == 200
      - stepId: resetClock
        operationId: resetTestClock
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/offsetDays == 0
    outputs:
      policyId: $steps.claimsTab.outputs.policyId

  # ------------------------------------------------------------------ support case
  - workflowId: supportCase
    summary: Raise a support case about an order, add a message, read it, close it
    inputs:
      type: object
      required: [accessToken, orderId]
      properties:
        accessToken: { type: string }
        orderId: { type: string }
    steps:
      - stepId: raise
        operationId: createCase
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
        requestBody:
          payload:
            type: SUPPORT
            subject: Which way should the dock face?
            message: Does the docking station need to face the window?
            orderId: $inputs.orderId
        successCriteria:
          - condition: $statusCode == 201
          - condition: $response.body#/status == 'OPEN'
        outputs:
          caseId: $response.body#/caseId
      - stepId: addMessage
        operationId: replyToCase
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: caseId, in: path, value: $steps.raise.outputs.caseId }
        requestBody:
          payload: { message: Also, can it go on carpet? }
        successCriteria:
          - condition: $statusCode == 200
      - stepId: read
        operationId: getCase
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: caseId, in: path, value: $steps.raise.outputs.caseId }
        successCriteria:
          - condition: $statusCode == 200
      - stepId: close
        operationId: closeCase
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$inputs.accessToken}' }
          - { name: caseId, in: path, value: $steps.raise.outputs.caseId }
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == 'CLOSED'
    outputs:
      caseId: $steps.raise.outputs.caseId

  # ------------------------------------------------------------------ staff portal
  - workflowId: staffReview
    summary: Enrol a staff user on the same network and review the customer, their order and their case
    inputs:
      type: object
      required: [staffEmail, customerUserId, orderId, caseId]
      properties:
        staffEmail: { type: string, description: A new address at the stack's mail domain }
        password: { type: string, default: Plant3r!Dock2026 }
        customerUserId: { type: string }
        orderId: { type: string }
        caseId: { type: string }
    steps:
      - stepId: staffEnrol
        operationId: staffEnrol
        requestBody:
          payload:
            email: $inputs.staffEmail
            password: $inputs.password
            givenName: Arazzo
            familyName: Staff
            jobTitle: Customer service
            acceptTerms: true
        successCriteria:
          - condition: $statusCode == 202
      - stepId: readStaffCode
        operationId: getLatestMail
        parameters:
          - { name: to, in: query, value: $inputs.staffEmail }
          - { name: subject, in: query, value: code }
          - { name: wait, in: query, value: 10 }
        successCriteria:
          - condition: $statusCode == 200
        onFailure:
          - reference: $components.failureActions.waitForMail
        outputs:
          code: $response.body#/code
      - stepId: staffVerifyEmail
        operationId: staffVerifyEmail
        requestBody:
          payload:
            email: $inputs.staffEmail
            code: $steps.readStaffCode.outputs.code
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          enrolmentToken: $response.body#/enrolmentToken
      - stepId: staffStartMfa
        operationId: staffStartMfaEnrolment
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.staffVerifyEmail.outputs.enrolmentToken}' }
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          secret: $response.body#/secret
      - stepId: staffTotp
        operationId: getTestTotp
        parameters:
          - { name: secret, in: query, value: $steps.staffStartMfa.outputs.secret }
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          code: $response.body#/code
      - stepId: staffActivateMfa
        operationId: staffActivateMfa
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.staffVerifyEmail.outputs.enrolmentToken}' }
        requestBody:
          payload:
            code: $steps.staffTotp.outputs.code
        successCriteria:
          - condition: $statusCode == 200
        outputs:
          staffToken: $response.body#/accessToken
      - stepId: staffCustomer
        operationId: staffGetCustomer
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.staffActivateMfa.outputs.staffToken}' }
          - { name: userId, in: path, value: $inputs.customerUserId }
        successCriteria:
          - condition: $statusCode == 200
      - stepId: staffOrder
        operationId: staffGetOrder
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.staffActivateMfa.outputs.staffToken}' }
          - { name: userId, in: path, value: $inputs.customerUserId }
          - { name: orderId, in: path, value: $inputs.orderId }
        successCriteria:
          - condition: $statusCode == 200
      - stepId: staffCase
        operationId: staffGetCase
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.staffActivateMfa.outputs.staffToken}' }
          - { name: caseId, in: path, value: $inputs.caseId }
        successCriteria:
          - condition: $statusCode == 200
      - stepId: staffReply
        operationId: staffReplyToCase
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.staffActivateMfa.outputs.staffToken}' }
          - { name: caseId, in: path, value: $inputs.caseId }
        requestBody:
          payload: { message: Face it into the room - it works on carpet too. }
        successCriteria:
          - condition: $statusCode == 200
      - stepId: staffSignOut
        operationId: staffLogout
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$steps.staffActivateMfa.outputs.staffToken}' }
        successCriteria:
          - condition: $statusCode == 204

  # ------------------------------------------------------------------ everything, end to end
  - workflowId: fullJourney
    summary: The whole customer journey plus staff review, for a brand-new customer and staff user
    inputs:
      type: object
      required: [email, staffEmail]
      properties:
        email: { type: string, description: 'New customer address, e.g. arazzo.1760000000@verdant-v1.testthis.now' }
        staffEmail: { type: string, description: 'New staff address, e.g. arazzo.staff.1760000000@verdant-v1.testthis.now' }
        password: { type: string, default: Plant3r!Dock2026 }
    steps:
      - stepId: journeySignUp
        workflowId: customerSignUp
        parameters:
          - { name: email, value: $inputs.email }
          - { name: password, value: $inputs.password }
      - stepId: journeySignIn
        workflowId: customerSignIn
        parameters:
          - { name: email, value: $inputs.email }
          - { name: password, value: $inputs.password }
          - { name: totpSecret, value: $workflows.customerSignUp.outputs.totpSecret }
      - stepId: journeyOrder
        workflowId: truckOrder
        parameters:
          - { name: accessToken, value: $workflows.customerSignIn.outputs.accessToken }
          - { name: email, value: $inputs.email }
      - stepId: journeyDeclined
        workflowId: declinedPayment
        parameters:
          - { name: accessToken, value: $workflows.customerSignIn.outputs.accessToken }
          - { name: addressId, value: $workflows.truckOrder.outputs.addressId }
      - stepId: journeyCourier
        workflowId: courierOrder
        parameters:
          - { name: accessToken, value: $workflows.customerSignIn.outputs.accessToken }
      - stepId: journeyTestClock
        workflowId: testClockAndCover
        parameters:
          - { name: accessToken, value: $workflows.customerSignIn.outputs.accessToken }
          - { name: orderId, value: $workflows.truckOrder.outputs.orderId }
      - stepId: journeySupport
        workflowId: supportCase
        parameters:
          - { name: accessToken, value: $workflows.customerSignIn.outputs.accessToken }
          - { name: orderId, value: $workflows.truckOrder.outputs.orderId }
      - stepId: journeyStaff
        workflowId: staffReview
        parameters:
          - { name: staffEmail, value: $inputs.staffEmail }
          - { name: password, value: $inputs.password }
          - { name: customerUserId, value: $workflows.customerSignUp.outputs.userId }
          - { name: orderId, value: $workflows.truckOrder.outputs.orderId }
          - { name: caseId, value: $workflows.supportCase.outputs.caseId }
      - stepId: journeySignOut
        operationId: logout
        parameters:
          - { name: Authorization, in: header, value: 'Bearer {$workflows.customerSignIn.outputs.accessToken}' }
        successCriteria:
          - condition: $statusCode == 204
    outputs:
      orderNumber: $workflows.truckOrder.outputs.orderNumber
      invoiceNumber: $workflows.truckOrder.outputs.invoiceNumber
      courierTrackingNumber: $workflows.courierOrder.outputs.trackingNumber
      caseId: $workflows.supportCase.outputs.caseId
