Implements #70: fabric drift reports card-vs-source edge divergence as a fourth finding class
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 35/100
Hướng nghiên cứu
Read the fw fabric drift implementation and enrich.py, especially compute_forward_edges, detect_raw_edges() and report_divergence(). Run the drift and divergence tests, including the direct --report-divergence count cross-check. Done means drift reports divergent edges with the specified truncation and summary, handles detector failures as UNKNOWN, and does not gate on findings.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Context
- Framework version: 1.6.258
- Project: 050-email-archive (shared-tooling mode)
- Reported: 2026-09-25T08:39:47Z
Description
Follow-up to #70, which this implements. Also depends on the detector fix in #71.
The blindness
fw fabric drift has three finding sections and all three start from what the cards say:
- unregistered — is there a file with no card?
- orphaned — is there a card with no file?
- stale — does an edge a card declares still resolve on disk?
None of them ever asks the converse: does the declared edge set still match what the source
actually imports? So a card can name one edge out of ten and drift reports clean forever.
What that cost on the consumer project (#70): a commit moved four services into legacy/ as pure
renames and updated no require path. Three archive import entry points threw MODULE_NOT_FOUND
for 185 days. All three had cards with edges — none naming the importer that broke — and
every declared target still existed, so stale stayed 0 and drift stayed green the whole time.
The fix — a fourth section, Divergent cards (card vs source)
Divergent cards (card vs source):
! scripts/some-script.js → missing: src/database/models/index.js
… 160 more (full list: bin/fw fabric drift --divergence-full)
(informational: 14 card edge(s) the detectors cannot see; 37 detected import(s) whose target has no card)
Summary: unregistered: 0, orphaned: 0, stale: 0, divergent: 180 (193 edge(s))
Three design choices worth stating, since #70 left them open:
- Direction is split. The headline
divergentcount is only the dangerous direction —
the source imports a target the card does not declare. The reverse (a card declares an edge
the detectors did not find) is usually a legitimately hand-authored edge, so it is reported as
an informational count and deliberately not folded into the headline number. Edge types the
detectors cannot emit (writes,writes_data,readsof a runtime artifact) are excluded
entirely rather than counted as noise. - It reports, it does not gate.
do_driftstill returns 0 on findings, exactly as the
existing three sections do. On a mature project this section opens at 180 findings, and a gate
that starts red is a gate people learn to ignore. Whether it should later become a ratchet on a
coverage percentage is left as a policy decision, not baked in here. - The per-card list is truncated to 20 with
--divergence-fullto un-truncate. The counts are
always exact; only the enumeration is capped, and it says so. At 180 findings an unabridged list
buries sections 1-3.
Detection is delegated, not duplicated
enrich.py gained --report-divergence (read-only) and report_divergence(). The per-filetype
detector dispatch was extracted out of compute_forward_edges into detect_raw_edges(), so the
write path (enrich) and the read path (drift) now share one definition of "what counts as an
import." Two copies would be free to disagree, and a drift report that disagrees with the
enricher is worse than no report — it sends readers to fix edges the enricher then refuses to
write, or stays silent on edges the enricher would add.
--report-divergence returns before any filtering or write path, so "this flag cannot write" is a
property of the control flow rather than a promise in the help text.
Two failure modes that were easy to get wrong
Both of these were found by negative-controlling the change rather than by review:
- A failing detector must not read as a clean one. First version used a bare
_div_raw=$(python3 ... ). Underset -ea failing command substitution in an assignment
aborts the function — drift printed the section header and then simply stopped, mid-report.
Worse, without capturing rc, "the detector could not run" is indistinguishable from "the
detector found nothing", anddivergent: 0on a broken reader is precisely the failure this
section exists to abolish. Now:
_div_raw=$(PROJECT_ROOT="$PROJECT_ROOT" python3 "$LIB_DIR/enrich.py" \
--report-divergence 2>/dev/null) || _div_rc=$?
if [ "$_div_rc" -ne 0 ] || ! grep -q '^##DIVERGENT_CARDS=' <<< "$_div_raw"; then
echo " ? UNKNOWN — the divergence detector did not run ..."
# ... Summary prints "divergent: UNKNOWN (detector failed)"
- The shape tests were satisfiable by a broken wiring. Asserting only that the section and
thedivergent:term exist passes when the count is a wrong 0. The guard therefore also
cross-checks drift's count againstenrich.py --report-divergenceinvoked directly, and
asserts the oracle is non-zero so the cross-check cannot pass vacuously.
Compatibility
The existing Summary: line is extended, not restructured — unregistered: N, orphaned: N, stale: N survive verbatim, with , divergent: N (M edge(s)) appended. --summary gains
divergent: and divergent_edges: lines. The consumer's existing drift-determinism suite
(which parses the Unregistered section and stops at the first blank line) passes unchanged.
Cost: drift goes from ~7s to ~8s on 724 cards — one extra pass reading each card's source.
Reported via fw upstream report from field installation
- Ngôn ngữ chính
- Shell
- Star
- 16
- Fork
- 6
- Merge trung bình
- 6 giờ 24 phút
- Pull request đã merge (30 ngày)
- 5
Chuẩn bị môi trường
- Có Dockerfile hoặc tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của DimitriGeelen/agentic-engineering-framework
-
area:doctor bug severity:low status:accepted triaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
DimitriGeelen/agentic-engineering-framework#100 · 3 bình luận ·
-
area:doctor bug severity:medium status:accepted triaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
DimitriGeelen/agentic-engineering-framework#97 · 3 bình luận ·
-
area:context bug severity:low status:accepted triaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
DimitriGeelen/agentic-engineering-framework#96 · 3 bình luận ·
-
area:cli bug severity:medium status:accepted triaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
DimitriGeelen/agentic-engineering-framework#95 · 3 bình luận ·
-
area:watchtower bug severity:low status:released triaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
DimitriGeelen/agentic-engineering-framework#94 · 4 bình luận ·
Tất cả issue của DimitriGeelen/agentic-engineering-framework
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 67/100
phusion/passenger-docker#480 ·
-
Gemini: respect PLANNING_DISABLED=1 in lifecycle context hooksCó thể đã có người làm @AmariAugustin đã nhận hôm nay. Đang mởbug good first issue help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 87/100
OthmanAdi/planning-with-files#329 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Agents panel: Codex token stats always empty because Codex now writes `*.jsonl.zst` sessionsĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
Maintainer thường phản hồi trong vòng 2 ngày
-
headers-parser: `forRegExp` does not escape regexp characters in path segments that contain `*`Có thể đã có người làm @RaphaelFakhri đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
Maintainer thường phản hồi trong vòng 1 ngày
-
needs-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
mattpocock/skills#1307 ·
Maintainer thường phản hồi trong vòng 1 ngày