fortrangoingonforty/fgof-devloop / 4b880d2

Browse files

Resolve devloop audit findings

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
4b880d28887b366bdf0656b25e67b4a607d0b91a
Parents
209b581
Tree
4ac1c6d

4 changed files

StatusFile+-
M README.md 2 1
M src/fgof_devloop.f90 5 1
M test/test_devloop_jobs.f90 7 0
M test/test_devloop_model.f90 15 1
README.mdmodified
@@ -95,13 +95,14 @@ Current semantics:
95
 - `devloop_watch_summary` condenses `fgof-watch` event batches into file, directory, create, modify, remove, move, ignored, and failure counters
95
 - `devloop_watch_summary` condenses `fgof-watch` event batches into file, directory, create, modify, remove, move, ignored, and failure counters
96
 - `devloop_watch_options()` projects dev-loop policy into `fgof-watch` options for debounce polls, hidden-path filtering, and directory event emission
96
 - `devloop_watch_options()` projects dev-loop policy into `fgof-watch` options for debounce polls, hidden-path filtering, and directory event emission
97
 - `devloop_watch_trigger()` turns successful watch summaries into change triggers while suppressing watcher failures, disabled restart-on-change policy, empty batches, directory-only batches when disabled, and batches below `min_restart_changes`
97
 - `devloop_watch_trigger()` turns successful watch summaries into change triggers while suppressing watcher failures, disabled restart-on-change policy, empty batches, directory-only batches when disabled, and batches below `min_restart_changes`
98
+- `devloop_change_trigger()` suppresses nonpositive change counts so empty batches cannot start cycles through the lower-level trigger API
98
 - `devloop_build_command()`, `devloop_run_command()`, and `devloop_smoke_command()` wrap `fgof-process` commands with loop roles and optional process options
99
 - `devloop_build_command()`, `devloop_run_command()`, and `devloop_smoke_command()` wrap `fgof-process` commands with loop roles and optional process options
99
 - `run_devloop_command()` executes one command spec and preserves the raw `process_result`, including stdout, stderr, exit code, timeout state, and process error details
100
 - `run_devloop_command()` executes one command spec and preserves the raw `process_result`, including stdout, stderr, exit code, timeout state, and process error details
100
 - `run_devloop_cycle()` starts a cycle, executes enabled build/run/smoke specs in order, skips later specs after the first failure, and feeds the outcome into `finish_devloop_cycle()`
101
 - `run_devloop_cycle()` starts a cycle, executes enabled build/run/smoke specs in order, skips later specs after the first failure, and feeds the outcome into `finish_devloop_cycle()`
101
 - `devloop_service_job()` builds a long-running service/job spec backed by `fgof-jobs`
102
 - `devloop_service_job()` builds a long-running service/job spec backed by `fgof-jobs`
102
 - `attach_devloop_job()` records an already-launched pid/process group and ownership expectations
103
 - `attach_devloop_job()` records an already-launched pid/process group and ownership expectations
103
 - `observe_devloop_job()` applies `fgof-jobs` wait results while preserving member-level terminal state
104
 - `observe_devloop_job()` applies `fgof-jobs` wait results while preserving member-level terminal state
104
-- `devloop_job_restart_plan()` models whether a watched change should stop, start, restart, release, or require terminal handoff for a long-running job; released jobs return no action
105
+- `devloop_job_restart_plan()` models whether a watched change should stop, start, restart, release, or require terminal handoff for a long-running job; released jobs and release-on-handoff plans return no stop/start/restart action
105
 - `begin_devloop_cycle()` increments the cycle counter and starts work only when the loop is active, idle, and policy permits the trigger
106
 - `begin_devloop_cycle()` increments the cycle counter and starts work only when the loop is active, idle, and policy permits the trigger
106
 - `finish_devloop_cycle()` records success or failure and returns an explicit decision to idle, restart, or stop
107
 - `finish_devloop_cycle()` records success or failure and returns an explicit decision to idle, restart, or stop
107
 - negative `max_failures` values normalize to unlimited failures
108
 - negative `max_failures` values normalize to unlimited failures
src/fgof_devloop.f90modified
@@ -354,8 +354,10 @@ contains
354
     type(devloop_trigger) :: trigger
354
     type(devloop_trigger) :: trigger
355
 
355
 
356
     trigger = clear_devloop_trigger()
356
     trigger = clear_devloop_trigger()
357
+    if (change_count <= 0) return
358
+
357
     trigger%kind = FGOF_DEVLOOP_TRIGGER_CHANGE
359
     trigger%kind = FGOF_DEVLOOP_TRIGGER_CHANGE
358
-    trigger%change_count = max(0, change_count)
360
+    trigger%change_count = change_count
359
     if (present(reason)) then
361
     if (present(reason)) then
360
       trigger%reason = reason
362
       trigger%reason = reason
361
     else
363
     else
@@ -693,6 +695,8 @@ contains
693
 
695
 
694
     if (job_state%terminal_handoff_required .and. job_state%spec%release_on_handoff) then
696
     if (job_state%terminal_handoff_required .and. job_state%spec%release_on_handoff) then
695
       plan%should_release = .true.
697
       plan%should_release = .true.
698
+      plan%reason = "release for terminal handoff"
699
+      return
696
     end if
700
     end if
697
 
701
 
698
     if (job_state%running .or. job_state%stopped .or. job_state%cleanup_needed) then
702
     if (job_state%running .or. job_state%stopped .or. job_state%cleanup_needed) then
test/test_devloop_jobs.f90modified
@@ -83,6 +83,13 @@ contains
83
 
83
 
84
     if (.not. plan%terminal_handoff_required) error stop "foreground jobs should surface terminal handoff"
84
     if (.not. plan%terminal_handoff_required) error stop "foreground jobs should surface terminal handoff"
85
     if (.not. plan%should_release) error stop "release-on-handoff should be explicit"
85
     if (.not. plan%should_release) error stop "release-on-handoff should be explicit"
86
+    if (plan%action /= FGOF_DEVLOOP_JOB_ACTION_NONE) error stop "release-on-handoff should not restart"
87
+    if (plan%should_stop) error stop "release-on-handoff should not stop the job"
88
+    if (plan%should_start) error stop "release-on-handoff should not start a replacement"
89
+    if (plan%should_restart) error stop "release-on-handoff should not request restart"
90
+    if (plan%reason /= "release for terminal handoff") then
91
+      error stop "release-on-handoff reason should be explicit"
92
+    end if
86
   end subroutine test_terminal_handoff_plan
93
   end subroutine test_terminal_handoff_plan
87
 
94
 
88
   subroutine test_pipeline_wait_observation()
95
   subroutine test_pipeline_wait_observation()
test/test_devloop_model.f90modified
@@ -4,6 +4,7 @@ program test_devloop_model
4
     FGOF_DEVLOOP_DECISION_RESTART, &
4
     FGOF_DEVLOOP_DECISION_RESTART, &
5
     FGOF_DEVLOOP_DECISION_STOP, &
5
     FGOF_DEVLOOP_DECISION_STOP, &
6
     FGOF_DEVLOOP_TRIGGER_CHANGE, &
6
     FGOF_DEVLOOP_TRIGGER_CHANGE, &
7
+    FGOF_DEVLOOP_TRIGGER_NONE, &
7
     FGOF_DEVLOOP_TRIGGER_START, &
8
     FGOF_DEVLOOP_TRIGGER_START, &
8
     begin_devloop_cycle, &
9
     begin_devloop_cycle, &
9
     clear_devloop_options, &
10
     clear_devloop_options, &
@@ -15,13 +16,14 @@ program test_devloop_model
15
     should_start_on_open, &
16
     should_start_on_open, &
16
     start_devloop, &
17
     start_devloop, &
17
     stop_devloop
18
     stop_devloop
18
-  use fgof_devloop_types, only : devloop_cycle, devloop_decision, devloop_options, devloop_state
19
+  use fgof_devloop_types, only : devloop_cycle, devloop_decision, devloop_options, devloop_state, devloop_trigger
19
   implicit none
20
   implicit none
20
 
21
 
21
   type(devloop_state) :: state
22
   type(devloop_state) :: state
22
   type(devloop_options) :: options
23
   type(devloop_options) :: options
23
   type(devloop_cycle) :: cycle
24
   type(devloop_cycle) :: cycle
24
   type(devloop_decision) :: decision
25
   type(devloop_decision) :: decision
26
+  type(devloop_trigger) :: trigger
25
 
27
 
26
   state = clear_devloop_state()
28
   state = clear_devloop_state()
27
   call start_devloop(state)
29
   call start_devloop(state)
@@ -48,6 +50,18 @@ program test_devloop_model
48
   if (.not. decision%should_run) error stop "restart decision should request another run"
50
   if (.not. decision%should_run) error stop "restart decision should request another run"
49
   if (state%consecutive_failures /= 1) error stop "failed cycle should increment failure count"
51
   if (state%consecutive_failures /= 1) error stop "failed cycle should increment failure count"
50
 
52
 
53
+  call start_devloop(state)
54
+  trigger = devloop_change_trigger(0)
55
+  if (trigger%kind /= FGOF_DEVLOOP_TRIGGER_NONE) then
56
+    error stop "zero-count change trigger should be suppressed"
57
+  end if
58
+  trigger = devloop_change_trigger(-2)
59
+  if (trigger%kind /= FGOF_DEVLOOP_TRIGGER_NONE) then
60
+    error stop "negative-count change trigger should be suppressed"
61
+  end if
62
+  cycle = begin_devloop_cycle(state, devloop_change_trigger(0))
63
+  if (cycle%started) error stop "zero-count changes should not start a cycle"
64
+
51
   options = clear_devloop_options()
65
   options = clear_devloop_options()
52
   options%restart_on_change = .false.
66
   options%restart_on_change = .false.
53
   call start_devloop(state, options)
67
   call start_devloop(state, options)