Watchdog live capture targets stale processes and emits excluded-app freeze events #45
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by independent exact-head review of Breadway/bread PR24 at
c4e0277713. Confidence high: real daemon, real eu-stack and CLI probes reproduce. Repair on existing PR24, preserve feature/breadbug-watchdog for stacked PR25.Evidence: /tmp/opencode/watchdog-review/{repro.py,repro.log,stale-capture-report.json,lifecycle-probe.rs,lifecycle.log}. Existing 51 bread-bug,29 scrub,13 CLI,11 watchdog,3 journal tests and2 daemon IPC cases pass but miss these behaviors. Full BREAD-18 blocked-main-loop/notification and actual suspend/resume acceptance stays open.
Full exact-head changes-requested review with additional identity-redaction and unavailable-store grace requests persisted: #24 (comment) . BREAD-18 moved todo, confirmed; original full integration criteria preserved. Repair existing PR24, no duplicate PR. Keep feature/breadbug-watchdog for PR25. CI run146 check job2479 and all steps succeeded at
c4e0277713; bugs reproduced despite green suites. No merge or done.All five original items plus the identity-scrub and store-independent grace requests verified repaired at
bbb12d6(details: #24 (comment)). Not closed: exact-head CI is red pending a clean rerun, and the PR is unmerged. Note for follow-up: system.os_id exemption uses exact equality, so a composite value containing a configured identity survives in that single field (low risk, value comes from /etc/os-release).Independent verification on merged main: PASS, no merge action (already merged)
Verified SHA:
90cd7b4b6fae1b4dd99e2f9830d42a05e7f3c036(currentorigin/maintip, the PR24 merge; repair commitsd2a588b,875cb2c,1317554). There is no PR left to merge: PR24 is already merged, so this verifies the content that is actually on main. Scratch worktree~/.worktrees/bread/verify-bread46-90cd7b4detached at90cd7b4, clean tree, primary checkout untouched. Breadboard card BREAD-46 stays in review for coordinator verification, not done.Locked workspace checks on the exact SHA
cargo fmt --check: passcargo clippy --workspace --all-targets --locked -- -D warnings: pass, 0 warningscargo test --workspace --locked: 594 passed, 0 failed, 21 suites. The fourlivestack_realtests ran against the real eu-stack 0.196 with real child processes (real_capture_is_module_relative_with_the_elf_build_id,real_stripped_binary_has_module_offset_and_build_id_but_no_symbol,real_unstripped_binary_names_its_functions_with_the_same_offsets,real_capture_refuses_an_unrelated_process_that_borrows_a_bread_name), zero "skipped" lines.checkworkflow only triggers on feature/fix pushes, so main has no check run to read; the exact-SHA dev-releasebuild (push)run 167 on90cd7b4is success.Evidence obtained outside the test suite
eu-stack -b -m -r -n 128 -p PID(elfutils 0.196) against a compiled PIE, the same binary afterstrip, and a--build-id=nonebuild. Output form matches the committed fixtures exactly:[build-id]@0xBASE+0xOFFSETdetail lines, and no detail line at all for the no-build-id module. Recorded under /tmp/opencode/bread46-probe.breadbugbinary, a hand-seeded store in scratch XDG dirs and a realbreadpad-named target:0xanywhere in the payload; no memory, register or environment text.recovered, and freeze thenstopped: "the app's latest marker is not a freeze", 0 frames.readelf/nm: breadpadparkst_value 0x1149 vs frame offset 0x1180; libcclock_nanosleep0xe4a20 vs frame 0xe4a6b,__nanosleep0xf0ed0 vs frame 0xf0ee6. Module-relative values, not virtual addresses and not function displacements.Per finding
bread-bug/src/procid.rs:43,61,84,94,123records and compares kernel start ticks,/proc/PID/exeand boot id;bread-bug/src/livestack.rs:74,78,126,130verifies the instance before running eu-stack, again after it returns, andexe_matches_appties the recorded executable to the app, socommis never consulted.breadd/src/bugstore.rs:820rejects a heartbeat whose pid executable does not match the app, andipc_markerclears client-supplied pid/process, so a recorded identity is always daemon-observed. Tests:livestack::tests::comm_prefix_is_not_an_identity,same_app_with_a_reused_pid_is_refused,another_apps_executable_is_refused,the_tool_is_not_run_when_the_target_does_not_match(call counter proves eu-stack never ran),a_swap_between_the_check_and_the_result_discards_the_frames; daemonheartbeat_from_an_unrelated_executable_is_rejected; real toolsreal_capture_refuses_an_unrelated_process_that_borrows_a_bread_name. The old code atc4e0277(livestack.rs:20-24) was a/proc/PID/commprefix test only, so these tests cannot pass against it.breadbug/src/lib.rs:219takes the single latest marker and requires it to beFreeze, refusing withNoActiveFreezeotherwise;Stoppedis a new store-only kind written when a frozen instance ends or is replaced (bread-bug/src/store.rs:67-80,breadd/src/bugstore.rs:796). Capture is not invoked for recovery, stop or death (asserted with a call counter, not just absence from output). Tests:a_recovered_stopped_or_later_marker_invalidates_the_freeze(recovered, stopped, panic, crash),interactive_prompt_is_not_offered_after_recovery,a_new_freeze_after_recovery_is_capturable_again, plus the CLI probe above. Old code.rev().find(|m| m.kind != MarkerKind::Recovered)walked back past a recovery, which is the reported defect.bread-bug/src/livestack.rs:104pins the argument list to-b -m -r -n 128 -p;parse_eu_stackandparse_module_line(171, 264) build a frame from the module line plus the[build-id]@base+offsetdetail line, cross-check the absolute pc against base+offset (accepting the return-address minus one), and emit a frame only when module, module-relative offset and build-id are all present. Absolute pcs andsymbol + 0xdispdisplacements are never stored (no_frame_carries_a_virtual_address,default_output_without_module_info_yields_no_offset,a_detail_line_that_does_not_match_its_frame_is_ignored,malformed_build_ids_and_modules_are_rejected). Stripped coverage: fixtureeu-stack-pie-stripped.txtplus realreal_stripped_binary_has_module_offset_and_build_id_but_no_symbol. No memory payload:memory_registers_and_environment_never_reach_the_payloadand the exhaustive four-field destructor testa_frame_has_exactly_four_fields_and_nothing_else_can_be_added_silently;breadbug/src/report/tests.rs:73keeps a username-bearing module path scrubbed out of the report while the build-id survives, so frames do not widen the privacy surface.breadd/src/watchdog.rs:170,217,breadd/src/bugstore.rs:820) and again before record/publish (breadd/src/bugstore.rs:770-793), so an excluded app produces no marker file and nobread.bug.freezeorbread.bug.recovered. Tests:watchdog::tests::excluded_apps_are_never_tracked,bugstore::watchdog_store_tests::excluded_apps_never_publish_watchdog_markers,recovered_marker_is_also_suppressed_for_an_excluded_app, real daemonconfigured_excluded_app_never_tracks_or_publishes_a_freeze(asserts noapps/clipdirectory and no bus event). Storage-failure behavior is defined and observable: an unstorable freeze is still published with"stored": falseand a warning (an_unstorable_freeze_is_published_with_stored_false_and_warned,store_errors_publish_freeze_with_stored_false,missing_store_still_publishes_freeze_with_stored_false, positivea_stored_freeze_says_stored_true), andbreadbugnever offers a live stack without a stored marker. Reading note: the issue text's "(no bread.bug.freeze when no marker exists)" holds in the excluded case it describes, and the review's "define observable storage-failure behavior instead of pretending a stored marker exists" holds through thestored: falseflag. The two differ for a genuine disk failure by design, documented in code and accepted in review comment 5169; flagged so the coordinator can say if total silence was intended instead.breadd/src/watchdog.rs:262probes every silent instance and every frozen one, so a dead or replaced frozen instance emitsGonewithwas_frozen: trueand is dropped from tracking;is_same(50) requires pid plus start ticks/exe, so a heartbeat (170) or a stop (217) from another instance retires the old instance instead of recovering it or untracking the current one. The retired frozen instance gets a store-onlystoppedmarker (breadd/src/bugstore.rs:796), which makesbreadbugrefuse a live capture. Tests:frozen_instance_that_dies_is_retired_with_a_gone_event,new_instance_heartbeat_before_the_tick_retires_the_old_one_without_recovery,reused_pid_with_a_new_start_time_is_a_new_instance,a_replaced_pid_is_retired_by_the_tick,an_old_instance_cannot_stop_a_new_one,stop_and_replacement_retire_only_the_old_frozen_instance, real daemonfrozen_dead_process_is_retired_and_next_instance_is_not_recovery; the CLI probe confirms astoppedmarker makes the freeze uncapturable. Old codeif silent <= limit || beat.frozen { continue; }(c4e0277watchdog.rs:147-153) never re-probed a frozen app and attributed any later heartbeat to recovery.Residual notes, not blockers for these five findings
system.os_idexemption uses exact equality (carried over from the earlier review, low risk, the value comes from /etc/os-release).exe_matches_appis a name-based identity (bread<app>/bread<app>d), a reuse and attribution guard for privacy and consent, not an anti-spoofing control. Out of scope here.stored: false(finding 4), so a consumer that ignores the flag sees an event with no stored marker.breadbugitself honors it.No merge, tag, branch deletion or done transition.
feature/breadbug-watchdogis untouched. Card BREAD-46 left in review for coordinator verification.Independent post-merge verification on current origin/main: PASS for all five findings. Full evidence: #45 (comment)
Verified SHA
90cd7b4b6fae1b4dd99e2f9830d42a05e7f3c036(origin/main tip, PR24 merge; repairsd2a588b,875cb2c,1317554). Scratch worktree ~/.worktrees/bread/verify-bread46-90cd7b4, clean tree, primary checkout untouched. No PR to merge: PR24 is already merged, so this verifies merged main.Checks on that SHA: cargo fmt --check pass; cargo clippy --workspace --all-targets --locked -- -D warnings pass with 0 warnings; cargo test --workspace --locked 594 passed / 0 failed over 21 suites, with all four real eu-stack tests executed and nothing skipped. Independent evidence beyond the suites: real eu-stack 0.196 output matches the committed fixtures; real breadbug CLI against a hand-seeded store refuses a wrong start time, a wrong executable, a recovered or stopped marker and a killed target with 0 frames each, and returns 8 module-relative frames with build ids (offsets cross-checked against readelf/nm) for a matching instance.
Findings: 1 PASS (identity + revalidation, comm never consulted), 2 PASS (latest-marker rule, capture not invoked), 3 PASS (module-relative offset + build-id, no addresses, stripped fixture, no memory payload), 4 PASS (exclusions before tracking and publishing, stored:false on storage failure), 5 PASS (dead frozen instance retired with Gone, cross-instance recovery/stop not attributed).
Not blockers, recorded: the os_id composite-equality note from review comment 5169; exe_matches_app is name-based; a genuine storage failure still publishes bread.bug.freeze with stored:false by design; and BREAD-18's blocked-main-loop, suspend/resume, provider rollout and BOS-86 gates stay open and out of scope.