Skip to content

feat: Implement dynamic FSM transition resolution from callback response#179

Merged
sthanikan2000 merged 4 commits intoOpenNSW:mainfrom
mushrafmim:feat/178-implement-end-state-based-on-configs
Feb 26, 2026
Merged

feat: Implement dynamic FSM transition resolution from callback response#179
sthanikan2000 merged 4 commits intoOpenNSW:mainfrom
mushrafmim:feat/178-implement-end-state-based-on-configs

Conversation

@mushrafmim
Copy link
Contributor

@mushrafmim mushrafmim commented Feb 24, 2026

Summary

Replaces the hardcoded decision == "APPROVED" check in the OGA callback flow with a configurable TransitionConfig that maps arbitrary response field values to FSM actions. This removes the TODO workarounds in both the backend plugin and the OGA handler, and wires the Phytosanitary Certificate workflow node to use the new config.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Refactoring (no functional changes)

Changes Made

  • backend/internal/task/plugin/transition.go — New TransitionConfig struct with a Resolve(data) method. Reads a dot-path field from the callback response, looks it up in a string→action mapping, and falls back to an optional Default action.
  • backend/internal/task/plugin/simple_form.goCallbackConfig gains a Transition *TransitionConfig field. resolveAction now delegates to TransitionConfig.Resolve when present, keeping the legacy hardcoded path as a fallback for unconfigured nodes.
  • backend/internal/task/plugin/transition_test.go — 11 table-driven unit tests covering: happy-path mapping, multi-entry mapping, missing field with/without default, unmapped value with/without default, non-string field values, dot-path nested fields, and empty data.
  • backend/internal/database/migrations/010_add_conditional_state_identification.sql — Updates the Phytosanitary Certificate workflow node config to use the new callback.transition block: maps APPROVED and MANUAL_REVIEWOGA_VERIFICATION_APPROVED, with OGA_VERIFICATION_REJECTED as the default.
  • oga/internal/handler.go — Removes the hardcoded decision field validation from HandleReviewApplication. Validation is now the responsibility of the backend FSM via TransitionConfig, so the OGA handler simply forwards the raw body.

Testing

  • I have tested this change locally
  • I have added unit tests for new functionality
  • I have tested edge cases
  • All existing tests pass

OGA App Setup

The OGA app must have a form definition file present for the Phytosanitary Certificate review flow to work.

Place a file at the following path inside the OGA service:

oga/data/forms/consignment:moa:npqs:phytosanitary:001.json

Content of the file:

{
  "schema": {
    "type": "object",
    "required": [
      "decision",
      "phytosanitaryClearance"
    ],
    "properties": {
      "decision": {
        "type": "string",
        "title": "Decision",
        "oneOf": [
          {
            "const": "APPROVED",
            "title": "Approved"
          },
          {
            "const": "MANUAL_REVIEW",
            "title": "Manual Inspection Required"
          },
          {
            "const": "REJECTED",
            "title": "Rejected"
          }
        ]
      },
      "phytosanitaryClearance": {
        "type": "string",
        "title": "Phytosanitary Clearance Status",
        "oneOf": [
          {
            "const": "CLEARED",
            "title": "Cleared for Export"
          },
          {
            "const": "CONDITIONAL",
            "title": "Cleared with Conditions"
          },
          {
            "const": "REJECTED",
            "title": "Rejected - Non Compliance"
          }
        ]
      },
      "inspectionReference": {
        "type": "string",
        "title": "Inspection / Certificate Reference No"
      },
      "remarks": {
        "type": "string",
        "title": "NPQS Remarks"
      }
    }
  },
  "uiSchema": {
    "type": "VerticalLayout",
    "elements": [
      {
        "type": "Control",
        "scope": "#/properties/decision"
      },
      {
        "type": "Control",
        "scope": "#/properties/phytosanitaryClearance"
      },
      {
        "type": "Control",
        "scope": "#/properties/inspectionReference"
      },
      {
        "type": "Control",
        "scope": "#/properties/remarks",
        "options": {
          "multi": true
        }
      }
    ]
  }
}

This file provides the review form rendered by the OGA app when processing a phytosanitary certificate verification request. Without it, the OGA handler will not be able to serve the form for review.

Note: Only the Phytosanitary Certificate flow (c0000003-0003-0003-0003-000000000003) has been updated to use the new dynamic transition config. Other workflow nodes are unaffected and continue to use the legacy hardcoded behaviour.

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • My changes generate no new warnings
  • I have checked that there are no merge conflicts

Related Issues

Closes #178

Additional Notes

The TransitionConfig.Default field acts as a catch-all — if the callback response contains a field value not present in the mapping (e.g. a new decision type added by the OGA service), the FSM will still transition predictably rather than returning an error.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @mushrafmim, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the Finite State Machine (FSM) transition logic to be dynamically configurable rather than hardcoded. It introduces a new TransitionConfig mechanism that allows mapping specific values from callback responses to FSM actions, including a default fallback. This change enhances flexibility, removes previous technical debt (TODOs), and improves the maintainability of workflow definitions, starting with the Phytosanitary Certificate workflow.

Highlights

  • Dynamic FSM Transition Configuration: Introduced a new TransitionConfig struct and its Resolve method to dynamically determine FSM actions based on callback responses, replacing previous hardcoded logic.
  • Integration into Callback Flow: Integrated the TransitionConfig into CallbackConfig within simple_form.go, allowing configurable transition logic while maintaining a fallback for unconfigured nodes.
  • Comprehensive Unit Testing: Added comprehensive unit tests for the TransitionConfig to ensure robust and predictable behavior across various scenarios, including mapping, missing fields, and type handling.
  • Phytosanitary Certificate Workflow Update: Updated the Phytosanitary Certificate workflow node configuration in the database to utilize the new dynamic callback.transition block for FSM state changes.
  • Decoupled Validation Logic: Removed the hardcoded decision field validation from the OGA handler, delegating transition logic responsibility to the backend FSM via TransitionConfig.
Changelog
  • backend/internal/database/migrations/010_add_conditional_state_identification.sql
    • Updated the workflow_node_templates table to configure the Phytosanitary Certificate workflow node with the new dynamic callback.transition block, mapping APPROVED and MANUAL_REVIEW decisions to OGA_VERIFICATION_APPROVED and OGA_VERIFICATION_REJECTED as the default.
  • backend/internal/task/plugin/simple_form.go
    • Modified CallbackConfig to include a Transition field of type *TransitionConfig.
    • Updated the resolveAction method to delegate FSM action resolution to this new configuration when present, falling back to the legacy hardcoded logic otherwise.
  • backend/internal/task/plugin/transition.go
    • Added a new file defining the TransitionConfig struct and its Resolve method, which extracts a field from provided data, maps its value to an FSM action, and supports a default action.
  • backend/internal/task/plugin/transition_test.go
    • Added a new file containing 11 table-driven unit tests for the TransitionConfig.Resolve method, covering various scenarios like successful mapping, missing fields, unmapped values, non-string field types, nested fields, and empty data.
  • oga/internal/handler.go
    • Removed the hardcoded validation for the decision field in HandleReviewApplication, as this logic is now handled by the backend's TransitionConfig.
Activity
  • No human activity has occurred on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully implements dynamic FSM transition resolution from callback responses, replacing hardcoded logic with a configurable TransitionConfig. This is a significant improvement for flexibility and maintainability, especially for the Phytosanitary Certificate workflow node. The new TransitionConfig and its comprehensive unit tests demonstrate a robust solution. The removal of hardcoded validation in the OGA handler also aligns well with the new dynamic approach. While there are a couple of minor areas related to hardcoded strings, these have been identified as deferrable refactoring tasks, with a recommendation to create tracking issues for future resolution.

@mushrafmim mushrafmim force-pushed the feat/178-implement-end-state-based-on-configs branch from b6ad482 to 70027f0 Compare February 24, 2026 09:57
ginaxu1
ginaxu1 previously approved these changes Feb 24, 2026
@mushrafmim mushrafmim force-pushed the feat/178-implement-end-state-based-on-configs branch 3 times, most recently from e6fdad7 to 48dee6b Compare February 25, 2026 01:31
ginaxu1
ginaxu1 previously approved these changes Feb 25, 2026
@mushrafmim mushrafmim force-pushed the feat/178-implement-end-state-based-on-configs branch 2 times, most recently from 6a29f99 to dd850cb Compare February 25, 2026 07:03
ginaxu1
ginaxu1 previously approved these changes Feb 25, 2026
@mushrafmim mushrafmim force-pushed the feat/178-implement-end-state-based-on-configs branch 2 times, most recently from 23a8162 to eae0851 Compare February 25, 2026 13:04
Copy link
Contributor

@sthanikan2000 sthanikan2000 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to do a rebase to the main branch after merging the #183

Because there are some bugs in the unit tests of workflow manager which is fixed in that PR, and will remove the CI failure

@mushrafmim mushrafmim force-pushed the feat/178-implement-end-state-based-on-configs branch from eae0851 to 5719830 Compare February 25, 2026 13:52
@mushrafmim mushrafmim force-pushed the feat/178-implement-end-state-based-on-configs branch from 5719830 to 86a5f57 Compare February 26, 2026 03:38
@sthanikan2000 sthanikan2000 merged commit 67131f4 into OpenNSW:main Feb 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Configurable FSM transition resolution via TransitionConfig

3 participants