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:
9595
 - `devloop_watch_summary` condenses `fgof-watch` event batches into file, directory, create, modify, remove, move, ignored, and failure counters
9696
 - `devloop_watch_options()` projects dev-loop policy into `fgof-watch` options for debounce polls, hidden-path filtering, and directory event emission
9797
 - `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
9899
 - `devloop_build_command()`, `devloop_run_command()`, and `devloop_smoke_command()` wrap `fgof-process` commands with loop roles and optional process options
99100
 - `run_devloop_command()` executes one command spec and preserves the raw `process_result`, including stdout, stderr, exit code, timeout state, and process error details
100101
 - `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()`
101102
 - `devloop_service_job()` builds a long-running service/job spec backed by `fgof-jobs`
102103
 - `attach_devloop_job()` records an already-launched pid/process group and ownership expectations
103104
 - `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
105106
 - `begin_devloop_cycle()` increments the cycle counter and starts work only when the loop is active, idle, and policy permits the trigger
106107
 - `finish_devloop_cycle()` records success or failure and returns an explicit decision to idle, restart, or stop
107108
 - negative `max_failures` values normalize to unlimited failures
src/fgof_devloop.f90modified
@@ -354,8 +354,10 @@ contains
354354
     type(devloop_trigger) :: trigger
355355
 
356356
     trigger = clear_devloop_trigger()
357
+    if (change_count <= 0) return
358
+
357359
     trigger%kind = FGOF_DEVLOOP_TRIGGER_CHANGE
358
-    trigger%change_count = max(0, change_count)
360
+    trigger%change_count = change_count
359361
     if (present(reason)) then
360362
       trigger%reason = reason
361363
     else
@@ -693,6 +695,8 @@ contains
693695
 
694696
     if (job_state%terminal_handoff_required .and. job_state%spec%release_on_handoff) then
695697
       plan%should_release = .true.
698
+      plan%reason = "release for terminal handoff"
699
+      return
696700
     end if
697701
 
698702
     if (job_state%running .or. job_state%stopped .or. job_state%cleanup_needed) then
test/test_devloop_jobs.f90modified
@@ -83,6 +83,13 @@ contains
8383
 
8484
     if (.not. plan%terminal_handoff_required) error stop "foreground jobs should surface terminal handoff"
8585
     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
8693
   end subroutine test_terminal_handoff_plan
8794
 
8895
   subroutine test_pipeline_wait_observation()
test/test_devloop_model.f90modified
@@ -4,6 +4,7 @@ program test_devloop_model
44
     FGOF_DEVLOOP_DECISION_RESTART, &
55
     FGOF_DEVLOOP_DECISION_STOP, &
66
     FGOF_DEVLOOP_TRIGGER_CHANGE, &
7
+    FGOF_DEVLOOP_TRIGGER_NONE, &
78
     FGOF_DEVLOOP_TRIGGER_START, &
89
     begin_devloop_cycle, &
910
     clear_devloop_options, &
@@ -15,13 +16,14 @@ program test_devloop_model
1516
     should_start_on_open, &
1617
     start_devloop, &
1718
     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
1920
   implicit none
2021
 
2122
   type(devloop_state) :: state
2223
   type(devloop_options) :: options
2324
   type(devloop_cycle) :: cycle
2425
   type(devloop_decision) :: decision
26
+  type(devloop_trigger) :: trigger
2527
 
2628
   state = clear_devloop_state()
2729
   call start_devloop(state)
@@ -48,6 +50,18 @@ program test_devloop_model
4850
   if (.not. decision%should_run) error stop "restart decision should request another run"
4951
   if (state%consecutive_failures /= 1) error stop "failed cycle should increment failure count"
5052
 
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
+
5165
   options = clear_devloop_options()
5266
   options%restart_on_change = .false.
5367
   call start_devloop(state, options)