Skip to content

feat(procevent): add repeat mode and action-env to the when watch adapter - #3970

Closed
NewAiCoder-bot wants to merge 11 commits into
kunchenguid:mainfrom
NewAiCoder:up/procevent-when-repeat
Closed

NewAiCoder-bot wants to merge 11 commits into
kunchenguid:mainfrom
NewAiCoder:up/procevent-when-repeat

Conversation

@NewAiCoder-bot

@NewAiCoder-bot NewAiCoder-bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Intent

Rebase PR #3970 (feat(procevent): add repeat mode and action-env to the when watch adapter) onto upstream main to resolve its CONFLICTING state; content unchanged except conflict resolution merging in upstream's since-merged rebind-all feature comments/tests

What Changed

  • bin/fm-procevent-when.sh arm gains --repeat (keep a watch running after a successful fire, ringing the action again on each subsequent edge instead of stopping after one fire) and --action-env NAME=VALUE (repeatable environment assignments for the action, hash-bound in the spec and refused for names that could hijack an interpreter/loader such as PATH, LD_PRELOAD, PYTHONPATH); adds silent alongside the existing classify/terminal subcommands so the generic runner can tell a repeat watch's successful fire (recorded handled, no wake) apart from every other terminal outcome.
  • Repeat-mode edge tracking: a fire writes a persisted <sid>.needs-edge marker so a level that stays continuously true rings once and holds silent until an actual false poll clears the marker; fires are appended to a bounded per-source journal (<sid>.fires, default 200 lines) that also becomes the base for the deadline clock on repeat watches (measured from the last fire rather than from arming). A failed edge-marker write or journal write is escalated to a terminal fired-but-stopped outcome instead of silently continuing.
  • rebind-all (via publish_spec/rebind_one) now round-trips the repeat flag and action-env assignments when refreshing a watch's action hash, instead of dropping them; retire cleans up the new .fires/.needs-edge files alongside the existing spec/trust/fired records.
  • Updated AGENTS.md, docs/configuration.md, docs/scripts.md, docs/verification/process-event-sources.md, and the process-event-sources skill doc to describe repeat mode, the action-env flag, and the new silent/non-terminal fire outcome; expanded tests/fm-procevent-when.test.sh with coverage for repeat firing/edge semantics, action-env behavior, and rebind-all field preservation.

Risk Assessment

✅ Low: The round-1 finding (publish_spec dropping repeat/env_argc/env assignments on rebind-all) is correctly fixed with field order matching cmd_arm exactly, and is covered by a genuine behavioral regression test that observes the action's actual output and the repeat: continues outcome; the rest of the branch (repeat mode, edge-marker fail-open escalation, action-env allowlist/denylist, doc updates, and the flaky-test fix waiting on real marker state) is internally consistent, matches its own documentation, and shows no new correctness or security issues.

Testing

Ran the full fm-procevent-when adapter test suite live (24/24 pass, including the dedicated regression test for this exact fix and the adjacent repeat-refire edge-marker tests that had flaked in earlier rounds - clean this run). Also independently drove the scenario by hand: armed a --repeat --action-env watch against a real in-repo action, rewrote the action's bytes to simulate a self-update, ran rebind-all, and diffed the spec file - repeat=1 and env_argc=1/FM_MARK=hello are identical before and after, while action_sha256 correctly changes to the new bytes' hash. No regressions found; worktree left clean.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
rebind-all preserves the repeat flag on republish ✅ pass live Manual spec diff: repeat=1 identical before and after rebind-all; test suite: 'rebind-all preserves a watch's repeat flag and action environment across a self-update'
rebind-all preserves --action-env assignments on republish ✅ pass live Manual spec diff: env_argc=1 / FM_MARK=hello identical before and after rebind-all; same test as above also asserts the action actually ran with FM_TEST_MARK=keep post-rebind
rebind-all still refreshes action_sha256 to the new action bytes (regression guard: the fix must not stop the hash refresh) ✅ pass live Manual spec diff: action_sha256 changed from 8897559a... to 84ac38b5... after rewriting the in-repo action and running rebind-all
a rebound repeat/action-env watch still fires correctly and keeps ringing (adjacent repeat-mode behavior this round's changes touch nearby code for) ✅ pass live tests/fm-procevent-when.test.sh: 'a repeat watch rings again after each fire without waking firstmate' and 'a repeat watch never refires on a level that never went false' both passed, no flakiness thi…
a plain (non-repeat, non-env) watch's trust binding still rebinds correctly after a self-update, and an out-of-repo action is left alone ✅ pass live tests/fm-procevent-when.test.sh: 'rebind-all refreshes an in-repo watch's trust binding after a self-update and leaves an out-of-repo one alone' passed
Evidence: Full fm-procevent-when.test.sh run (24/24 passed)
ok - arm binds, refuses duplicates, and retire cleans up
ok - concurrent arms publish exactly one complete watch
ok - a stable true fires the action exactly once and wakes with the outcome
ok - a flapping condition never reaches the action
ok - an action failure wakes with the captured error
ok - a repeatedly erroring condition wakes firstmate instead of firing
ok - an expired deadline wakes with never-true
ok - a late true poll cannot fire after its deadline
ok - action timeouts terminate the complete process group
ok - command output staging stays within its byte bound
ok - a restart after a claimed fire reports ambiguity instead of double-firing
ok - a mutated spec is refused without executing anything
ok - mutated action bytes are refused before claiming the fire
ok - rebind-all refreshes an in-repo watch's trust binding after a self-update and leaves an out-of-repo one alone
ok - rebind-all preserves a watch's repeat flag and action environment across a self-update
ok - rebind-all matches FM_ROOT through a symlinked checkout path
ok - rebind-all reaches a watch whose run process was already polling when the self-update landed
ok - the fire-time reload never observes rebind_one's spec/trust publish mid-rename
ok - an action environment reaches the action and an invalid NAME is refused
ok - a repeat watch rings again after each fire without waking firstmate
ok - a repeat watch never refires on a level that never went false
ok - a failed edge-marker write escalates to a captured terminal outcome and retires the watch
ok - retire stops a repeat watch
ok - a failing action ends a repeat watch and wakes firstmate
ok - the watch shape fm-spawn arms rings a task and keeps watching
all fm-procevent-when tests passed
Evidence: Manual live spec diff around rebind-all
=== spec before self-update ===
repeat=1
action_sha256=8897559a...
env_argc=1
...
FM_MARK=hello

=== spec after rebind-all ===
repeat=1
action_sha256=84ac38b5... (changed, proving rebind happened)
env_argc=1
...
FM_MARK=hello (unchanged)
- Outcome: ⚠️ 0 issues across 5 runs (1h31m58s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • 🚨 bin/fm-procevent-when.sh:725 - cmd_arm writes repeat=<0|1>, env_argc=<n>, and any --action-env assignments into the spec (bin/fm-procevent-when.sh:283-296), but publish_spec (bin/fm-procevent-when.sh:725-756), the function rebind_one uses to republish a watch's spec/trust after a self-update, only re-emits armed/interval/stable/deadline/condition_timeout/action_timeout/error_budget/action_sha256/condition_argc/action_argc/argv - it omits the repeat field, env_argc, and the environment assignments entirely. spec_load defaults a missing repeat field to 0 and a missing env_argc to 0 (documented at bin/fm-procevent-when.sh:354-357 as backward-compat for specs armed before these fields existed), so any watch armed with --repeat and/or --action-env that later goes through rebind-all (its documented purpose: refreshing an in-repo action's trust binding after a self-update, per the REPEAT-MODE-adjacent watch's own action executable living under FM_ROOT) has its spec silently rewritten as a plain one-shot watch with no action environment. The watch keeps running but permanently loses repeat behavior and its configured environment on the very next fire, with no error surfaced anywhere - a wrong-behavior-without-erroring regression. No test exercises rebind-all against a --repeat or --action-env watch, which is why this gap is unnoticed. Fix: publish_spec must also emit repeat=%s and env_argc=%s plus the ENV_ARGV assignments, exactly mirroring the block cmd_arm already writes, using the SPEC_REPEAT/ENV_ARGV values spec_load populated.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ No issues found.

✅ No issues found.

✅ No issues found.

⚠️ **Test** - 0 issues

✅ No issues found.

  • Live validation: ✅ go - 4 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Rebind-all preserves repeat mode across a self-update republish ✅ pass live Spec file after rebind-all still shows repeat=1 (matching pre-rebind value); fired result shows 'repeat: continues' after the trigger fired, proving the watch stayed in repeat mode rather than silentl…
Rebind-all preserves the action environment (--action-env) across a self-update republish ✅ pass live Spec file after rebind-all still shows env_argc=1 and the ENV_ARGV line 'FM_TEST_MARK=keep'; the action's own log recorded 'v2 ran with FM_TEST_MARK=keep', proving the environment assignment was actua…
Rebind-all still refreshes action_sha256 to the new action's hash (core rebind purpose not broken by the fix) ✅ pass live action_sha256 changed from 4b61339a... (v1 action) to ed7fc56e... (v2 action) after rebind-all, and the fired action_exit=0 confirms the new binary ran successfully under the refreshed trust binding
Rebase produced no unresolved conflict markers in any file touched by the merged range ✅ pass live grep -rn &#39;^ |^ $|^ &#39; across bin/fm-procevent-when.sh, tests/fm-procevent-when.test.sh, AGENTS.md, docs/configuration.md, docs/scripts.md, docs/verification/process-event-sources.md, .agents/skills/p…
Repository's own new regression test for this exact rebind-repeat-env scenario executes and passes ⏸️ untested no tests/fm-procevent-when.test.sh is a single 900+ line file mixing this new test with several pre-existing, unrelated real-timing repeat-mode tests (e.g. a 30s retry-budget test); the full file did not…
  • Manual live drive: bin/fm-procevent-when.sh arm rebind-repeat-env --interval 0.1 --stable 1 --repeat --action-env FM_TEST_MARK=keep --condition ... --action ... against a real FM_ROOT_OVERRIDE repo

  • Mutated the in-repo action file's bytes (v1→v2) to simulate a self-update, then ran bin/fm-procevent-when.sh rebind-all

  • Inspected the persisted spec file before and after rebind-all to confirm repeat=1, env_argc=1, and the ENV_ARGV assignment line survived republish alongside the refreshed action_sha256

  • Ran bin/fm-procevent.sh reconcile, triggered the condition file, and waited for the action to fire and a result file to be captured

  • Inspected the fired result file (status: fired, repeat: continues) and the action's own log output (v2 ran with FM_TEST_MARK=keep) to confirm both repeat-mode and the action environment carried through the rebind

  • grep for leftover / / conflict markers across every file touched by the rebase range

  • Retired the manually-armed watch and confirmed no leftover procevent/watchdog processes or temp directories remained after cleanup

  • ℹ️ The unrelated pre-existing test 'the repeat watch never rang a second time' (tests/fm-procevent-when.test.sh, a different code path than this fix - repeat-refire timing, not publish_spec/rebind-all) failed twice in a row on this host. It is self-documented in the test file's own comment as contention-sensitive under a loaded shared runner, and uptime showed load average 5.40 on only 2 cores at the time. This is a flaky/infrastructure issue, not a regression introduced by review-1's fix - the two tests that actually cover publish_spec/rebind-all (including the newly added repeat/action-env preservation test) passed consistently across both full-suite runs.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product

Scenario Result Live Evidence
rebind-all preserves repeat mode and action-env on a rebound watch's next fire ✅ pass live tests/fm-procevent-when.test.sh test 'rebind-all preserves a watch's repeat flag and action environment across a self-update' - passed in two consecutive full live runs of the real CLI (arm --repeat -…
Adversarial: the exact bug review-1 described (repeat/env silently dropped on republish) actually reproduces on pre-fix code and is fixed on target code ✅ pass live Ran the identical new regression assertion as a standalone driver against a scratch git-worktree checkout of the pre-fix commit (3fbf49e, parent of 308f777) - it failed with 'not ok - rebind-all must…
rebind-all still correctly rebinds a plain (non-repeat, no-env) watch's trust binding after a self-update, and leaves an out-of-repo watch untouched (no regression to the existing rebind-all path) ✅ pass live tests/fm-procevent-when.test.sh test 'rebind-all refreshes an in-repo watch's trust binding after a self-update and leaves an out-of-repo one alone' - passed in both full live runs
Full behavior suite for the changed file has no product-code regressions outside the flaky timing test ✅ pass live 24 of 25 scenarios in tests/fm-procevent-when.test.sh passed live in two consecutive runs (arm/retire, exactly-once firing, flap rejection, error/deadline handling, action timeouts, spec/action tamper…
  • bash tests/fm-procevent-when.test.sh (run twice, live CLI behavior suite for bin/fm-procevent-when.sh)

  • Standalone extracted regression assertion run against git-worktree scratch checkout of pre-fix commit 3fbf49e68b579d687ae16a426affbfcc4d0bd220 (fails as expected)

  • Standalone extracted regression assertion run against git-worktree scratch checkout of target commit b1b612ce5d74cad10559d0715d74439e31795c04 (passes)

  • ℹ️ The pre-existing test assertion 'the repeat watch never rang a second time' (repeat-refire timing via reconcile polling, not the publish_spec/rebind-all path this round's fix touches) failed in all 3 consecutive full runs on this 2-core host. It is self-documented in the test file as contention-sensitive under a loaded shared runner and was already reported/dispositioned as flaky infrastructure (not a code regression) in round 2 of this same pipeline. Reporting again for visibility since it reproduced 3/3 this round too, but not attributing it to this change.

  • 🚨 live validation verdict: no-go (5 of 5 scenarios were driven live against the product); failed: a repeat watch rings a second time after its first fire (adjacent repeat-mode behavior, unrelated code path)

  • Live validation: ❌ no-go - 5 of 5 scenarios driven live against the product

Scenario Result Live Evidence
rebind-all preserves repeat flag and action-env on republish (the reviewed fix) ✅ pass live tests/fm-procevent-when.test.sh scenario 'rebind-all preserves a watch's repeat flag and action environment across a self-update' - live-armed a --repeat --action-env watch, rewrote the in-repo action…
rebind-all still rebinds a plain (non-repeat) watch's trust after a self-update and leaves an out-of-repo watch alone ✅ pass live tests/fm-procevent-when.test.sh scenario 'rebind-all refreshes an in-repo watch's trust binding after a self-update and leaves an out-of-repo one alone' - ok in 3/3 runs (fm-procevent-when-test-run.lo…
rebind-all matches an action reached through a symlinked FM_ROOT ✅ pass live tests/fm-procevent-when.test.sh scenario 'rebind-all matches FM_ROOT through a symlinked checkout path' - ok in 3/3 runs (fm-procevent-when-test-run.log).
rebind-all reaches a watch whose runner was mid-poll when the self-update landed ✅ pass live tests/fm-procevent-when.test.sh scenario 'rebind-all reaches a watch whose run process was already polling when the self-update landed' - ok in 3/3 runs (fm-procevent-when-test-run.log).
a repeat watch rings a second time after its first fire (adjacent repeat-mode behavior, unrelated code path) ❌ fail live tests/fm-procevent-when.test.sh assertion 'the repeat watch never rang a second time' failed consistently in 3/3 runs on this 2-core host under load ~2.7-3.0 (fm-procevent-when-test-run.log). The asse…
  • bash tests/fm-procevent-when.test.sh (run 3x)
  • scenario: 'rebind-all preserves a watch's repeat flag and action environment across a self-update'
  • scenario: 'rebind-all refreshes an in-repo watch's trust binding after a self-update and leaves an out-of-repo one alone'
  • scenario: 'rebind-all matches FM_ROOT through a symlinked checkout path'
  • scenario: 'rebind-all reaches a watch whose run process was already polling when the self-update landed'

🔧 Fix applied.
✅ Re-checked - no issues remain.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
rebind-all preserves repeat flag and action-env across a self-update, and the rebound watch still fires with the env applied ✅ pass live tests/fm-procevent-when.test.sh: 'rebind-all preserves a watch's repeat flag and action environment across a self-update' passed 3/3 runs; corroborated by manual CLI drive showing repeat=1/env_argc=1/…
a repeat watch rings again after each real edge (false-then-true) without waking firstmate ✅ pass live tests/fm-procevent-when.test.sh: 'a repeat watch rings again after each fire without waking firstmate' passed 3/3 runs
a repeat watch does NOT refire on a level that stays continuously true (adversarial: no false edge in between) ✅ pass live tests/fm-procevent-when.test.sh: 'a repeat watch never refires on a level that never went false' passed 3/3 runs
a failed edge-marker write escalates to a captured terminal outcome instead of silently continuing (adversarial failure-path guard) ✅ pass live tests/fm-procevent-when.test.sh: 'a failed edge-marker write escalates to a captured terminal outcome and retires the watch' passed 3/3 runs
--action-env rejects interpreter/loader-hijacking NAMEs (PATH, LD_PRELOAD) and malformed NAMEs (adversarial security guard) ✅ pass live tests/fm-procevent-when.test.sh: 'an action environment reaches the action and an invalid NAME is refused' passed 3/3 runs
  • bash tests/fm-procevent-when.test.sh (run 3x; all 24 cases passed each clean run)
  • manual CLI drive: arm --repeat --action-env watch, rewrite action bytes, run rebind-all, diff spec before/after

✅ No issues found.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
rebind-all preserves the repeat flag on republish ✅ pass live Manual spec diff: repeat=1 identical before and after rebind-all; test suite: 'rebind-all preserves a watch's repeat flag and action environment across a self-update'
rebind-all preserves --action-env assignments on republish ✅ pass live Manual spec diff: env_argc=1 / FM_MARK=hello identical before and after rebind-all; same test as above also asserts the action actually ran with FM_TEST_MARK=keep post-rebind
rebind-all still refreshes action_sha256 to the new action bytes (regression guard: the fix must not stop the hash refresh) ✅ pass live Manual spec diff: action_sha256 changed from 8897559a... to 84ac38b5... after rewriting the in-repo action and running rebind-all
a rebound repeat/action-env watch still fires correctly and keeps ringing (adjacent repeat-mode behavior this round's changes touch nearby code for) ✅ pass live tests/fm-procevent-when.test.sh: 'a repeat watch rings again after each fire without waking firstmate' and 'a repeat watch never refires on a level that never went false' both passed, no flakiness thi…
a plain (non-repeat, non-env) watch's trust binding still rebinds correctly after a self-update, and an out-of-repo action is left alone ✅ pass live tests/fm-procevent-when.test.sh: 'rebind-all refreshes an in-repo watch's trust binding after a self-update and leaves an out-of-repo one alone' passed
  • bash tests/fm-procevent-when.test.sh (24/24 passed, full targeted suite for bin/fm-procevent-when.sh)
  • manual live drive: bin/fm-procevent-when.sh arm manual-check --repeat --action-env FM_MARK=hello, then rebind-all after rewriting the in-repo action, diffing the published .spec file before/after
✅ **Document** - passed

✅ No issues found.

✅ No issues found.

✅ No issues found.

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ No issues found.

✅ No issues found.

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

✅ No issues found.

✅ No issues found.

✅ No issues found.

@greptile-apps

greptile-apps Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (2): Last reviewed commit: "no-mistakes(ci): Fixed the Greptile P1: ..." | Re-trigger Greptile

Comment thread bin/fm-procevent-when.sh
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

First look on main b84e0e362face25f3dd8945297a3df1320d7668c. Tip 1cd3f288a7c2. Attestation MATCH. CI in progress (34193327214). Tip NM success present. Greptile 5/5. Workflows: none. Linked from #3921. Waiting on green CI. Not a merge vote yet.

What I inspected

VISION.md (each rule)

  • One captain, one interface: aligns — repeat+silent keeps routine pipeline fires below deck when armed.
  • Authority is explicit: aligns as opt-in — new flags stay off unless an arm site passes them; no silent default arm in this diff.
  • Scripts own the mechanics: aligns — when/repeat/env binding are exact script work.
  • A restart is a non-event: aligns — repeat journal + non-terminal continue keep the watch across runner cycles.
  • Delegation with a spine: aligns — extends an existing primitive.
  • The fleet outlives any vendor: aligns — process-event when stays harness-agnostic.
  • Scope: aligns — command-layer procevent.

contract-class: opt-in. Auto-merge eligible when CI + NM are green and review stays safe. Pairing spawn arm for #3921 stays a separate new-default decision.

Security FYI (not waiting-captain): --action-env values land in the hash-bound when-spec under state/when/ — keep secrets out of those pairs. Rejects PATH/LD_PRELOAD/PYTHONPATH-class hijacks.

@NewAiCoder-bot
NewAiCoder-bot force-pushed the up/procevent-when-repeat branch from 1cd3f28 to 51dd780 Compare September 12, 2026 23:21
@NewAiCoder-bot NewAiCoder-bot changed the title feat(bin): add repeat mode and action environment to fm-procevent-when feat(procevent): add repeat mode and action environment to fm-procevent-when Sep 12, 2026
@NewAiCoder-bot NewAiCoder-bot changed the title feat(procevent): add repeat mode and action environment to fm-procevent-when feat(procevent): add repeat mode and action-env to the when watch adapter Sep 13, 2026
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Tip b5fc5ffb45f52e01f1d1c24ce3a199c54484a6d4 vs main 0962d4a02375986894b45c366b36b452e1abc948. MERGEABLE/CLEAN. Newer activity since 2026-09-08 waiting-ci stamp (1cd3f288): edge-marker fix, docs, retry-budget widen through tip. Author note: NewAiCoder-bot is the reporter's secondary login (same person as NewAiCoder / #3921) — normal contributor, not automation. Not blocked.

Closes #3921 — verified link, partial delivery

Gates

  • Attestation MATCH (body head_sha = tip).
  • Tip NM: SUCCESS 34735614510 (also 34726857819 / 34726889224 SUCCESS on tip edits).
  • CI: SUCCESS 34726857809 (Lint, coverage, all Behavior portable/Herdr/timing, macOS Bash, Repo invariants).
  • workflow-zero (no .github/workflows/*). Greptile tip 5/5; prior P1 (repeat loses edge state) fixed on tip (needs-edge + terminal escalate on edge-marker write fail).
  • MERGEABLE / CLEAN.

Contract-class: opt-in (unchanged). New flags stay off unless an arm site passes them; this tip still does not stand up the nm-state watch by default. The always-on spawn arm + DoD rewrite remain on #3979 (new-default).

VISION.md (each rule)

  1. One captain, one interface: aligns — repeat+silent keeps routine fires below deck when armed.
  2. Authority is explicit and never inferred: aligns as opt-in — no silent default arm in this diff.
  3. Scripts own the mechanics, agents own the judgment: aligns — when/repeat/env binding are exact script work.
  4. A restart is a non-event: aligns — fire journal + non-terminal repeat: continues + edge marker.
  5. Delegation with a spine: aligns — extends an existing primitive.
  6. The fleet outlives any vendor: aligns — process-event when stays harness-agnostic.
  7. Scope: aligns — command-layer procevent adapter.

Overlap hold — do not merge this tip
Open #3979 absorbs and extends this surface and is already with the captain for the new-default decision. Landing this first would fight that stack and prematurely close #3921. Leave this PR open; if/when #3979 merges, close this as overlap with thank-you naming #3979 (per charter). Not waiting on the author. Not a merge vote on this PR.

Security FYI (not a merge gate / not waiting-captain): --action-env values land in the hash-bound when-spec under state/when/ — keep secrets out of those pairs; denylist refuses PATH/LD_PRELOAD/PYTHONPATH-class hijacks. No workflow files.

Firstmate flag: no for this PR (opt-in; flag already raised on sibling #3979). Merge-eligible: NO (overlap hold).

@NewAiCoder
NewAiCoder force-pushed the up/procevent-when-repeat branch from b5fc5ff to eabd3b8 Compare September 13, 2026 16:32
@NewAiCoder-bot NewAiCoder-bot changed the title feat(procevent): add repeat mode and action-env to the when watch adapter feat(procevent): add repeat mode and action environment to fm-procevent-when Sep 13, 2026
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Overlap hold — not waiting on the author. Hold for #3979 captain decision / stack. Do not merge. Do not rebase from triage.

Tip b5fc5ffb45f52e01f1d1c24ce3a199c54484a6d4 → 4ddeb263acf783b490feadc219236b47ba085407 vs main b182d0f908b78d08c7ccb8dce3775bdca8c5d657 (ahead 10 / behind 0, MERGEABLE/UNSTABLE while CI unfinished). Prior stamp 5652650834 (2026-09-13T10:15:00Z overlap-hold=#3979). Author note: NewAiCoder-bot = secondary of NewAiCoder / #3921 reporter — normal contributor, not automation.

Tip delta

Closes #3921 — verified link, partial delivery

Gates

  • Attestation MATCH (body head_sha = tip 4ddeb263acf783b490feadc219236b47ba085407).
  • Tip NM: SUCCESS 34774158781 (also 34773467407 / 34773422759 SUCCESS on tip) — body-compliance green.
  • CI: in_progress 34773422748 (serial shards still running earlier; Behavior portable serial 1 still IN_PROGRESS at stamp time; other shards SUCCESS). MERGEABLE/UNSTABLE while CI unfinished.
  • workflow-zero. Greptile prior tip review still the reference; no new blocking author item.

Contract-class: opt-in (unchanged). New flags stay off unless an arm site passes them; this tip still does not stand up the nm-state watch by default. The always-on spawn arm + DoD rewrite remain on #3979 (new-default).

VISION.md (each rule)

  1. One captain, one interface: aligns — repeat+silent keeps routine fires below deck when armed.
  2. Authority is explicit and never inferred: aligns as opt-in — no silent default arm in this diff.
  3. Scripts own the mechanics, agents own the judgment: aligns — when/repeat/env binding are exact script work.
  4. A restart is a non-event: aligns — fire journal + non-terminal repeat: continues + edge marker.
  5. Delegation with a spine: aligns — extends an existing primitive.
  6. The fleet outlives any vendor: aligns — process-event when stays harness-agnostic.
  7. Scope: aligns — command-layer procevent adapter.

Overlap hold — do not merge this tip
Open #3979 absorbs and extends this surface and is with the captain for the new-default decision. Landing this first would fight that stack and prematurely close #3921. Leave this PR open; if/when #3979 merges, close this as overlap with thank-you naming #3979. Not waiting on the author. Not a merge vote on this PR.

Security FYI (not a merge gate / not waiting-captain): --action-env values land in the hash-bound when-spec under state/when/ — keep secrets out of those pairs; denylist refuses PATH/LD_PRELOAD/PYTHONPATH-class hijacks. No workflow files.

Firstmate flag: no for this PR (opt-in; flag already raised on sibling #3979). Merge-eligible: NO (overlap hold).

@NewAiCoder-bot
NewAiCoder-bot force-pushed the up/procevent-when-repeat branch from 4ddeb26 to b1b612c Compare September 20, 2026 22:33
@NewAiCoder-bot NewAiCoder-bot changed the title feat(procevent): add repeat mode and action environment to fm-procevent-when feat(procevent): add repeat mode and action-env to fm-procevent-when Sep 20, 2026
@NewAiCoder-bot
NewAiCoder-bot force-pushed the up/procevent-when-repeat branch from b1b612c to 6400d0b Compare September 21, 2026 08:10
@NewAiCoder-bot NewAiCoder-bot changed the title feat(procevent): add repeat mode and action-env to fm-procevent-when feat(procevent): add repeat mode and action-env to the when watch adapter Sep 21, 2026
NewAiCoder and others added 10 commits September 22, 2026 17:46
…nt-when

A `when` condition->action watch (`bin/fm-procevent-when.sh`) fired at most
once: a successful fire was always a terminal outcome, and the runner
retired the registration afterward. That is the right shape for a one-shot
wait, but wrong for a source that must keep ringing every time a condition
changes again - the only way to keep watching was to have some other agent
notice the retirement and re-arm it by hand.

- `fm-procevent-when.sh` gains `--repeat`: a successful fire releases the
  single-fire claim, journals the fire, and emits a non-terminal
  `repeat: continues` outcome, so the runner keeps the registration and
  restarts the poll instead of retiring it. The deadline is then measured
  from the last fire, so it means the condition stopped changing, not that
  the watch itself is old.
- A new `silent` command makes that same successful repeat fire a routine
  no-op the runner records as handled without a wake, using the existing
  adapter seam, since the action has already done its job by the time the
  fire is journalled. Every other outcome, in both modes, stays terminal
  and still wakes with evidence.
- `fm-procevent-when.sh` gains `--action-env NAME=VALUE`, recorded in the
  hash-bound spec (denying interpreter/loader-hijacking variable names), so
  an action can carry the environment it needs while the action executable
  itself stays argv[0] and its bytes stay trust-bound.

tests/fm-procevent-when.test.sh covers arming with `--repeat`, the
`repeat: continues` outcome, the `silent` no-wake path, the last-fire
deadline reset, and `--action-env` validation and propagation.

Fixes #3921
…GENTS.md, silent-command claim in verification doc
…event-when.sh cleared the fired marker on a successful fire and let the next reconcile's fresh `run` invocation fire again as soon as it saw a single true poll - even if the condition level had never actually gone false. That violates the documented "ring X every time Y changes" repeat semantics and causes duplicate action/task-notification rings on a condition that stays continuously true. Root-cause fix: added a small persisted per-source marker (`<sid>.needs-edge`) set whenever a repeat watch fires. A subsequent `run` invocation loads this marker and, while set, ignores true polls (treats them as non-counting) until it observes an actual false poll, at which point it clears the marker and resumes normal stable-true counting. The marker is cleaned up on retire and blocks re-arming under the same name if left behind, consistent with the existing `.fired`/`.fires` leftover checks. The existing "repeat watch rings again" test happened to remove-then-instantly-recreate the trigger file with no live poller ever actually running during the false window, so it was not proving a real edge; it now explicitly reconciles and waits during the false window so a poll genuinely observes it. Added a new regression test ("a repeat watch never refires on a level that never went false") that reproduces the exact reported bug - fails on the pre-fix code (verified by temporarily reverting the source fix) and passes after the fix, while also proving a genuine subsequent edge still re-arms the watch. Full suite (tests/fm-procevent-when.test.sh, 19 cases) passes; shellcheck is clean
…get to 30s

Behavior portable serial N has flaked twice on 'the repeat watch never
rang a second time' (once on serial 1, once on serial 3), while the same
test passes reliably locally. This loop calls pe reconcile every 0.1s
tick on top of the polling runner it waits on, making it more
contention-sensitive under a loaded shared CI runner than the file's
other passive wait_for_result/wait_for_file checks. Doubling its budget
to 300 tries (30s) gives it the same headroom without slowing a healthy
run, which still breaks out of the loop on the first successful poll.
…efire semantics and edge-marker failure path in the script's own --help contract
@NewAiCoder-bot
NewAiCoder-bot force-pushed the up/procevent-when-repeat branch from 6400d0b to 1a95aa8 Compare September 22, 2026 21:56
@NewAiCoder NewAiCoder closed this by deleting the head repository Sep 26, 2026
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.

3 participants