(
ctx context.Context,
store DeleteTaskMutationStore,
record Task,
dependencies []Dependency,
runs []Run,
visited map[string]struct{},
)
| 2910 | } |
| 2911 | |
| 2912 | func (m *Service) canonicalTaskStatusReadOnlyWithStore( |
| 2913 | ctx context.Context, |
| 2914 | store DeleteTaskMutationStore, |
| 2915 | record Task, |
| 2916 | dependencies []Dependency, |
| 2917 | runs []Run, |
| 2918 | visited map[string]struct{}, |
| 2919 | ) (Status, error) { |
| 2920 | taskID := strings.TrimSpace(record.ID) |
| 2921 | if taskID != "" { |
| 2922 | if _, seen := visited[taskID]; seen { |
| 2923 | // Defensive termination guard: dependency cycles are invalid, but read |
| 2924 | // paths should still terminate without mutating persisted state. |
| 2925 | return taskStatusFromPolicySnapshot( |
| 2926 | record.Status, |
| 2927 | true, |
| 2928 | taskApprovalBlocked(record), |
| 2929 | taskPausedBlocked(record), |
| 2930 | false, |
| 2931 | taskNeedsAttentionBlocked(record), |
| 2932 | taskAttemptsExhausted(record, runs), |
| 2933 | runs, |
| 2934 | ), nil |
| 2935 | } |
| 2936 | visited[taskID] = struct{}{} |
| 2937 | defer delete(visited, taskID) |
| 2938 | } |
| 2939 | |
| 2940 | unresolvedDependencies, err := m.hasUnresolvedDependenciesReadOnlyWithStore(ctx, store, dependencies, visited) |
| 2941 | if err != nil { |
| 2942 | return "", err |
| 2943 | } |
| 2944 | openBlocks, err := store.HasOpenTaskBlocks(ctx, record.ID) |
| 2945 | if err != nil { |
| 2946 | return "", err |
| 2947 | } |
| 2948 | return taskStatusFromPolicySnapshot( |
| 2949 | record.Status, |
| 2950 | unresolvedDependencies, |
| 2951 | taskApprovalBlocked(record), |
| 2952 | taskPausedBlocked(record), |
| 2953 | openBlocks, |
| 2954 | taskNeedsAttentionBlocked(record), |
| 2955 | taskAttemptsExhausted(record, runs), |
| 2956 | runs, |
| 2957 | ), nil |
| 2958 | } |
| 2959 | |
| 2960 | func hasOpenRun(runs []Run) bool { |
| 2961 | for _, run := range runs { |
no test coverage detected