@@ -3135,6 +3135,312 @@ public CompletableFuture<UsageSummary> getUsageSummaryAsync(String period) {
31353135 return CompletableFuture .supplyAsync (() -> getUsageSummary (period ), asyncExecutor );
31363136 }
31373137
3138+ // ========================================================================
3139+ // Workflow Control Plane
3140+ // ========================================================================
3141+ // The Workflow Control Plane provides governance gates for external
3142+ // orchestrators like LangChain, LangGraph, and CrewAI.
3143+ //
3144+ // "LangChain runs the workflow. AxonFlow decides when it's allowed to move forward."
3145+
3146+ /**
3147+ * Creates a new workflow for governance tracking.
3148+ *
3149+ * <p>Registers a new workflow with AxonFlow. Call this at the start of your
3150+ * external orchestrator workflow (LangChain, LangGraph, CrewAI, etc.).
3151+ *
3152+ * @param request workflow creation request
3153+ * @return created workflow with ID
3154+ * @throws AxonFlowException if creation fails
3155+ *
3156+ * @example
3157+ * <pre>{@code
3158+ * CreateWorkflowResponse workflow = axonflow.createWorkflow(
3159+ * CreateWorkflowRequest.builder()
3160+ * .workflowName("code-review-pipeline")
3161+ * .source(WorkflowSource.LANGGRAPH)
3162+ * .totalSteps(5)
3163+ * .build()
3164+ * );
3165+ * System.out.println("Workflow created: " + workflow.getWorkflowId());
3166+ * }</pre>
3167+ */
3168+ public com .getaxonflow .sdk .types .workflow .WorkflowTypes .CreateWorkflowResponse createWorkflow (
3169+ com .getaxonflow .sdk .types .workflow .WorkflowTypes .CreateWorkflowRequest request ) {
3170+ Objects .requireNonNull (request , "request cannot be null" );
3171+
3172+ return retryExecutor .execute (() -> {
3173+ Request httpRequest = buildOrchestratorRequest ("POST" , "/api/v1/workflows" , request );
3174+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
3175+ return parseResponse (response ,
3176+ new TypeReference <com .getaxonflow .sdk .types .workflow .WorkflowTypes .CreateWorkflowResponse >() {});
3177+ }
3178+ }, "createWorkflow" );
3179+ }
3180+
3181+ /**
3182+ * Gets the status of a workflow.
3183+ *
3184+ * @param workflowId workflow ID
3185+ * @return workflow status including steps
3186+ * @throws AxonFlowException if workflow not found
3187+ */
3188+ public com .getaxonflow .sdk .types .workflow .WorkflowTypes .WorkflowStatusResponse getWorkflow (String workflowId ) {
3189+ Objects .requireNonNull (workflowId , "workflowId cannot be null" );
3190+
3191+ return retryExecutor .execute (() -> {
3192+ Request httpRequest = buildOrchestratorRequest ("GET" , "/api/v1/workflows/" + workflowId , null );
3193+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
3194+ return parseResponse (response ,
3195+ new TypeReference <com .getaxonflow .sdk .types .workflow .WorkflowTypes .WorkflowStatusResponse >() {});
3196+ }
3197+ }, "getWorkflow" );
3198+ }
3199+
3200+ /**
3201+ * Checks if a workflow step is allowed to proceed (step gate).
3202+ *
3203+ * <p>This is the core governance method. Call this before executing each step
3204+ * in your workflow to check if the step is allowed based on policies.
3205+ *
3206+ * @param workflowId workflow ID
3207+ * @param stepId unique step identifier (you provide this)
3208+ * @param request step gate request with step details
3209+ * @return gate decision: allow, block, or require_approval
3210+ * @throws AxonFlowException if check fails
3211+ *
3212+ * @example
3213+ * <pre>{@code
3214+ * StepGateResponse gate = axonflow.stepGate(
3215+ * workflow.getWorkflowId(),
3216+ * "step-1",
3217+ * StepGateRequest.builder()
3218+ * .stepName("Generate Code")
3219+ * .stepType(StepType.LLM_CALL)
3220+ * .model("gpt-4")
3221+ * .provider("openai")
3222+ * .build()
3223+ * );
3224+ *
3225+ * if (gate.isBlocked()) {
3226+ * throw new RuntimeException("Step blocked: " + gate.getReason());
3227+ * } else if (gate.requiresApproval()) {
3228+ * System.out.println("Approval needed: " + gate.getApprovalUrl());
3229+ * } else {
3230+ * // Execute the step
3231+ * executeStep();
3232+ * }
3233+ * }</pre>
3234+ */
3235+ public com .getaxonflow .sdk .types .workflow .WorkflowTypes .StepGateResponse stepGate (
3236+ String workflowId ,
3237+ String stepId ,
3238+ com .getaxonflow .sdk .types .workflow .WorkflowTypes .StepGateRequest request ) {
3239+ Objects .requireNonNull (workflowId , "workflowId cannot be null" );
3240+ Objects .requireNonNull (stepId , "stepId cannot be null" );
3241+ Objects .requireNonNull (request , "request cannot be null" );
3242+
3243+ return retryExecutor .execute (() -> {
3244+ Request httpRequest = buildOrchestratorRequest ("POST" ,
3245+ "/api/v1/workflows/" + workflowId + "/steps/" + stepId + "/gate" , request );
3246+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
3247+ return parseResponse (response ,
3248+ new TypeReference <com .getaxonflow .sdk .types .workflow .WorkflowTypes .StepGateResponse >() {});
3249+ }
3250+ }, "stepGate" );
3251+ }
3252+
3253+ /**
3254+ * Marks a step as completed.
3255+ *
3256+ * <p>Call this after successfully executing a step to record its completion.
3257+ *
3258+ * @param workflowId workflow ID
3259+ * @param stepId step ID
3260+ * @param request optional completion request with output data
3261+ */
3262+ public void markStepCompleted (
3263+ String workflowId ,
3264+ String stepId ,
3265+ com .getaxonflow .sdk .types .workflow .WorkflowTypes .MarkStepCompletedRequest request ) {
3266+ Objects .requireNonNull (workflowId , "workflowId cannot be null" );
3267+ Objects .requireNonNull (stepId , "stepId cannot be null" );
3268+
3269+ retryExecutor .execute (() -> {
3270+ Request httpRequest = buildOrchestratorRequest ("POST" ,
3271+ "/api/v1/workflows/" + workflowId + "/steps/" + stepId + "/complete" ,
3272+ request != null ? request : Collections .emptyMap ());
3273+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
3274+ if (!response .isSuccessful ()) {
3275+ handleErrorResponse (response );
3276+ }
3277+ return null ;
3278+ }
3279+ }, "markStepCompleted" );
3280+ }
3281+
3282+ /**
3283+ * Marks a step as completed with no output data.
3284+ *
3285+ * @param workflowId workflow ID
3286+ * @param stepId step ID
3287+ */
3288+ public void markStepCompleted (String workflowId , String stepId ) {
3289+ markStepCompleted (workflowId , stepId , null );
3290+ }
3291+
3292+ /**
3293+ * Completes a workflow successfully.
3294+ *
3295+ * <p>Call this when your workflow has completed all steps successfully.
3296+ *
3297+ * @param workflowId workflow ID
3298+ */
3299+ public void completeWorkflow (String workflowId ) {
3300+ Objects .requireNonNull (workflowId , "workflowId cannot be null" );
3301+
3302+ retryExecutor .execute (() -> {
3303+ Request httpRequest = buildOrchestratorRequest ("POST" ,
3304+ "/api/v1/workflows/" + workflowId + "/complete" , Collections .emptyMap ());
3305+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
3306+ if (!response .isSuccessful ()) {
3307+ handleErrorResponse (response );
3308+ }
3309+ return null ;
3310+ }
3311+ }, "completeWorkflow" );
3312+ }
3313+
3314+ /**
3315+ * Aborts a workflow.
3316+ *
3317+ * <p>Call this when you need to stop a workflow due to an error or user request.
3318+ *
3319+ * @param workflowId workflow ID
3320+ * @param reason optional reason for aborting
3321+ */
3322+ public void abortWorkflow (String workflowId , String reason ) {
3323+ Objects .requireNonNull (workflowId , "workflowId cannot be null" );
3324+
3325+ retryExecutor .execute (() -> {
3326+ Map <String , String > body = reason != null ?
3327+ Collections .singletonMap ("reason" , reason ) : Collections .emptyMap ();
3328+ Request httpRequest = buildOrchestratorRequest ("POST" ,
3329+ "/api/v1/workflows/" + workflowId + "/abort" , body );
3330+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
3331+ if (!response .isSuccessful ()) {
3332+ handleErrorResponse (response );
3333+ }
3334+ return null ;
3335+ }
3336+ }, "abortWorkflow" );
3337+ }
3338+
3339+ /**
3340+ * Aborts a workflow with no reason.
3341+ *
3342+ * @param workflowId workflow ID
3343+ */
3344+ public void abortWorkflow (String workflowId ) {
3345+ abortWorkflow (workflowId , null );
3346+ }
3347+
3348+ /**
3349+ * Resumes a workflow after approval.
3350+ *
3351+ * <p>Call this after a step has been approved to continue the workflow.
3352+ *
3353+ * @param workflowId workflow ID
3354+ */
3355+ public void resumeWorkflow (String workflowId ) {
3356+ Objects .requireNonNull (workflowId , "workflowId cannot be null" );
3357+
3358+ retryExecutor .execute (() -> {
3359+ Request httpRequest = buildOrchestratorRequest ("POST" ,
3360+ "/api/v1/workflows/" + workflowId + "/resume" , Collections .emptyMap ());
3361+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
3362+ if (!response .isSuccessful ()) {
3363+ handleErrorResponse (response );
3364+ }
3365+ return null ;
3366+ }
3367+ }, "resumeWorkflow" );
3368+ }
3369+
3370+ /**
3371+ * Lists workflows with optional filters.
3372+ *
3373+ * @param options filter and pagination options
3374+ * @return list of workflows
3375+ */
3376+ public com .getaxonflow .sdk .types .workflow .WorkflowTypes .ListWorkflowsResponse listWorkflows (
3377+ com .getaxonflow .sdk .types .workflow .WorkflowTypes .ListWorkflowsOptions options ) {
3378+ return retryExecutor .execute (() -> {
3379+ StringBuilder path = new StringBuilder ("/api/v1/workflows" );
3380+ StringBuilder query = new StringBuilder ();
3381+
3382+ if (options != null ) {
3383+ if (options .getStatus () != null ) {
3384+ appendQueryParam (query , "status" , options .getStatus ().getValue ());
3385+ }
3386+ if (options .getSource () != null ) {
3387+ appendQueryParam (query , "source" , options .getSource ().getValue ());
3388+ }
3389+ if (options .getLimit () > 0 ) {
3390+ appendQueryParam (query , "limit" , String .valueOf (options .getLimit ()));
3391+ }
3392+ if (options .getOffset () > 0 ) {
3393+ appendQueryParam (query , "offset" , String .valueOf (options .getOffset ()));
3394+ }
3395+ }
3396+
3397+ if (query .length () > 0 ) {
3398+ path .append ("?" ).append (query );
3399+ }
3400+
3401+ Request httpRequest = buildOrchestratorRequest ("GET" , path .toString (), null );
3402+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
3403+ return parseResponse (response ,
3404+ new TypeReference <com .getaxonflow .sdk .types .workflow .WorkflowTypes .ListWorkflowsResponse >() {});
3405+ }
3406+ }, "listWorkflows" );
3407+ }
3408+
3409+ /**
3410+ * Lists all workflows with default options.
3411+ *
3412+ * @return list of workflows
3413+ */
3414+ public com .getaxonflow .sdk .types .workflow .WorkflowTypes .ListWorkflowsResponse listWorkflows () {
3415+ return listWorkflows (null );
3416+ }
3417+
3418+ /**
3419+ * Asynchronously creates a workflow.
3420+ *
3421+ * @param request workflow creation request
3422+ * @return a future containing the created workflow
3423+ */
3424+ public CompletableFuture <com .getaxonflow .sdk .types .workflow .WorkflowTypes .CreateWorkflowResponse > createWorkflowAsync (
3425+ com .getaxonflow .sdk .types .workflow .WorkflowTypes .CreateWorkflowRequest request ) {
3426+ return CompletableFuture .supplyAsync (() -> createWorkflow (request ), asyncExecutor );
3427+ }
3428+
3429+ /**
3430+ * Asynchronously checks a step gate.
3431+ *
3432+ * @param workflowId workflow ID
3433+ * @param stepId step ID
3434+ * @param request step gate request
3435+ * @return a future containing the gate decision
3436+ */
3437+ public CompletableFuture <com .getaxonflow .sdk .types .workflow .WorkflowTypes .StepGateResponse > stepGateAsync (
3438+ String workflowId ,
3439+ String stepId ,
3440+ com .getaxonflow .sdk .types .workflow .WorkflowTypes .StepGateRequest request ) {
3441+ return CompletableFuture .supplyAsync (() -> stepGate (workflowId , stepId , request ), asyncExecutor );
3442+ }
3443+
31383444 @ Override
31393445 public void close () {
31403446 httpClient .dispatcher ().executorService ().shutdown ();
0 commit comments