fix: breadcrumbs audit batch (CRUMB-25, 28, 32, 35, 36, 37, 44) #46
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/crumb-audit-batch"
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?
Batch of breadcrumbs audit findings. Each card is one commit on this branch with a test that covers it.
CRUMB-25: detect --apply --json emitted more than one JSON object
detect --apply --json, which printed the apply result and the detection result as separate JSON objects, so a consumer parsing one object failed.f66fca8, src/app.rs).CRUMB-28: connect did not propagate hidden=false to the saved profile
802-11-wireless.hidden=truein the NetworkManager profile, because the rewrite only ran for credential changes or hidden=true.profile_needs_updatecompares the saved hidden flag in both directions, and treats a missing key as false (d4e313a, src/nm.rs).unhiding_a_network_triggers_a_profile_update_and_clears_the_flagandhidden_flag_matching_the_saved_profile_does_not_force_a_rewritein src/nm.rs.CRUMB-32: DNS pin left DHCP DNS on IPv6 when the profile had no ipv6 section
apply_dns. The pin created the ipv4 section but only touched ipv6 if it already existed, so a profile without one kept auto DNS around the pin.method=autowhen creating a missing IP section; existing methods are preserved (reworkd4db5b8). Unpin still only touches existing sections (4b5f017, src/nm.rs).apply_dns_pin_creates_ipv6_section_when_profile_has_none(fails before the fix),apply_dns_pin_v4_only_still_clears_ipv6_auto_dns_on_created_section(fails before the fix),apply_dns_unpin_does_not_invent_ipv6_section,apply_dns_unpin_without_ipv6_section_leaves_no_ipv6_when_pinned_before.CRUMB-35: a blank password answer could wipe a network created concurrently
cmd_add.existingwas computed from the config loaded at startup, before the interactive prompt. The lockedConfig::updateclosure reloads the config, so a network created in the meantime was treated as new and got its password set to none.password_to_write. The prompt stays outside the lock (c3889ea, src/app.rs).blank_prompt_keeps_password_when_network_exists_in_fresh_config,blank_prompt_sets_no_password_for_a_new_network,non_blank_prompt_sets_password_for_existing_and_new_networks,explicit_password_always_replaces_even_when_empty.CRUMB-36: a non-204 HTTP answer with a blocked ping was not treated as a portal
a2e3e2c, src/status.rs).2dff10b).CRUMB-44: a configured profile key with surrounding spaces could not be selected from the bus
set_profilebus command. The payload was trimmed before the config lookup, so a configured key such as" home "could never match. A whitespace-only payload also reportedmissing string "profile", which was misleading.319e473, src/bread_events.rs).exact_profile_key_with_spaces_resolves_to_itself,padded_payload_falls_back_to_trimmed_key,exact_key_wins_over_trimmed_key_when_both_exist,whitespace_only_payload_reports_empty_profile_not_missing_field,absent_or_non_string_payload_reports_missing_field). The integration testset_profile_command_trims_name_before_applyingis renamed toset_profile_command_falls_back_to_trimmed_name_when_applyingand now asserts the new contract. The tests use config fixtures only and do not touch the bus.CRUMB-37: two release workflows published the same tag
.github/workflows/release.ymland.forgejo/workflows/release.ymlboth ran onv*tag pushes, and both wrote/srv/breadway-dl/breadcrumbs/<version>and rangh release upload --clobber. Forgejo is authoritative, since GitHub auto-mirrors from origin.workflow_dispatchonly (e44b642). The Forgejo release path is unchanged.tests/release_workflows.rs, which asserts the GitHub workflow does not trigger on tag pushes and the Forgejo one still triggers onv*.CLAUDE.mdis not tracked in this repo (removed in7a58d4aand listed in.gitignoreas local-only), so there is no tracked file to rewrite. This card's CLAUDE.md part is therefore not applicable here.Verified locally:
cargo fmt --all -- --check,cargo clippy --all-targets -- -D warnings,cargo test --release.Refs CRUMB-25 CRUMB-28 CRUMB-32 CRUMB-35 CRUMB-36 CRUMB-37 CRUMB-44
Review of PR #46 at head
e44b642c3d(base main939d9edc). Head re-read from Forgejo before and after review: unchanged.PR VERDICT: CHANGES REQUESTED (blocking: CRUMB-32; test gap: CRUMB-36). Five cards pass.
Checks on this head: Forgejo "check / check (push)" = success. Independently in a clean worktree: cargo fmt --check rc 0, cargo clippy --all-targets -D warnings rc 0, cargo test rc 0 (113 lib, 54, 56, 2 release_workflows, 0 failures). Existing reviews: none. Branch protection is not readable anonymously, so required-check configuration is unverified.
PER CARD
939d9edc; removed in7a58d4a; listed in .gitignore), so that half is correctly not applicable. Non-blocking hazard: the GitHub workflow still derives VERSION from GITHUB_REF_NAME, so a manual dispatch from a branch writes /srv/breadway-dl/breadcrumbs/ and repoints the "latest" symlink at it. Consider deleting that workflow or guarding the job with if: startsWith(github.ref, 'refs/tags/v').CRUMB-32 (blocking)
Change: apply_dns (src/nm.rs:857-858) now creates an ipv6 section containing only ignore-auto-dns and dns, with no method key. Tests assert the absence of method on purpose (nm.rs:1226 and :1237).
Evidence that this is invalid to NetworkManager:
Consequence: if the hypothetical profile ever does occur, enforce_dns's Update2 fails, the error is only logged ("dns: could not apply DNS setting"), and the v4 pin that worked before this change is lost too. That is a regression in the one case the change targets.
The fake does not catch it: tests/common/fake_nm.rs validate_settings (line 206) checks dns wire types but not that ip sections carry a method, so the tests mirror the code.
Requested: when creating a missing ipv6 section, also set method=auto (as the create path at nm.rs:697-699 does); flip the two assertions to expect method == "auto"; teach validate_settings to reject ipv4/ipv6 sections without method, so a regression fails in the fake.
Does pinning stop v6 auto DNS? With method=auto, per nm-settings docs ipv6.ignore-auto-dns=TRUE ignores RA/DHCPv6 name servers and search domains, and only dns/dns-search are used; so yes for this profile's own DNS. Not covered: dns-priority is never set, so other active connections (VPN, second NIC) can still contribute DNS; the card does not claim that. Separate pre-existing issue, not for this PR: with ipv6.method ignore, disabled or link-local, libnm rejects a non-empty ipv6.dns, so a pin with a v6 server on such a profile fails the same way (reproduced in-process).
Confidence: high that methodless ipv6 is rejected (source plus libnm 1.46.0 run); medium-high that the no-ipv6-section case rarely occurs live. Only provable on a live system: the running daemon's behavior (same libnm code, but I did not run NetworkManager), real RA/RDNSS and DHCPv6 DNS being dropped after Reapply, resulting resolv.conf or systemd-resolved state, and interaction with Tailscale DNS. None of that was touched.
Scope: seven files, all attributable to the seven cards; no scope creep. Added lines and commit messages contain no em or en dashes and no attribution.
Not merged. Cards not moved.
NetworkManager rejects an ipv4 or ipv6 section that has no method ("ipv6.method: property is missing"), so the DNS pin's created sections made verify and Update2 fail and the pin never took effect. Created sections now carry method = auto, the default NM applies to a profile with no section at all. Existing sections keep their method. The fake NM now rejects a present ipv4/ipv6 section without a method, so a client that builds an invalid shape fails in tests the way it would against real NetworkManager.Rework for the independent review (comment 5672), on the same branch.
d4db5b8fix(nm): give a created ipv4/ipv6 section method auto (CRUMB-32). The DNS pin now creates a missing ipv6 section withmethod = auto. The same missing-method path existed for ipv4, so that is fixed too. Existing sections keep their method. The assertions at the two previously flagged spots now expectauto. The fake NM'svalidate_settingsrejects a present ipv4/ipv6 section with nomethod, with unit tests for reject and accept.2dff10btest(status): cover the non-204 HTTP answer with a blocked ping (CRUMB-36). Tests ofconnectivity()through FakeRunner: 403/404 with ping blocked gives Portal, 000/500 with ping blocked gives NoNet, 403 with ping working gives Online. Breaking the portal branch toelse if false && answeredfails the 403/404 test; restored afterwards.Checks on
2dff10b: cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test --release all pass.Independent whole-head review: PASS. Awaiting coordinator merge, not merged by this reviewer.
Reviewed exact head
2dff10b59cagainst base939d9edcfe. Both re-read unchanged at completion; mergeable=true, merged=false. Read all seven live cards, comments 5672/5679, linked CRUMB-33 and transitive CRUMB-29. Both prerequisite implementation heads are ancestors. Complete eight-file diff reviewed; PR and changed-file lists exhausted. No competing PR for these cards.Per-card acceptance and evidence:
7a58d4a, so no stale tracked file remains. No release dispatch or tags used.Independent checks in clean isolated clone /home/breadway/.worktrees/breadcrumbs/review-46-2dff-independent, using /home/breadway/.local/bin/cargo wrapper: fmt --all -- --check; clippy --locked --all-targets -- -D warnings; test --locked; test --locked --release. All exit 0. Each full test profile: 115 library + 57 CLI + 62 flow/watch + 2 workflow tests, zero failures. Review-only CLI fixture separately passed. Final git diff/status clean. Logs and review fixture sources: /tmp/opencode/crumb46-2dff-evidence/.
CI/review gates: exact-head check / check (push) success, run131/job0. Read check.yml and both hosts workflows. The GitHub mirror workflow does not run for a Forgejo-only PR or this fix-branch push, so no missing GitHub branch check is expected. Authenticated MCP list_branches reports main protected=false, enable_status_check=false, status_check_contexts=[], required_approvals=0. This resolves the earlier anonymous branch_protections 401 without credential extraction. Public formal reviews API returns []; both prior review comments read and requested repairs verified. No approval submitted on behalf of the author.
Nonblocking separate findings deduplicated and filed: CRUMB-48, pre-existing nonempty IPv6 DNS rejected with ignore/disabled/link-local, independently reproduced with libnm; CRUMB-49, manual GitHub release dispatch can publish a branch as latest. Neither fixed. Minor PR-body drift: CRUMB-32 paragraph still says no method; comment 5679 correctly documents auto.
These cards describe source-level behavior, not acceptance measurements on real Wi-Fi or DHCPv6. Fixtures plus real libnm validation satisfy that scope. No claim of live RA/DHCPv6, resolver, Tailscale or deployment verification. No live networking/desktop operations, implementation, commits, pushes, tags or card moves. Coordinator must re-read head/base/checks/reviews before merge, then verify integration; this review does not mark any card done.
Fresh independent review: PASS for exact head
2dff10b59cagainst base939d9edcfe. This is a new review, not adoption of comment 5689. No merge or card transitions performed.Read all seven live cards and their comments, linked CRUMB-33 and transitive CRUMB-29, prior requests 5672 and rework 5679. Both prerequisite heads are ancestors of the reviewed head. Read complete 922-line actual diff, all eight changed files, repository instructions and workflow definitions. PR and file list pagination exhausted; no competing PR for these seven cards.
Acceptance mapping:
d4db5b8resolves request 5672.2dff10b. Interpretation: unexpected 2xx-4xx answers are portal candidates after failed ping, not indiscriminately every non-204 answer; existing classic portal handling remains.Fresh independent checks using /home/breadway/.local/bin/cargo wrapper in new isolated clone /home/breadway/.worktrees/breadcrumbs/review-46-sol-restarted:
Each full suite: 115 library + 57 CLI + 62 flow/watch + 2 workflow tests = 236, zero failures. Two independent appended review fixtures passed separately, then original files restored byte-for-byte. Final git diff and status clean. Logs, independent fixture sources, password race script and libnm script: /tmp/opencode/pr46-sol-restarted/. Earlier agents worktrees untouched.
Live gates re-read after testing: head and base unchanged, open=true, mergeable=true, merged=false. Exact-head check / check (push) success, run131/job0, Successful in 5m37s. Read check.yml: this is the expected fix-branch job; GitHub mirror CI does not trigger for a Forgejo-only PR/fix push. Authenticated Forgejo MCP list_branches: main protected=false, enable_status_check=false, status_check_contexts=[], required_approvals=0. Read-only public formal reviews endpoint returned [] (no formal reviews); all three PR discussion comments read and earlier concrete change requests verified resolved. No credential extraction or formal self-approval.
Nonblocking documentation drift: PR body still describes DNS-only methodless ipv6; actual reviewed code and rework 5679 correctly use auto. Known separate CRUMB-48 and CRUMB-49 remain out of scope and were not duplicated or fixed. No new blocking defect found.
Scope is source-level acceptance supported by isolated behavior tests and real in-process libnm validation, not a claim of live Wi-Fi, DHCPv6, resolver, Tailscale or deployment verification. No live network/desktop operations, implementation, commits, pushes, tags, release/version changes or board moves. Awaiting coordinator fresh merge gates and integrated verification; no card is marked done by this review.