Skip to content

refactor: consolidate job creation into factory package#805

Merged
adityachoudhari26 merged 4 commits intomainfrom
job-agent-logic-cleanup
Feb 13, 2026
Merged

refactor: consolidate job creation into factory package#805
adityachoudhari26 merged 4 commits intomainfrom
job-agent-logic-cleanup

Conversation

@adityachoudhari26
Copy link
Copy Markdown
Member

@adityachoudhari26 adityachoudhari26 commented Feb 13, 2026

Summary by CodeRabbit

  • Refactor

    • Streamlined job creation and workflow dispatch paths; job creation now uses a factory and central helpers.
    • Dispatch flow simplified: dispatchers are invoked with original inputs without prior contextual augmentation or persistence.
  • Tests

    • Expanded coverage for job creation, dispatch contexts, config merging, and workflow job scenarios.
    • Removed legacy registry test suite and reorganized tests to use explicit environment/resource setup.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Refactors dispatch-context construction: logic for merging JobAgentConfig and populating DispatchContext is moved from the jobagents registry into the jobs Factory. Registry.Dispatch is simplified to delegate directly to dispatcher; Factory now builds jobs (release and workflow) with merged configs and complete DispatchContext before upsert and dispatch.

Changes

Cohort / File(s) Summary
Registry Simplification
apps/workspace-engine/pkg/workspace/jobagents/registry.go, apps/workspace-engine/pkg/workspace/jobagents/registry_test.go
Removed private context-building/merge helpers and removed DispatchContext assembly/persistence from Registry.Dispatch; Dispatch now calls dispatcher.Dispatch directly. The registry test file was deleted.
Factory Expansion
apps/workspace-engine/pkg/workspace/jobs/factory.go, apps/workspace-engine/pkg/workspace/jobs/factory_test.go
Added helpers to create InvalidJobAgent jobs, merge configs, and build DispatchContext; introduced CreateJobForWorkflowJob and refactored CreateJobForRelease to use new helpers. Tests expanded to cover DispatchContext population, config deep-merge, workflow job paths, and error cases.
Merge Utilities
apps/workspace-engine/pkg/workspace/jobs/helpers.go
New deepMerge, mergeJobAgentConfig, and deepCopy helpers implementing recursive map merging and JSON-based deep copy for config merging.
Workflow Manager
apps/workspace-engine/pkg/workspace/workflowmanager/manager.go
Added factory *jobs.Factory field and wiring. CreateWorkflowRun now uses factory.CreateJobForWorkflowJob, upserts the job, then dispatches via jobAgentRegistry; removed inline dispatch helper.
Test Setup Adjustments
apps/workspace-engine/pkg/workspace/releasemanager/deployment/executor_test.go
Tests now explicitly create/upsert Environment and Resource entities before executing release tests.

Sequence Diagram(s)

sequenceDiagram
  participant WF as WorkflowManager
  participant F as jobs.Factory
  participant S as Store
  participant R as jobagents.Registry
  participant D as Dispatcher

  WF->>F: CreateJobForWorkflowJob(workflowRunID, workflowJob)
  F->>S: read environment/resource/release/version/agent
  F->>F: mergeJobAgentConfig + buildDispatchContext
  F-->>WF: Job (with DispatchContext)
  WF->>S: UpsertJob(Job)
  WF->>R: Dispatch(Job)
  R->>D: Dispatch(job, jobAgentType, DispatchContext)
  D-->>R: result
  R-->>WF: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I stitched configs with careful paws,

Deep-merge maps without a pause,
The factory hums, the registry sends,
Jobs take flight where logic mends,
A hop, a patch, and joyful applause!

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: consolidating job creation logic into the factory package by removing job agent context building from the registry and moving that responsibility to the factory.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch job-agent-logic-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/workspace-engine/pkg/workspace/jobs/factory.go`:
- Around line 78-91: The local variable mergedConfig is pre-allocated with
make(oapi.JobAgentConfig) but immediately overwritten by mergeJobAgentConfig;
remove the wasted allocation by deleting the make call and either declare
mergedConfig via short assignment from mergeJobAgentConfig (e.g. mergedConfig,
err := mergeJobAgentConfig(...)) or declare var mergedConfig oapi.JobAgentConfig
before calling mergeJobAgentConfig; update the call site in
Factory.buildJobAgentConfig that uses jobAgent.Config, deployment.JobAgentConfig
and release.Version.JobAgentConfig to use the returned mergedConfig and handle
err as already done.
🧹 Nitpick comments (6)
apps/workspace-engine/pkg/workspace/jobs/helpers.go (2)

19-29: deepMerge assigns nested source maps by reference when dst[k] is not already a map.

When src contains a nested map[string]any and dst[k] doesn't exist (or isn't a map), line 27 assigns the source's map reference directly into dst. This means the merged config shares mutable state with the original config maps. If any source config is later mutated in-place (e.g., via a store update), the merged config silently changes too.

Given that configs are likely treated as immutable in practice, this may be acceptable, but it's worth being aware of.

🔧 Defensive copy of nested maps
 func deepMerge(dst, src map[string]any) {
 	for k, v := range src {
 		if sm, ok := v.(map[string]any); ok {
 			if dm, ok := dst[k].(map[string]any); ok {
 				deepMerge(dm, sm)
 				continue
 			}
+			// Clone the source map to avoid sharing references
+			cloned := make(map[string]any, len(sm))
+			deepMerge(cloned, sm)
+			dst[k] = cloned
+			continue
 		}
 		dst[k] = v
 	}
 }

11-17: mergeJobAgentConfig never returns an error.

The function signature returns error, but the current implementation always returns nil. This is fine for forward-compatibility if error handling may be added later, but callers currently check for an error that can never occur.

apps/workspace-engine/pkg/workspace/jobs/factory_test.go (1)

67-76: Minor inconsistency: createTestResource omits fields that inline construction includes.

The helper doesn't set Metadata, CreatedAt, or WorkspaceId, while inline oapi.Resource construction in setupFullStore (line 340) and other tests does. This could cause subtle differences in test behavior if the factory code ever accesses those fields from the dispatch context.

Not blocking, but consider aligning the helper with the inline patterns for consistency.

apps/workspace-engine/pkg/workspace/jobs/factory.go (3)

32-76: Significant duplication between noAgentConfiguredJob and jobAgentNotFoundJob.

These two methods differ only in the message string and trace step name. The job construction (UUID, status, timestamps, metadata) is identical. Consider extracting a shared helper to reduce duplication.

Also, the trace step name in jobAgentNotFoundJob (line 58) says "Create NoAgentFoundJob job" which doesn't match any status name — consider aligning it with "Create InvalidJobAgent job" for consistency.

Example consolidation
+func (f *Factory) invalidJobAgentJob(releaseID, jobAgentID, message, stepName string, action *trace.Action) *oapi.Job {
+	if action != nil {
+		action.AddStep(stepName, trace.StepResultPass,
+			fmt.Sprintf("Created InvalidJobAgent job for release %s with job agent %s", releaseID, jobAgentID)).
+			AddMetadata("release_id", releaseID).
+			AddMetadata("job_agent_id", jobAgentID).
+			AddMetadata("message", message)
+	}
+	return &oapi.Job{
+		Id:             uuid.New().String(),
+		ReleaseId:      releaseID,
+		JobAgentId:     jobAgentID,
+		JobAgentConfig: oapi.JobAgentConfig{},
+		Status:         oapi.JobStatusInvalidJobAgent,
+		Message:        &message,
+		CreatedAt:      time.Now(),
+		UpdatedAt:      time.Now(),
+		Metadata:       make(map[string]string),
+	}
+}
+
 func (f *Factory) noAgentConfiguredJob(releaseID, jobAgentID, deploymentName string, action *trace.Action) *oapi.Job {
-	...
+	msg := fmt.Sprintf("No job agent configured for deployment '%s'", deploymentName)
+	return f.invalidJobAgentJob(releaseID, jobAgentID, msg, "Create InvalidJobAgent job", action)
 }
 
 func (f *Factory) jobAgentNotFoundJob(releaseID, jobAgentID, deploymentName string, action *trace.Action) *oapi.Job {
-	...
+	msg := fmt.Sprintf("Job agent '%s' not found for deployment '%s'", jobAgentID, deploymentName)
+	return f.invalidJobAgentJob(releaseID, jobAgentID, msg, "Create InvalidJobAgent job", action)
 }

275-302: Missing doc comment on exported method CreateJobForWorkflowJob.

CreateJobForRelease (line 139) has a doc comment, but CreateJobForWorkflowJob does not. As per coding guidelines, exported functions/methods should be documented.

Proposed fix
+// CreateJobForWorkflowJob creates a job for a given workflow job.
+// The job is configured with merged settings from JobAgent + WorkflowJob config.
 func (f *Factory) CreateJobForWorkflowJob(ctx context.Context, wfJob *oapi.WorkflowJob) (*oapi.Job, error) {

As per coding guidelines: "Write comments that explain why, document complex logic/algorithms, provide non-obvious context, include TODO/FIXME, and document exported functions/types/methods."


186-190: Trace step message is misleading post-refactor.

"Applied default job agent configuration" no longer accurately describes what happens — configs are now merged from agent, deployment, and version sources. If this is pre-existing text, consider updating it to reflect the new merge behavior.

Comment on lines +78 to +91
func (f *Factory) buildJobAgentConfig(release *oapi.Release, deployment *oapi.Deployment, jobAgent *oapi.JobAgent) (oapi.JobAgentConfig, error) {
mergedConfig := make(oapi.JobAgentConfig)

agentConfig := jobAgent.Config
deploymentConfig := deployment.JobAgentConfig
versionConfig := release.Version.JobAgentConfig

mergedConfig, err := mergeJobAgentConfig(agentConfig, deploymentConfig, versionConfig)
if err != nil {
return nil, fmt.Errorf("failed to merge job agent configs: %w", err)
}

return mergedConfig, nil
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Wasted allocation on line 79.

mergedConfig is initialized with make(oapi.JobAgentConfig) but immediately reassigned by the return of mergeJobAgentConfig on line 85. The make call allocates a map that is never used.

Proposed fix
 func (f *Factory) buildJobAgentConfig(release *oapi.Release, deployment *oapi.Deployment, jobAgent *oapi.JobAgent) (oapi.JobAgentConfig, error) {
-	mergedConfig := make(oapi.JobAgentConfig)
-
 	agentConfig := jobAgent.Config
 	deploymentConfig := deployment.JobAgentConfig
 	versionConfig := release.Version.JobAgentConfig
 
-	mergedConfig, err := mergeJobAgentConfig(agentConfig, deploymentConfig, versionConfig)
+	mergedConfig, err := mergeJobAgentConfig(agentConfig, deploymentConfig, versionConfig)
 	if err != nil {
 		return nil, fmt.Errorf("failed to merge job agent configs: %w", err)
 	}
 
 	return mergedConfig, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (f *Factory) buildJobAgentConfig(release *oapi.Release, deployment *oapi.Deployment, jobAgent *oapi.JobAgent) (oapi.JobAgentConfig, error) {
mergedConfig := make(oapi.JobAgentConfig)
agentConfig := jobAgent.Config
deploymentConfig := deployment.JobAgentConfig
versionConfig := release.Version.JobAgentConfig
mergedConfig, err := mergeJobAgentConfig(agentConfig, deploymentConfig, versionConfig)
if err != nil {
return nil, fmt.Errorf("failed to merge job agent configs: %w", err)
}
return mergedConfig, nil
}
func (f *Factory) buildJobAgentConfig(release *oapi.Release, deployment *oapi.Deployment, jobAgent *oapi.JobAgent) (oapi.JobAgentConfig, error) {
agentConfig := jobAgent.Config
deploymentConfig := deployment.JobAgentConfig
versionConfig := release.Version.JobAgentConfig
mergedConfig, err := mergeJobAgentConfig(agentConfig, deploymentConfig, versionConfig)
if err != nil {
return nil, fmt.Errorf("failed to merge job agent configs: %w", err)
}
return mergedConfig, nil
}
🤖 Prompt for AI Agents
In `@apps/workspace-engine/pkg/workspace/jobs/factory.go` around lines 78 - 91,
The local variable mergedConfig is pre-allocated with make(oapi.JobAgentConfig)
but immediately overwritten by mergeJobAgentConfig; remove the wasted allocation
by deleting the make call and either declare mergedConfig via short assignment
from mergeJobAgentConfig (e.g. mergedConfig, err := mergeJobAgentConfig(...)) or
declare var mergedConfig oapi.JobAgentConfig before calling mergeJobAgentConfig;
update the call site in Factory.buildJobAgentConfig that uses jobAgent.Config,
deployment.JobAgentConfig and release.Version.JobAgentConfig to use the returned
mergedConfig and handle err as already done.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/workspace-engine/pkg/workspace/jobs/factory.go`:
- Around line 125-134: DispatchContext currently sets Version and Variables to
&releaseCopy.Version and &releaseCopy.Variables which aliases those fields to
Release and risks silent mutation; change this to create independent copies
(e.g., make local copies like versionCopy := releaseCopy.Version and
variablesCopy := releaseCopy.Variables) and assign &versionCopy and
&variablesCopy to DispatchContext.Version and DispatchContext.Variables
respectively (or, if aliasing was intentional, add a clear inline comment in the
factory function explaining the deliberate shared-state decision); reference
DispatchContext, Release, Version, Variables, and releaseCopy to locate the
change.
🧹 Nitpick comments (1)
apps/workspace-engine/pkg/workspace/jobs/factory.go (1)

32-76: Consider extracting a shared helper for invalid-agent jobs.

noAgentConfiguredJob and jobAgentNotFoundJob build identical oapi.Job structs — only the message and trace step text differ. A single private helper (e.g., invalidJobAgentJob(releaseID, jobAgentID, message string, action …)) called from both sites would remove the duplication.

Comment on lines +125 to +134
return &oapi.DispatchContext{
Release: releaseCopy,
Deployment: deploymentCopy,
Environment: envCopy,
Resource: resourceCopy,
JobAgent: *agentCopy,
JobAgentConfig: mergedConfig,
Version: &releaseCopy.Version,
Variables: &releaseCopy.Variables,
}, nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Version and Variables alias fields inside Release — intentional?

&releaseCopy.Version and &releaseCopy.Variables point into the same struct held by Release. Mutating dispatchContext.Version silently mutates dispatchContext.Release.Version (and likewise for Variables). Given the care taken to deep-copy everything else, this shared-state shortcut is worth a brief comment confirming it's intentional, or separate copies if it isn't.

🤖 Prompt for AI Agents
In `@apps/workspace-engine/pkg/workspace/jobs/factory.go` around lines 125 - 134,
DispatchContext currently sets Version and Variables to &releaseCopy.Version and
&releaseCopy.Variables which aliases those fields to Release and risks silent
mutation; change this to create independent copies (e.g., make local copies like
versionCopy := releaseCopy.Version and variablesCopy := releaseCopy.Variables)
and assign &versionCopy and &variablesCopy to DispatchContext.Version and
DispatchContext.Variables respectively (or, if aliasing was intentional, add a
clear inline comment in the factory function explaining the deliberate
shared-state decision); reference DispatchContext, Release, Version, Variables,
and releaseCopy to locate the change.

@adityachoudhari26 adityachoudhari26 merged commit eb122e1 into main Feb 13, 2026
9 checks passed
@adityachoudhari26 adityachoudhari26 deleted the job-agent-logic-cleanup branch February 13, 2026 20:35
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.

1 participant