Skip to content

Instantly share code, notes, and snippets.

@jclyne
Created February 11, 2026 17:32
Show Gist options
  • Select an option

  • Save jclyne/bc4c7621af9475ecd2ffd0644b4b361c to your computer and use it in GitHub Desktop.

Select an option

Save jclyne/bc4c7621af9475ecd2ffd0644b4b361c to your computer and use it in GitHub Desktop.

Client Behavior Specification: ActionCard + CompleteScenario Plasma Flow

Date: 2026-02-11 Author: jclyne Status: Draft Related Plan: thoughts/shared/plans/2026-02-10-actioncard-completescenario-plasma-flow.md Related Research: thoughts/shared/research/2026-02-10-clientrenderable-actioncard-plasma-flow-architecture.md Related Review: thoughts/shared/reviews/2026-02-11-actioncard-completescenario-design-review.md


1. Overview

This specification describes the client-side behavior for enabling plasma-adapter MCP tools to return ActionCard UI components that trigger completeScenario Plasma flows. It covers both cash-ios (with actual code references) and cash-android.

Design Approach

The server creates the Plasma flow server-side to discover blockers. Agent-friendly blockers (requiresForm == false) are resolved conversationally by the LLM. Non-agent-friendly blockers (requiresForm == true) produce an ActionCard for client handoff. When the client taps the ActionCard and calls completeScenario with the same flow token, Plasma's PlasmaFlowCreator returns the existing flow idempotently. There is minor redundant processing on this second call (see design review Section 2.1) which is accepted as a trade-off to enable conversational blocker resolution.

Capabilities

  1. ActionCard with CompleteScenario Route: MCP tools (EnableCard, DisableCard, CreateSavingsGoal) return ClientRenderable ActionCards with a server-provided flow_token. When tapped, the client calls Plasma's completeScenario API with that token, resuming the existing flow and rendering remaining blockers natively.

  2. Completion Card Replacement: When a flow completes, the Plasma KgoosePublisher emits a CompletePlasmaFlow event. The client receives a clientRenderingUpdate activity and replaces the original ActionCard with a completion card.

  3. Agent-Friendly Blocker Routing (Future): When the client encounters a blocker with agent_friendly = true during a kgoose-initiated Plasma flow, the client navigates back to chat and sends a hidden message with blocker details for LLM handling.


2. cash-ios: Current State (What Already Works)

2.1. CompleteScenario Route -- ALREADY EXISTS with flowToken Support

The .completeScenario client route is already registered in FlowsPlugin.swift and already accepts a flowToken:

File: Code/CoreLibraries/Flows/FlowsPlugin/Sources/FlowsPlugin.swift

registry.register(.completeScenario) { [weak self] payload, routingParams in
    guard let self else { return }
    let clientScenarioPayload = ClientRoute.ClientScenario(clientScenarioName: payload.clientScenario)

    var updatedRoutingParams = routingParams
    updatedRoutingParams.analyticsParams.payload = .startPlasmaFlow(.init(flowToken: payload.flowToken))

    navigator.navigate(
        to: .clientScenario(clientScenarioPayload),
        metadata: updatedRoutingParams,
    )
}

The route reroutes to .clientScenario with the flowToken embedded in StartPlasmaFlowAnalyticsParams.

Test confirmation (FlowsPluginTests.swift):

func test_completeScenario() throws {
    let payload = ClientRoute.CompleteScenario(
        clientScenario: "TEST_SCENARIO",
        flowToken: "test-flow-token-123",
    )
    // ... navigates to .clientScenario with flowToken in startPlasmaFlowAnalyticsParams
}

2.2. ClientScenario Route -- ALREADY Extracts flowToken and Goose Context

File: Code/OSHooks/ClientRouting/ClientRoutingImplementations/Sources/SignedInFlowsPlugin.swift

The .clientScenario handler extracts the flow token and passes goose context:

registry.register(.clientScenario) { [weak self] (payload, routingParams) in
    guard let self else { return }
    guard let scenario = SQPBFranklinApiClientScenario(string: payload.clientScenarioName.uppercased()) else {
        fatalError("No client scenario exists with the name: \(payload.clientScenarioName)")
    }

    // Get the flow token from the routing params, use a new token if we don't have one
    let flowTokenString = routingParams.analyticsParams.startPlasmaFlowAnalyticsParams?.flowToken
    let flowToken = flowTokenString.flatMap { try? FlowToken($0) } ?? .init()

    // If we have a scenario plan map, use it. Otherwise call through to complete-scenario
    let scenarioPlan = scenarioPlanMapProvider()?.scenarioPlan(for: scenario)
    if let scenarioPlan, scenarioPlan.hasBlockers {
        let flow = legacyFlowFactory.makeFlow(
            clientScenario: scenario,
            flowToken: flowToken,
            // ...
            scenarioPlan: scenarioPlan,
        )
        presentFlow(flow, source: routingParams.source)
    } else {
        presentClientScenario(scenario, source: routingParams.source, flowToken: flowToken)
    }
}

The presentClientScenario method extracts goose IDs and builds the request:

private func presentClientScenario(
    _ clientScenario: SQPBFranklinApiClientScenario,
    source: RoutingParams.Source,
    flowToken: FlowToken = .init(),
) {
    let (gooseSessionID, gooseToolRequestID) = extractMoneybotIDs(from: source)
    let request = CompleteScenarioRequest(
        clientScenario: clientScenario,
        gooseSessionID: gooseSessionID,
        gooseToolRequestID: gooseToolRequestID,
    )

    let loadingViewFlowViewController = chainedFlowServerFactory.makeChainedFlow(
        // ...
        serverRequest: request,
        gooseSessionID: gooseSessionID,
        sourceScreen: source.sourceString,
        flowToken: flowToken,
        config: .defaultConfig,
    )

    presentationManager.presentModally(loadingViewFlowViewController)
}

2.3. CompleteScenarioRequest -- Sets Goose Headers

File: Code/CoreLibraries/Flows/FlowFactories/Sources/CompleteScenarioRequest.swift

public struct CompleteScenarioRequest: FlowServerRequest, Hashable {
    public static let endpoint: RequestEndpoint = "2.0/cash/complete-scenario"

    public init(
        clientScenario: SQPBFranklinApiClientScenario,
        gooseSessionID: String? = nil,
        gooseToolRequestID: String? = nil,
        paymentTokens: [String] = [],
    ) {
        headers[RequestBuilder.HeaderKeys.clientScenario] = clientScenario.stringValue

        if let gooseSessionID {
            headers[GooseHeaders.sessionID] = gooseSessionID
        }
        if let gooseToolRequestID {
            headers[GooseHeaders.toolRequestID] = gooseToolRequestID
        }
    }
}

Note: The CompleteScenarioRequest does NOT set the Cash-Flow-Token header itself. The flowToken is passed separately to chainedFlowServerFactory.makeChainedFlow(flowToken:), which is responsible for setting it on the underlying HTTP request via the flow networking layer.

2.4. GooseHeaders and RoutingParams

File: Code/CoreLibraries/Flows/FlowNetworking/Sources/GooseHeaders.swift

public struct GooseHeaders {
    public static let sessionID = "Cash-Goose-Session-Id"
    public static let toolRequestID = "Cash-Goose-Tool-Request-Id"
}

File: Code/OSHooks/ClientRouting/ClientRouting/Sources/RoutingParams+Goose.swift

public extension RoutingParams {
    var gooseSessionID: String? {
        if case let .tap(.moneybot(sessionID, _)) = source {
            return sessionID
        }
        return nil
    }

    var gooseToolRequestID: String? {
        if case let .tap(.moneybot(_, toolRequestID)) = source {
            return toolRequestID
        }
        return nil
    }
}

File: Code/SystemResources/Networking/Networking/Sources/RequestBuilder.swift

extension RequestBuilder {
    public enum HeaderKeys {
        public static let clientScenario = "Cash-Client-Scenario"
        public static let flowToken = "Cash-Flow-Token"
    }
}

2.5. ActionCard Rendering Pipeline -- ALREADY WORKS

Proto parsing: ActionCardProto.swift → parses title, description, headerIcon, tapBehavior, accessory

Content factory: ActionCardContentFactory.swiftmakeContent() builds ActionCardModel from ActionCardProto, wiring up tap behavior with navigation

Tap behavior: TapBehaviorProto.swift.cardTapAction(CardTapActionProto) or .cardButtons(CardButtonsProto)

Card tap action: CardTapActionProto.swift → contains ClientRouteActionProto (has client_route_url) and optional hiddenMessage

Client route navigation on tap: ActionCardContentFactory.swift:

private func handleClientRouteNavigation(
    _ networkURL: NetworkURL,
    context: ClientRenderContext,
) async {
    await navigator.navigate(
        to: networkURL,
        source: .tap(.moneybot(sessionID: context.sessionID, toolRequestID: context.toolRequestID)),
    )
}

This sets source: .tap(.moneybot(...)) which propagates gooseSessionID and gooseToolRequestID through RoutingParams to the flow infrastructure.

View: ActionCard.swift → SwiftUI view with ActionCardModel, supports both .cardTapAction (whole card tap) and .cardButtons (primary/secondary buttons)

Model: ActionCardModel.swift → contains title, description, headerIcon, tapBehavior, accessory, isErrorState

2.6. Completion Card Replacement -- ALREADY WORKS

File: IncomingMessagesProcessor.swift

The processSessionActivities method handles clientRenderingUpdate activities, matching by toolRequestID:

func processSessionActivities(_ activities: [ActivityProto], ...) {
    for activity in newActivities {
        switch activity.activityType {
        case let .clientRenderingUpdate(clientRenderingUpdate):
            if let clientRenderable = clientRenderingUpdate.clientRenderable {
                if let renderedContent = renderClientRenderable(
                    clientRenderable,
                    sessionID: sessionID,
                    toolRequestID: clientRenderingUpdate.toolRequestID,
                    toolResponseID: clientRenderingUpdate.toolRequestID,
                    // ...
                ) {
                    updateRenderedContent(
                        potentialRenderedContent: renderedContent,
                        toolID: clientRenderingUpdate.toolRequestID,
                        timestamp: activity.created,
                    )
                }
            }
        }
    }
}

Content is keyed by toolRequestID in renderedContentByToolResponseID, and newer timestamps replace older ones. Header icons are preserved across transitions via transitionHeaderIconToActionCard.

2.7. Hidden Messages -- ALREADY WORKS

File: MessageSender.swift

public extension MessageSender {
    func sendHiddenMessage(_ text: String) async {
        await sendMessages([InputMessage(text: text, isHidden: true)], suggestionID: nil, clientSuggestionID: nil)
    }
}

Used by ActionCardContentFactory when CardTapActionProto.hiddenMessage or CardButtonsProto.hiddenMessage is present:

if let hiddenMessage {
    await messageSender.sendHiddenMessage(hiddenMessage)
}

2.8. FlowToken Format Difference

File: Code/CoreLibraries/Flows/FlowCore/Sources/FlowToken.swift

iOS generates 25-character alphanumeric tokens:

public struct FlowToken: Codable, Hashable, Sendable {
    public init() {
        // 25 random alphanumeric characters
        let allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        let string = (0..<25).map { _ in String(allowedCharacters.randomElement(using: &generator)!) }.joined()
        try! self.init(string)
    }

    public init(_ token: String) throws {
        guard !token.isEmpty else { throw Error.emptyToken }
        self.value = token
    }
}

Server generates UUIDs (PlasmaFlowAdapter.kt:44): UUID.randomUUID().toString()

The server-provided flow token (UUID format) will work fine on iOS since FlowToken.init(_ token: String) accepts any non-empty string. No format validation is performed.

2.9. BlockerDescriptor -- No agent_friendly Support

File: Code/CoreLibraries/Flows/FlowCore/Sources/BlockerDescriptor.swift

The iOS BlockerDescriptor wraps SQPBFranklinCommonScenariosBlockerDescriptor but does NOT expose agent_friendly. A codebase-wide search for agent_friendly returns zero results in cash-ios.


3. cash-ios: Required Changes

Phase 1: ActionCards with Server-Provided Flow Token (Zero iOS Changes)

The server returns ActionCards with a flow_token query parameter. Zero iOS changes are needed because the entire infrastructure already exists:

  • Server returns ActionCard with URL /dl/complete-scenario/ENABLE_ISSUED_CARD?flow_token=550e8400-...
  • iOS .completeScenario route already parses payload.flowToken (FlowsPlugin.swift)
  • FlowsPlugin wraps it in StartPlasmaFlowAnalyticsParams
  • SignedInFlowsPlugin extracts it: routingParams.analyticsParams.startPlasmaFlowAnalyticsParams?.flowToken
  • FlowToken("550e8400-...") succeeds (any non-empty string is valid)
  • The token is passed to chainedFlowServerFactory.makeChainedFlow(flowToken:)
  • Goose context propagation already works via RoutingParams+GooseCompleteScenarioRequest
  • ActionCard rendering and tap handling already work
  • Completion card replacement already works via IncomingMessagesProcessor

What happens on tap: The client calls POST /2.0/cash/complete-scenario with the server-provided flow_token. Plasma's PlasmaFlowCreator.createFlow() finds the existing flow by (customerToken, flowToken) and returns its current snapshot (with any remaining non-agent-friendly blockers). The client renders those blockers natively.

One thing to verify: Confirm that ChainedFlowServerFactory.makeChainedFlow(flowToken:) sets the Cash-Flow-Token header on the underlying HTTP request. The CompleteScenarioRequest does not set this header itself — it's handled by the chained flow infrastructure.

Phase 2: Agent-Friendly Blockers (Requires iOS Changes)

This requires iOS changes:

3.1. Expose agent_friendly on BlockerDescriptor

File to modify: Code/CoreLibraries/Flows/FlowCore/Sources/BlockerDescriptor.swift

Add a public property reading from the proto:

public struct BlockerDescriptor: Equatable, Identifiable, Sendable {
    // ... existing properties ...

    /// Whether this blocker can be handled conversationally by an AI agent
    public let isAgentFriendly: Bool

    public init(_ proto: SQPBFranklinCommonScenariosBlockerDescriptor) throws {
        // ... existing init code ...

        self.isAgentFriendly = proto.agentFriendly  // or proto.agent_friendly depending on generated accessor
    }
}

3.2. Add Agent-Friendly Blocker Detection in Flow Processing

The blocker processing pipeline needs to check isAgentFriendly before rendering. The exact location depends on where blockers are dispatched — likely in the BlockerDescriptorIterator or the flow step state machine layer.

File: Code/CoreLibraries/Flows/FlowCore/Sources/BlockerDescriptorIterator.swift

Before yielding a blocker for native rendering, check:

if blockerDescriptor.isAgentFriendly, let moneybotContext = flowContext.moneybotContext {
    // Route back to chat instead of rendering natively
    routeAgentFriendlyBlockerToChat(
        blockerDescriptor: blockerDescriptor,
        flowToken: flowToken,
        moneybotContext: moneybotContext,
    )
    return
}

3.3. Route Agent-Friendly Blocker Back to Chat

Navigate back to the moneybot chat screen and send a hidden message:

private func routeAgentFriendlyBlockerToChat(
    blockerDescriptor: BlockerDescriptor,
    flowToken: FlowToken,
    moneybotContext: MoneybotContext,
) {
    let hiddenMessage = buildAgentFriendlyBlockerMessage(
        flowToken: flowToken,
        blockerDescriptor: blockerDescriptor,
    )

    // Dismiss the current flow presentation
    presentationManager.dismissModal {
        // Send hidden message to the kgoose session
        Task {
            await messageSender.sendHiddenMessage(hiddenMessage)
        }
    }
}

3.4. Hidden Message Format

func buildAgentFriendlyBlockerMessage(
    flowToken: FlowToken,
    blockerDescriptor: BlockerDescriptor,
) -> String {
    let json: [String: Any] = [
        "type": "agent_friendly_blocker",
        "flow_token": flowToken.value,
        "blocker_descriptor_id": blockerDescriptor.id.rawValue,
        "blocker_type": determineBlockerType(blockerDescriptor),
        "display_message": extractDisplayMessage(blockerDescriptor),
        "form_elements": extractFormElements(blockerDescriptor),
        "primary_action": extractPrimaryAction(blockerDescriptor),
    ]
    return String(data: try! JSONSerialization.data(withJSONObject: json), encoding: .utf8)!
}

4. cash-android: Required Changes

The Android codebase is not available locally. The following is derived from the research document at thoughts/shared/research/2026-02-10-clientrenderable-actioncard-plasma-flow-architecture.md.

Phase 1: ActionCards with Server-Provided Flow Token

Needs verification: Unlike iOS (where FlowsPlugin already parses flowToken), Android may require changes to parse the flow_token query parameter from the URL:

  1. Parse flow_token from URL in ClientRoute.CompleteScenario: Add optional flowToken query parameter parsing
  2. Forward to BlockersHelper.completeClientScenario(): Pass the flow token instead of generating a new one
  3. Set Cash-Flow-Token header: Ensure the parsed token is set on the Plasma API call

If Android doesn't support flow_token parsing yet, the client will generate a new FlowToken and call completeScenario with a different token than the server used. Plasma will create a second, separate flow — the server-side flow becomes orphaned. This is functional (the user still gets the native blocker UI) but loses completion card replacement since the kgoose context is on the original flow.

Phase 2: Agent-Friendly Blockers

Same pattern as iOS:

  1. Expose agent_friendly on BlockerDescriptor in the blocker rendering pipeline
  2. Route back to moneybot chat with hidden message if agent_friendly == true and MoneybotContext is available

5. Full Header Chain

Headers Required on Plasma completeScenario API Calls

Header Value iOS Source
Cash-Client-Scenario Scenario name CompleteScenarioRequest.initRequestBuilder.HeaderKeys.clientScenario
Cash-Flow-Token UUID from server or client-generated ChainedFlowServerFactory.makeChainedFlow(flowToken:)RequestBuilder.HeaderKeys.flowToken
Cash-Goose-Session-Id Kgoose session ID CompleteScenarioRequest.initGooseHeaders.sessionID
Cash-Goose-Tool-Request-Id Tool request ID CompleteScenarioRequest.initGooseHeaders.toolRequestID

MoneybotContext Flow (iOS)

ActionCardContentFactory.handleClientRouteNavigation()
  → navigator.navigate(source: .tap(.moneybot(sessionID, toolRequestID)))
    → FlowsPlugin.completeScenario handler
      → wraps flowToken in StartPlasmaFlowAnalyticsParams
      → navigator.navigate(to: .clientScenario, metadata: updatedRoutingParams)
        → SignedInFlowsPlugin.clientScenario handler
          → extracts flowToken from analyticsParams
          → extractMoneybotIDs(from: source) → (gooseSessionID, gooseToolRequestID)
          → CompleteScenarioRequest(gooseSessionID:, gooseToolRequestID:)
          → chainedFlowServerFactory.makeChainedFlow(flowToken:, gooseSessionID:)
            → POST /2.0/cash/complete-scenario with all headers
              → Plasma stores kgoose context in flow snapshot
                → KgoosePublisher emits CompletePlasmaFlow
                  → IncomingMessagesProcessor.processSessionActivities()
                    → replaces ActionCard by toolRequestID

6. Hidden Message Format (Phase 2)

JSON Schema

{
  "type": "agent_friendly_blocker",
  "flow_token": "<string>",
  "blocker_descriptor_id": "<string>",
  "blocker_type": "<string>",
  "display_message": "<string>",
  "form_elements": [
    {
      "id": "<string>",
      "type": "<string>",
      "label": "<string>",
      "keyboard_type": "<string optional>"
    }
  ],
  "primary_action": {
    "id": "<string>",
    "text": "<string>"
  }
}

Example: Informational Acknowledgment

{
  "type": "agent_friendly_blocker",
  "flow_token": "550e8400-e29b-41d4-a716-446655440000",
  "blocker_descriptor_id": "enable_card_terms_acknowledgment",
  "blocker_type": "FORM_INFORMATIONAL",
  "display_message": "By enabling your card, you agree to the Cash Card terms of service.",
  "form_elements": [],
  "primary_action": { "id": "Acknowledge", "text": "I Agree" }
}

7. Edge Cases

  1. Flow token format: Server sends UUID, iOS FlowToken accepts any non-empty string. No issue.
  2. No flow token in URL: Client generates one (FlowToken() = 25-char alphanumeric). Plasma creates a new, separate flow. The server-side flow is orphaned but cleaned up by TTL. Completion card replacement won't work since kgoose context is on the server-side flow. This scenario only applies to Android if it doesn't support flow_token parsing yet — iOS already handles it.
  3. Multiple taps: Plasma is idempotent on same (customerToken, flowToken). iOS should debounce — ActionCardContentFactory already uses TapDebouncer.
  4. Flow completed before tap: KgoosePublisher fires CompletePlasmaFlowIncomingMessagesProcessor replaces ActionCard with completion card before user taps.
  5. No MoneybotContext for agent-friendly blocker: If gooseSessionID is nil, skip agent-friendly detection, render blocker natively.
  6. Old client versions: Don't parse flow_token from URL → generate new FlowToken → create new flow. Server-side flow orphaned but cleaned up by TTL. Functional but no completion card replacement.
  7. Stale ActionCard after new chat message: The card URL is static. If the user taps after a long time, the server-side flow may be expired. The completeScenario API will return an error which the client should handle gracefully.
  8. Redundant processing on tap: When the client calls completeScenario with the server-provided token, Plasma returns the existing flow idempotently. executeAppApi() runs fastpath collection and quota consumption unnecessarily. This is accepted — see design review Section 2.1.

8. Testing Strategy

Unit Tests (iOS)

Phase 1: No new iOS tests needed (existing FlowsPluginTests.test_completeScenario already covers the flow token path). Verify:

  • ChainedFlowServerFactory sets Cash-Flow-Token header when flowToken is provided
  • CompleteScenarioRequest headers include goose session/tool request IDs
  • ActionCardContentFactory sets moneybot source on navigation

Phase 2:

  • BlockerDescriptor exposes isAgentFriendly from proto
  • Agent-friendly blocker routes to chat when MoneybotContext is available
  • Hidden message JSON format is correct
  • Non-agent-friendly blockers still render natively

Integration Tests

  • End-to-end: ActionCard tap → completeScenario → blocker → completion card replacement
  • Flow token roundtrip: server UUID → iOS FlowToken → Cash-Flow-Token header → Plasma flow creation

9. Key iOS Code References

File Path Relevance
FlowsPlugin.swift Code/CoreLibraries/Flows/FlowsPlugin/Sources/ .completeScenario route registration, flowToken → analyticsParams
SignedInFlowsPlugin.swift Code/OSHooks/ClientRouting/ClientRoutingImplementations/Sources/ .clientScenario handler, flowToken extraction, goose ID extraction
CompleteScenarioRequest.swift Code/CoreLibraries/Flows/FlowFactories/Sources/ HTTP request with goose headers
GooseHeaders.swift Code/CoreLibraries/Flows/FlowNetworking/Sources/ Cash-Goose-Session-Id, Cash-Goose-Tool-Request-Id
RoutingParams+Goose.swift Code/OSHooks/ClientRouting/ClientRouting/Sources/ Extract gooseSessionID/gooseToolRequestID from source
RequestBuilder.swift Code/SystemResources/Networking/Networking/Sources/ Cash-Client-Scenario, Cash-Flow-Token header constants
ActionCardProto.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Networking/Proto/ Proto → model parsing
ActionCardContentFactory.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Models/ClientRender/ Tap handling, navigation with moneybot context
ActionCardModel.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Views/ChatContent/ Model: title, description, tapBehavior
ActionCard.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Views/ChatContent/ SwiftUI view
CardTapActionProto.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Networking/Proto/ ClientRouteAction + hiddenMessage
ClientRouteActionProto.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Networking/Proto/ client_route_url parsing
TapBehaviorProto.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Networking/Proto/ .cardTapAction / .cardButtons
FlowLauncherToolHandler.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Models/ Existing tool handler pattern for client-side flow launching
IncomingMessagesProcessor.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Presentation/ Activity processing, completion card replacement
MessageSender.swift Code/Features/Moneybot/MoneybotImplementations/Sources/Models/ sendHiddenMessage()
BlockerDescriptor.swift Code/CoreLibraries/Flows/FlowCore/Sources/ Proto wrapper (NO agent_friendly today)
FlowToken.swift Code/CoreLibraries/Flows/FlowCore/Sources/ 25-char alphanumeric tokens, accepts any non-empty string
FlowsPluginTests.swift Code/CoreLibraries/Flows/FlowsPlugin/UnitTests/ Tests for .completeScenario route with flowToken

Appendix A: Scenario Name Mapping

Server FlowType Plasma Scenario Name URL Path Segment
ENABLE_CARD ENABLE_ISSUED_CARD ENABLE_ISSUED_CARD
DISABLE_CARD DISABLE_ISSUED_CARD_IN_POSTCARD DISABLE_ISSUED_CARD_IN_POSTCARD
CREATE_SAVINGS_GOAL SET_SAVINGS_GOAL_V2 SET_SAVINGS_GOAL_V2

Appendix B: Open Questions for iOS Team

  1. Cash-Flow-Token header: Confirm that ChainedFlowServerFactory.makeChainedFlow(flowToken:) sets the Cash-Flow-Token header on the underlying HTTP request to Plasma. The CompleteScenarioRequest does not set this header itself.
  2. agent_friendly proto accessor: What is the generated Swift accessor name for the agent_friendly field on SQPBFranklinCommonScenariosBlockerDescriptor? It may be agentFriendly, agent_friendly, or require a proto regeneration.
  3. Blocker interception point: What is the best place to intercept agent-friendly blockers in the flow step pipeline? Candidates: BlockerDescriptorIterator, blocker state machine evaluation, or DefaultCompleteScenarioPlanPresenter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment