diff --git a/factory/LEDGER.md b/factory/LEDGER.md index 097e64e..dd8adb0 100644 --- a/factory/LEDGER.md +++ b/factory/LEDGER.md @@ -13,3 +13,4 @@ number would misrepresent cost. | when (UTC) | run | status | issue | commits | CI | elapsed | tokens | outcome | |---|---|---|---|---|---|---|---|---| | 2026-08-01T05:01:53Z | issue-6 | done | #6 | 1 commits | failure | 414m | out 127,776 - cache-read 14,918,374 - agents 468,804 total across 11 spawns | bounces=0 breaker_verdict=no-defect-found findings_deferred=4 findings_fixed=1 guard_tests=1 laps=2 reviewer_blocking=0 seraph_unsure=0 slices=3 tests_added=3 | +| 2026-09-06T11:22:37Z | bridge-totals-flake | done | - | 3 commits | success | 173m | out 89,916 - cache-read 16,338,036 - agents 341,392 total across 7 spawns | breaker_verdict=no-defect-found guard_tests=2 laps=1 seraph_unsure=2 slices=2 tests_added=5 | diff --git a/factory/runs/20260906-112236-bridge-totals-flake.json b/factory/runs/20260906-112236-bridge-totals-flake.json new file mode 100644 index 0000000..bd36c31 --- /dev/null +++ b/factory/runs/20260906-112236-bridge-totals-flake.json @@ -0,0 +1,437 @@ +{ + "recorded_at": "2026-09-06T11:22:37Z", + "repo": "bridge-totals-flake", + "branch": "fix/bridge-totals-flake-2", + "base": "718f615d03ce6d8fa6bc749daaaee17a1e2816ae", + "window_start": "2026-08-14T21:35:07+10:00", + "head": "6b7736527c8eddb5583f724d54345e3f939c09b7", + "issue": {}, + "commits": [ + { + "sha": "6b7736527c8eddb5583f724d54345e3f939c09b7", + "subject": "Read the Linux default stance through one testable seam" + }, + { + "sha": "3de3c106756f9e4fbdd15b61ce970f4befd6371c", + "subject": "Satisfy clippy 1.98's chunks_exact_to_as_chunks lint" + }, + { + "sha": "752c237f7caa8e892273f955665e83c0d70f843a", + "subject": "Inject the default-inbound stance into to_result instead of reading the host inside it" + } + ], + "commit_count": 3, + "diffstat": "3 files changed, 272 insertions(+), 6 deletions(-)", + "ci": { + "conclusion": "success", + "displayTitle": "linux/bridge: inject the default-inbound stance into to_result", + "status": "completed", + "url": "https://github.com/ghostpsalm/Firebreak/actions/runs/34029917653" + }, + "tokens": { + "available": true, + "scoped": true, + "parent_thread": { + "input_tokens": 314, + "output_tokens": 89916, + "cache_read_input_tokens": 16338036, + "cache_creation_input_tokens": 364214 + }, + "subagents": { + "input_tokens": 0, + "output_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation_input_tokens": 0 + }, + "subagent_spawns": 7, + "by_agent": { + "breaker": { + "spawns": 1, + "output_tokens": 0, + "cache_read_input_tokens": 0, + "total_tokens": 68172, + "duration_ms": 631879, + "models": [] + }, + "seraph": { + "spawns": 2, + "output_tokens": 0, + "cache_read_input_tokens": 0, + "total_tokens": 81820, + "duration_ms": 860634, + "models": [] + }, + "architect": { + "spawns": 1, + "output_tokens": 0, + "cache_read_input_tokens": 0, + "total_tokens": 55324, + "duration_ms": 132318, + "models": [] + }, + "oracle": { + "spawns": 2, + "output_tokens": 0, + "cache_read_input_tokens": 0, + "total_tokens": 96792, + "duration_ms": 808015, + "models": [] + }, + "builder": { + "spawns": 1, + "output_tokens": 0, + "cache_read_input_tokens": 0, + "total_tokens": 39284, + "duration_ms": 248633, + "models": [] + } + }, + "usage_blocks": 157, + "skipped_no_timestamp": 0, + "transcripts": [ + { + "file": "4b0ed7d8-8669-4304-8e30-3d5348f30d37.jsonl", + "usage_blocks": 157 + } + ], + "agent_wall_clock_ms": 2681479, + "first_activity": "2026-09-06T08:29:42.320000+00:00", + "last_activity": "2026-09-06T11:22:26.982000+00:00", + "notification_tokens": 341392, + "attribution": "this session, by CLAUDE_CODE_SESSION_ID", + "suspect": false + }, + "asks": [], + "fence_decisions": { + "ask: This edits a version number.": 880, + "ask: This would publish a GitHub release, which ships to real devices.": 302, + "ask: This would publish a crate to a registry, which ships to real devices.": 301, + "ask: This posts to the release endpoint, which ships an update to real devices.": 299, + "block: recursive/forced delete": 2066, + "block: editing the deployed factory directly": 2114, + "block: writing to a file the harness owns": 299, + "block: touching a secret/credential file": 1483, + "block: sending a secret/credential file to a network client": 8949, + "block: sending ~/.claude.json to a network client": 598, + "block: force push (never rewrite published history)": 303, + "block: editing an existing migration": 298, + "block: malformed hook input": 10811, + "block: unbounded read of a 600-line source file": 1153, + "block: writing an owner approval from inside a factory worktree": 1776, + "ask: recording an owner decision: This writes into the owner-decision channel, which is the pro": 1280, + "block: writing an owner approval from a session with no recorded cwd": 296, + "block: running an owner-decision command from inside a factory worktree": 1523, + "block: running an owner-decision command from a session with no recorded cwd": 296, + "block: hard reset discards work": 287, + "block: unbounded read of a 1035-line source file": 267, + "block: unbounded read of a 12285-line source file": 271, + "block: The authoritative full gate is a control-plane operation and this process is not the contr": 848, + "block: a factory worker role executing the authoritative full gate": 1217, + "block: the driver's state file could not be read (/tmp/tmp5gg6zkro/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpcjn56q2t/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpm5jse129/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpazfvzb6b/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp_wpdqa42/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpnbouq2sj/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpu4z5vntz/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp3km13wp5/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpbttxmbb_/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpkfv3j_n0/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmprcdeo7eq/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp3scih68m/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp0snfov7l/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpwlayjgjc/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpro0i6r6k/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpp12qom32/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp1h_l3ms0/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpt7qhq560/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp390o34w4/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp8vr_6sw5/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpmmg2pt54/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpgg2igmgp/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpet6994ay/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmphgs7j87b/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpfp1xx48y/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpw1xpp6ux/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmphvo7uflw/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpv1lrjz_n/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpims6l778/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpx69jrzes/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmparmwdhx2/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpjj5hfgxi/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp63eh_k_q/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpe9nn0b2n/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpmzua66x4/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpzr6vlnfw/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp30seu3v5/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpbuwfufi0/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp9mrxl6pd/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpwvzkh7jl/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpt7823kw5/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmppe2vygxx/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpce8v1uxa/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp3ycp3_cx/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmprtv7u7fl/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp4oxt6061/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmphxr3fh5n/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpw_9q4roi/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpr9pja8hx/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpvbr3j23g/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpwvlufc3o/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpagvr6hrp/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp_77hd6q8/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpxqhbou1m/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpdldu5xup/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp_wno1ncl/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpvexlywd_/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpuebyatwy/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp77stuxq3/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp9231mfh9/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmppm_bz6_1/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp6qop2kya/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpyfr00zb0/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp5_tdwga5/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpme0wp78q/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpakn0wvcg/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpt2jtro04/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp_hmkeka1/factory/IMPLEMENT-STATE.json e": 1, + "block: destructive SQL DDL": 10, + "block: the driver's state file could not be read (/tmp/tmpe1ohgxdx/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp22fx_hfu/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpzzlqrv2o/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmphcdjtmoz/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmphv1xrs67/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmplzm4xdha/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmptf4oyf05/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpt2s20j_6/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpae49ykve/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpstzvkczl/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpzdmjstpz/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpw1c5ceyl/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp6zkzmvhw/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpfaa3a_bm/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpnc2kdyb2/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp0spr6ad_/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp4kyj78zj/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpnp4l2qf9/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp4b_xllxg/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpz5v7m5b8/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpvpv8a2cq/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmprh9bujts/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpkdkvxk0c/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpbqgirm8c/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpt4vp91_g/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmprew2f21q/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpm3ox33ll/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmprhxi4zp1/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpxd9dvrcr/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmppv_7xjl3/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmps5g7avuy/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpbuigym2v/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp402pp_7j/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpgd2by5n4/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp4jlxny6q/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpvp7pn0ka/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpafx2q9vu/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp1zih_684/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpwlmahcob/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp5kun87ou/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmphvq5u35d/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpdga36d7g/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp6lwrjx36/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpwjczxil7/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpz7rikbue/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp3o8exemr/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmprwkkgb6o/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp1h6qrpk7/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpimqirz6p/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpjogmbdh5/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp57a12zkr/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpx04a5rpb/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpc8u039db/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp_x799cnt/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpk6ndxdnm/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpvakwd8e9/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp48kz1iud/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpuwoyztuj/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp7lfhn7ng/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpoy_ynvvl/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpx8frt559/factory/IMPLEMENT-STATE.json e": 1, + "block: piping remote content straight to a shell": 2, + "block: the driver's state file could not be read (/tmp/tmp35adk889/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpa_2g7loh/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp0sldkmcw/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp_tmt5pgp/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp167no1za/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpa_x40fiu/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpzup4aqih/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp7yxxsuj8/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpp3pmmhpk/factory/IMPLEMENT-STATE.json e": 1, + "block: a model-tool write to Factory acceptance authority state": 3049, + "block: the driver's state file could not be read (/tmp/tmpxzssbp_s/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpzvhjpkwf/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp1i_khox8/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpfvuu6uyy/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpqsyq8b0w/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp26p1kh8c/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpgnrhn58t/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpacdh9v8q/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpe_i5_vdy/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpsn4w7aoq/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpke1rwfqu/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpue7078ds/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpov3ry77u/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmphdwsunuh/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmply5zc6sw/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpv8jsyiyh/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp2gka79ys/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpc86ddqts/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpt3cbq7vs/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp6s1kefuf/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmph486md9r/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpmq30lgn0/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp765cr4s1/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpzm7f62cb/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp8_6xpqpa/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpf9vlorgy/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmprxyz05q0/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp3p47y7u5/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp50aajdeb/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmp_1q7ru_c/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpo27mcpj2/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpa_rdnhfk/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpqhen9tcz/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpk9arwqgm/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpm587aduw/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpppcjm6kv/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpkqctyc60/factory/IMPLEMENT-STATE.json e": 1, + "block: git clean deletes untracked files": 1, + "block: the driver's state file could not be read (/tmp/tmpsuzxwaib/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmpnf61rih1/factory/IMPLEMENT-STATE.json e": 1, + "block: the root could not be established as controller-bound: /home/kogies/.factory/worktrees/rev": 9, + "block: the driver's state file could not be read (/tmp/tmp38aqe4nt/factory/IMPLEMENT-STATE.json e": 1, + "block: the driver's state file could not be read (/tmp/tmphba_mk15/factory/IMPLEMENT-STATE.json e": 1, + "block: the root could not be established as controller-bound: the git relationship of /tmp/tmpyre": 1, + "block: the root could not be established as controller-bound: the git relationship of /tmp/tmpx3f": 1, + "block: the root could not be established as controller-bound: the git relationship of /tmp/tmppbk": 1, + "block: the root could not be established as controller-bound: the git relationship of /tmp/tmp5hg": 1, + "block: the root could not be established as controller-bound: the git relationship of /tmp/tmpyb1": 1, + "block: the root could not be established as controller-bound: the git relationship of /tmp/tmp0yq": 1, + "block: the driver's state file could not be read (/tmp/tmpdck885_i/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp36t5f18y/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp2noiv3ez/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpd9k6y4vn/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpxpaetutx/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp1sjcrhyi/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpjgrhs4lt/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpl0acqpdp/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpo4f620on/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpdoccefq2/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp9yjy6lv1/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpx3ldqan7/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpbhxj3z4s/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpkqbsfkri/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpxm_rhji1/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpnjqhmqnv/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp_xb0xmse/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp9q74mvdj/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp745ph1su/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpgnhj51kv/line/factory/IMPLEMENT-STATE.j": 1, + "block: the root could not be established as controller-bound: /home/kogies/SpiritBox/docs/researc": 2, + "block: the driver's state file could not be read (/tmp/tmpq8iz0a2f/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp4yzcogyr/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp2evq6h6p/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpglzyyvfc/line/factory/IMPLEMENT-STATE.j": 1, + "block: the root could not be established as controller-bound: /home/kogies/ars-aifactory/.claude-": 7, + "block: the driver's state file could not be read (/tmp/tmpt1rynl70/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpueawqe3r/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpjdzvf0wh/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp2wpzex4u/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp27jw3vz5/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpnu7zgwz7/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpk18vbnm3/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpu_eyehip/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp9va7uewk/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmph3j3piu7/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpznuuwmni/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp3ynsim3w/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpwn4n5k6n/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp7cvj4q3o/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpuzd54od3/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpizo1tkqz/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpeltwx5v3/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpexgkfq3r/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp1l88xicj/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpuko3qsv_/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpksufxi94/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpejj6kv5v/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp3i_k31sz/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpswwcxycf/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp03qm12b5/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp03z3e7vm/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpv22ugnnx/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpl9_13cr7/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmptk1ikwl2/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp6y0b_1qc/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp9o2op_z1/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpdmir2ksg/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpyzuu9wns/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp71znqk94/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmposfnms80/line/factory/IMPLEMENT-STATE.j": 1, + "block: the root could not be established as controller-bound: the git relationship of /home/kogie": 4, + "block: unbounded read of a 910-line source file": 1, + "block: unbounded read of a 12178-line source file": 1, + "block: the driver's state file could not be read (/tmp/tmp0u39glmr/factory/IMPLEMENT-STATE.json e": 1, + "block: writing to ~/.claude.json, which holds a live credential": 1, + "block: the driver's state file could not be read (/tmp/tmpc6mkdm8v/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpk2vv1t3t/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpxh3k56hp/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpr0i4922z/line/factory/IMPLEMENT-STATE.j": 1, + "block: the root could not be established as controller-bound: /home/kogies/.factory/worktrees/ars": 12, + "block: the root could not be established as controller-bound: the git relationship of /tmp/tmp.aK": 6, + "block: unbounded read of a 573-line source file": 1, + "block: the driver's state file could not be read (/tmp/tmpe7spai4w/line/factory/IMPLEMENT-STATE.j": 1, + "block: the root could not be established as controller-bound: /home/kogies/ars-collatz/collatz_le": 1, + "block: unbounded read of a 597-line source file": 1, + "block: the root could not be established as controller-bound: /home/kogies/.factory/worktrees/har": 54, + "block: the root could not be established as controller-bound: the git relationship of /tmp/claude": 6, + "block: the driver's state file could not be read (/tmp/tmpxyj0n9ag/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpomp2k1rd/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpv7yvh0lp/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpzlgakpi2/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp8o8rx1v6/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpikv8fio0/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpntzwzmio/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmppr8ug0jx/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp40ck0ihe/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp5ns_rbv2/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp23o4er1z/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmp2raziv5d/line/factory/IMPLEMENT-STATE.j": 1, + "block: unbounded read of a 669-line source file": 1, + "block: the driver's state file could not be read (/tmp/tmpalybdgtw/line/factory/IMPLEMENT-STATE.j": 1, + "block: unbounded read of a 1213-line source file": 1, + "block: unbounded read of a 1232-line source file": 3, + "block: the driver's state file could not be read (/tmp/tmpgj7m9hv1/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpdqoyj99_/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpdaqla1b_/line/factory/IMPLEMENT-STATE.j": 1, + "block: writing into the ~/.claude root directory": 1, + "block: unbounded read of a 1309-line source file": 1, + "block: unbounded read of a 13039-line source file": 1, + "block: unbounded read of a 1326-line source file": 5, + "block: unbounded read of a 13294-line source file": 5, + "block: the driver's state file could not be read (/tmp/tmpbpl59ww5/line/factory/IMPLEMENT-STATE.j": 1, + "block: the driver's state file could not be read (/tmp/tmpsubmfzpj/line/factory/IMPLEMENT-STATE.j": 1 + }, + "status": "done", + "note": "Revived a 4-week-old branch that was the fix for main's CI, red since 2026-08-10 on linux::bridge::tests::totals_reflect_everything_counted_so_far. Rebased onto main (clean), pushed under a new name rather than force-pushing the old one. Second commit satisfies clippy 1.98's chunks_exact_to_as_chunks, not reproducible locally (host clippy 0.1.96). Third commit closes a coverage hole breaker found by mutation: the stance read had moved to three untested call sites and replacing all three with None left the gate at 224 passed. Now one seam; the same mutation fails the gate. Authorship for the third commit: the oracle agent wrote every test in src/linux/default_policy.rs and src/linux/bridge.rs and a separate builder agent wrote the implementation and was forbidden from touching an assertion; the oracle later ran cargo fmt on its own helper. Both seraph UNSUREs were attribution-only, never substance. Residue accepted and out of scope: nothing tests that to_result names Sources::system(), and main.rs:655 print_linux_report still reads the stance unguarded - worth its own issue.", + "outcome": { + "slices": "2", + "laps": "1", + "tests_added": "5", + "seraph_unsure": "2", + "breaker_verdict": "no-defect-found", + "guard_tests": "2" + }, + "model_verdicts": [], + "model_mismatches": [] +} diff --git a/src/filter_map.rs b/src/filter_map.rs index b3d45f2..049a6e1 100644 --- a/src/filter_map.rs +++ b/src/filter_map.rs @@ -150,8 +150,10 @@ pub fn enumerate_filters() -> Result> { #[cfg(windows)] fn decode_provider_data(data: &[u8]) -> (String, String) { let utf16: Vec = data - .chunks_exact(2) - .map(|c| u16::from_le_bytes([c[0], c[1]])) + .as_chunks::<2>() + .0 + .iter() + .map(|c| u16::from_le_bytes(*c)) .collect(); let text: String = String::from_utf16_lossy(&utf16) .chars() diff --git a/src/linux/bridge.rs b/src/linux/bridge.rs index 3e862a0..7eb7391 100644 --- a/src/linux/bridge.rs +++ b/src/linux/bridge.rs @@ -118,6 +118,34 @@ fn to_result( report: super::Report, collecting: bool, reviewed: &Reviewed, +) -> AnalysisResult { + to_result_from( + backend, + report, + collecting, + reviewed, + &super::default_policy::Sources::system(), + ) +} + +/// As [`to_result`], reading the stance from the supplied sources. +fn to_result_from( + backend: super::Backend, + report: super::Report, + collecting: bool, + reviewed: &Reviewed, + sources: &super::default_policy::Sources, +) -> AnalysisResult { + let stance = super::default_policy::read_from(backend, sources); + fold(backend, report, collecting, reviewed, stance) +} + +fn fold( + backend: super::Backend, + report: super::Report, + collecting: bool, + reviewed: &Reviewed, + stance: Option, ) -> AnalysisResult { let measured: i64 = report.rows.iter().filter_map(|r| r.hits).sum(); let unmeasurable = report.unmeasurable.len() as u64; @@ -130,7 +158,6 @@ fn to_result( ); } - let stance = super::default_policy::read(backend); let mut rows = report .rows .into_iter() @@ -340,11 +367,12 @@ mod tests { note: None, unmeasurable: vec![("b".into(), "no counter".into())], }; - let result = to_result( + let result = fold( super::super::Backend::Ufw, report, true, &Default::default(), + None, ); assert_eq!(result.ctx.unmatched_events, 1); assert!( @@ -363,13 +391,152 @@ mod tests { note: None, unmeasurable: vec![], }; - let result = to_result( + let result = fold( super::super::Backend::Ufw, report, true, &Default::default(), + None, ); assert_eq!(result.ctx.events_processed, 10); assert_eq!(result.rows.len(), 2); + assert!(result.ctx.default_inbound.is_none()); + } + + #[test] + fn a_known_default_stance_appends_the_synthetic_row() { + let report = super::super::Report { + rows: vec![row("a", "Allow", Some(4))], + note: None, + unmeasurable: vec![], + }; + let result = fold( + super::super::Backend::Ufw, + report, + true, + &Default::default(), + Some(super::super::default_policy::DefaultInbound { + verdict: crate::default_policy::Verdict::Drop, + detail: "DEFAULT_INPUT_POLICY=\"DROP\"".into(), + }), + ); + // The synthetic catch-all rides along with the user's rules, and the + // totals still count only what the backend actually measured. + assert_eq!(result.rows.len(), 2); + assert_eq!(result.ctx.events_processed, 4); + assert!(result.ctx.default_inbound.is_some()); + // …and it is recognisably *the catch-all*, not merely an extra row. + // Everything that keeps it out of plans, quick actions, the zero-hit + // list, the CSV export and the rule count keys off these four facts. + assert_default_policy_row(&result.rows[1], "Block"); + } + + /// The identity the rest of the app relies on to treat this row as not a + /// rule. Shared so the injected-source tests and the folded-stance test + /// cannot drift apart on what "the catch-all row" means. + fn assert_default_policy_row(r: &RuleRow, action: &str) { + assert!(r.is_default_policy(), "source must be DefaultPolicy"); + assert!( + r.rule + .name + .starts_with(crate::default_policy::ROW_ID_PREFIX), + "name must carry the synthetic prefix, got {:?}", + r.rule.name + ); + assert_eq!( + r.rule.policy_source_type.as_deref(), + Some(crate::model::RuleInfo::SOURCE_TYPE_DEFAULT) + ); + assert!( + !r.hits_known, + "counters sit after the verdict, so zero here would read as \ + \"measured, never matched\" and list the host's default deny as \ + a disable candidate" + ); + assert_eq!(r.rule.action, action); + } + + /// A ufw defaults file at a path nothing else in the run touches. The + /// seam exists precisely so no test reads the host's real one. + fn ufw_sources( + tag: &str, + body: &str, + ) -> (std::path::PathBuf, super::super::default_policy::Sources) { + let dir = std::env::temp_dir().join(format!("fb-bridge-{tag}-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let file = dir.join("ufw.conf"); + std::fs::write(&file, body).unwrap(); + ( + dir, + super::super::default_policy::Sources { + ufw_defaults: vec![file], + }, + ) + } + + fn one_rule_report() -> super::super::Report { + super::super::Report { + rows: vec![row("a", "Allow", Some(4))], + note: None, + unmeasurable: vec![], + } + } + + /// The three callers each read the stance themselves and handed it in, + /// so a caller that quietly stopped passing it dropped the default-inbound + /// row from the header, the rule table and the socket list with nothing + /// failing. Reading it *inside* this function puts file → parse → row → + /// header under one assertion the callers cannot bypass. + #[test] + fn the_ufw_default_stance_is_read_through_the_supplied_sources() { + let (dir, sources) = ufw_sources( + "defpol-drop", + "# /etc/default/ufw\nIPV6=yes\nDEFAULT_INPUT_POLICY=\"DROP\"\n\ + DEFAULT_OUTPUT_POLICY=\"ACCEPT\"\n", + ); + + let result = to_result_from( + super::super::Backend::Ufw, + one_rule_report(), + true, + &Default::default(), + &sources, + ); + + assert_eq!(result.rows.len(), 2, "the user's rule plus the catch-all"); + assert_default_policy_row(&result.rows[1], "Block"); + assert!( + result.ctx.default_inbound.is_some(), + "the evidence header carries the same verdict as the row" + ); + assert_eq!( + result.ctx.events_processed, 4, + "the catch-all is not measured and must contribute no hits" + ); + + let _ = std::fs::remove_dir_all(&dir); + } + + /// Unreadable is reported as unknown, never as a deny — so nothing is + /// appended at all rather than a row claiming a block that is not there. + #[test] + fn an_unreadable_ufw_default_appends_no_row_at_all() { + let (dir, sources) = ufw_sources( + "defpol-nokey", + "IPV6=yes\nDEFAULT_OUTPUT_POLICY=\"ACCEPT\"\n", + ); + + let result = to_result_from( + super::super::Backend::Ufw, + one_rule_report(), + true, + &Default::default(), + &sources, + ); + + assert_eq!(result.rows.len(), 1); + assert!(result.ctx.default_inbound.is_none()); + + let _ = std::fs::remove_dir_all(&dir); } } diff --git a/src/linux/default_policy.rs b/src/linux/default_policy.rs index ac762ef..04c11bb 100644 --- a/src/linux/default_policy.rs +++ b/src/linux/default_policy.rs @@ -26,15 +26,43 @@ use serde_json::Value; pub use crate::default_policy::{DefaultInbound, Verdict}; +/// Where the verdict is read from. Only ufw's is a file the caller can +/// point elsewhere; the other two backends are asked for it directly. +pub struct Sources { + /// ufw's defaults files, in the order they are tried. + pub ufw_defaults: Vec, +} + +impl Sources { + /// The host's real files. + pub fn system() -> Self { + Self { + ufw_defaults: vec![ + std::path::PathBuf::from("/etc/default/ufw"), + std::path::PathBuf::from("/etc/ufw/ufw.conf"), + ], + } + } +} + /// Read the active backend's default inbound verdict. `None` means it could /// not be read — which is reported as unknown, never as a deny. pub fn read(backend: super::Backend) -> Option { + read_from(backend, &Sources::system()) +} + +/// As [`read`], against the supplied sources. **Only the ufw branch reads +/// them**: firewalld and raw nftables shell out to the live host and ignore +/// `sources` entirely, so this seam does not make all three backends +/// testable — just the one whose evidence is a file. +pub fn read_from(backend: super::Backend, sources: &Sources) -> Option { match backend { super::Backend::Firewalld => { parse_firewalld_input_chain(&super::firewalld::input_chain_text().ok()?) } super::Backend::Ufw => { - let text = ["/etc/default/ufw", "/etc/ufw/ufw.conf"] + let text = sources + .ufw_defaults .iter() .find_map(|p| std::fs::read_to_string(p).ok())?; parse_ufw_defaults(&text) @@ -244,4 +272,73 @@ mod tests { assert!(parse_nft_input_policy(&json).is_none()); assert!(parse_ufw_defaults("IPV6=yes\n").is_none()); } + + /// A directory nothing else in the run shares, so a parallel `cargo + /// test` cannot collide with it. + fn scratch(tag: &str) -> std::path::PathBuf { + let dir = std::env::temp_dir().join(format!("fb-defpol-{tag}-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + dir + } + + /// ufw's defaults live in one of two files and the first need not exist, + /// so the reader walks the list. Stopping at the first missing path would + /// report a host that drops unmatched inbound as *unknown* — and unknown + /// is what the header shows when it cannot say whether a listening socket + /// is exposed. + /// + /// The point of the absent first entry is that it makes the fallback + /// ordering, not just the parser, the thing under test. + #[test] + fn a_missing_first_ufw_defaults_file_falls_through_to_the_next() { + let dir = scratch("fallback"); + let present = dir.join("ufw.conf"); + std::fs::write( + &present, + "# /etc/default/ufw\nIPV6=yes\nDEFAULT_INPUT_POLICY=\"DROP\"\n\ + DEFAULT_OUTPUT_POLICY=\"ACCEPT\"\n", + ) + .unwrap(); + let sources = Sources { + ufw_defaults: vec![dir.join("absent-on-purpose"), present], + }; + + let d = read_from(crate::linux::Backend::Ufw, &sources).expect("a verdict"); + + assert_eq!(d.verdict, Verdict::Drop); + // the *output* policy in the same file is accept; reading the wrong + // key would report an open host + assert!(d.detail.contains("DROP"), "{}", d.detail); + + let _ = std::fs::remove_dir_all(&dir); + } + + /// A defaults file that exists but names no input policy is unreadable, + /// not a deny. Guessing one would tell someone an exposed port is shut. + #[test] + fn a_defaults_file_without_the_key_is_unknown_not_a_deny() { + let dir = scratch("nokey"); + let file = dir.join("ufw.conf"); + std::fs::write(&file, "IPV6=yes\nDEFAULT_OUTPUT_POLICY=\"ACCEPT\"\n").unwrap(); + let sources = Sources { + ufw_defaults: vec![file], + }; + + assert!(read_from(crate::linux::Backend::Ufw, &sources).is_none()); + + let _ = std::fs::remove_dir_all(&dir); + } + + /// The seam exists so tests can point somewhere else; production must + /// still point at the host's real files, in this order. + #[test] + fn the_system_sources_are_ufws_two_defaults_files() { + assert_eq!( + Sources::system().ufw_defaults, + vec![ + std::path::PathBuf::from("/etc/default/ufw"), + std::path::PathBuf::from("/etc/ufw/ufw.conf"), + ] + ); + } }