Skip to content

fix(gate): publish signal markers atomically - #499

Merged
simonheimlicher merged 2 commits into
mainfrom
work/gate-signal-marker-atomicity
Jul 31, 2026
Merged

fix(gate): publish signal markers atomically#499
simonheimlicher merged 2 commits into
mainfrom
work/gate-signal-marker-atomicity

Conversation

@simonheimlicher

@simonheimlicher simonheimlicher commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Publish the gate signal harness PID and delivered-signal markers atomically, so a marker appears only once its content is complete.
  • Record the inverted predicate seam in that harness as a separate, tracked concern.

Background

The level-2 gate scenarios announce a grandchild PID and a delivered-signal value through filesystem markers. Both were written with Path.write_text, which creates the file before writing its content, so the marker exists while still empty.

Three sites treated existence as completeness. The spawn-window wrapper gates on marker.exists() and therefore raised the forwarded signal while the PID marker was still empty; _read_grandchild_pid then parsed an empty string and raised ValueError out of a retry loop guarding only FileNotFoundError. The delivered-signal reader carried the same guard against the same non-atomic write.

Root cause

marker.exists() becomes true at file creation, not at write completion. Under load that window widens, which is why test_signal_during_production_spawn_reaches_child_group failed inside a full 992-test run while passing in isolation.

Changes

  • outcomeeng_testing/harnesses/gate_signal.py: stage each marker beside its target and os.replace it into place, so publication is atomic and the existing existence gate means what it claims. Both readers additionally treat an unparseable marker as not-ready rather than fatal.
  • spx/15-validation.enabler/65-gate.enabler/ISSUES.md: record that the harness holds every predicate for the level-2 scenarios while each linked test is a bare delegating call, inverting the seam the test-infrastructure decision requires.

Test plan

  • just check-full passes on the head this PR opens with (pytest 544s, zero failures)
  • The previously intermittent scenario passes under full-suite load, the condition that exposed it
  • spx/15-validation.enabler/65-gate.enabler/tests/test_gate.scenario.l2.py passes across repeated runs

Validation

  • Touched-scope deterministic verification passes
  • /reload-plugins confirms the change loads in a running session

Refs

  • spx/15-validation.enabler/65-gate.enabler
  • spx/31-outcomeeng.enabler/31-verification.enabler/31-test-verification.enabler/15-test-infrastructure.pdr.md

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

The grandchild announced its PID with a plain write, which creates the
marker before it writes the content. The spawn-window wrapper gates on
marker.exists(), so it raised the forwarded signal while the file was
still empty, and the reader parsed an empty string.

Stage each marker beside its target and os.replace it into place, so the
marker appears only once complete and an existence check means what it
claims. Both readers additionally treat an unparseable marker as
not-ready rather than fatal.

Refs: spx/15-validation.enabler/65-gate.enabler
The four signal-harness entry points hold every predicate for the node's
level-2 scenarios while each linked test is a bare delegating call, so
the harness rather than the executed test decides pass and fail.

Restructuring the seam rewrites four entry points and the linked test
file in code that drives real subprocess signal delivery, so it belongs
on its own reviewed diff rather than bundled into the marker
publication fix beside it.

Refs: spx/15-validation.enabler/65-gate.enabler
@sonarqubecloud

Copy link
Copy Markdown

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Reviewed the atomic marker-publication fix in outcomeeng_testing/harnesses/gate_signal.py and the new spx/15-validation.enabler/65-gate.enabler/ISSUES.md.

Cross-checks performed:

  • Traced every writer of pid_path/signal_path: only the new announce() helper writes them (stage to marker.name + ".staged", then os.replace), and both readers (_read_grandchild_pid, _assert_grandchild_received_group_signal) only ever read the target path, never the staged path — so the existence check the spawn-window wrapper (_spawn_signal_wrapper_program) still uses on marker.exists() is now correctly synchronized with content completeness.
  • Grepped the repo for an existing atomic-write helper (os.replace, atomic_write, write_text_atomic) that this new inline announce() might disagree with — found none outside this file, so no horizontal-consistency conflict.
  • Verified the ISSUES.md citation against spx/31-outcomeeng.enabler/31-verification.enabler/31-test-verification.enabler/15-test-infrastructure.pdr.md lines 27 and 35 (executed test owns every predicate; harnesses never expose verdict-shaped helpers) — the quoted rule text matches, and the four named entry points (assert_signals_terminate_process_groups_within_grace, assert_spawn_window_signals_reach_child_groups, assert_production_spawner_captures_child_output, assert_production_spawner_signal_terminates_child) are confirmed bare-delegated by the four tests in test_gate.scenario.l2.py.
  • Confirmed the new ISSUES.md follows this repo's established per-node ISSUES.md convention (title, resolution shape, why-separate, revisit condition), consistent with existing files such as spx/31-outcomeeng.enabler/31-verification.enabler/ISSUES.md.

No findings.

@simonheimlicher
simonheimlicher merged commit 84e09bb into main Jul 31, 2026
6 checks passed
@simonheimlicher
simonheimlicher deleted the work/gate-signal-marker-atomicity branch July 31, 2026 05:57
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