feat(m664): single-pass walker — 21+ reader migrations, ~5× perf - #715
Merged
mlieberman85 merged 1 commit intoAug 24, 2026
Merged
Conversation
…traversal Milestone 664 replaces the pre-existing per-reader `safe_walk` architecture (where each ecosystem reader walked the scan tree independently — 20+ traversals per scan) with a single-pass shared walker + reader-registry. Every migrated reader declares its interest via a `globset::GlobSet` + `on_file`/`on_dir` callback; the shared walker traverses ONCE and dispatches per-file matches in registration order. ## Perf validation (release-mode, warm-cache, macOS APFS, 2026-08-23) - ansible (5,793 files): 4.10s → 0.777s = **5.3x** (target SC-001 ≤ 1.2s, beats 35%) - pytorch (21,651 files): 4.30s → 1.117s = **3.9x** (target SC-002 ≤ 1.5s, beats 26%) - mongo (55,190 files): 15.68s → 3.04s = **5.2x** (target SC-003 ≤ 3.0s, 40ms over absolute but exceeds ≥5x claim) - SC-005 microbenchmark: per-file p95 = 34.6 µs (target ≤ 100 µs, beats 65%) All spec improvement multipliers met or exceeded. See `specs/664-single-pass-walker/perf-results.md` for full numbers + `specs/664-single-pass-walker/perf-comparison.md` for side-by-side against cdxgen 12.1.5 / syft 1.44.0 / trivy 0.71.1. ## Post-m664 pre-vs-post byte-identity check (2026-08-23) Ran pre-m664 nightly (v0.2.0-nightly.20260821) vs post-m664 HEAD on the same three fixtures with identical flags. Two m664 bugs surfaced and fixed same-session: 1. Symlink-to-file skip regression in `walker.rs`. Shared walker used `entry.file_type().is_file()` (does NOT follow symlinks) but legacy `safe_walk` used `Path::is_file()` (follows). On pytorch, dropped `docs/requirements.txt` (symlink) + 22 sphinx/matplotlib/ipython dependencies. Fix: stat-follow symlink targets; symlink-to-file dispatched as file, symlink-to-dir descended (canonicalize visited-set catches loops). 2. Composer + cocoapods missed `dist/` subtree on mongo. Their legacy skip predicates were narrower than the shared walker default (which skips `dist/build/target/out/coverage/bower_components`). Fix: added `descend_into: [target, dist, build, out, coverage, bower_components]` per contract C10 — scoped visibility to the requesting reader only. Post-fix diff vs pre-m664 nightly: pytorch byte-identical; ansible +1 `pkg:pypi/botocore` (depth-10, coverage improvement from pilot depth 16 > legacy pip cap 6); mongo +1 `pkg:cargo/gluesmith` (same class). Remaining diffs are strict supersets — post-m664 finds MORE, never fewer. ## Architecture New crate module `waybill-cli/src/scan_fs/walk_registry/`: - `walker.rs::SharedWalker` — single-pass tree walker with the same canonicalize-keyed visited-set + m113 ExclusionSet + m114 permissive semantics as legacy `safe_walk`. - `registry.rs::ReaderRegistry` — dispatch table; each `ReaderRegistration` carries a `GlobSet` of filename patterns + optional `on_file`/`on_dir` callbacks + opaque per-scan `state` (`Arc<dyn Any>` slot). - `dispatch.rs` — per-file/per-dir dispatch loop; iterates registrations in insertion order (contract C1); every callback wrapped in `catch_unwind` (contract C4 panic isolation). - `dir_index.rs::DirIndex` — in-memory (directory → sorted-filenames) map. Reader callbacks consult via `ctx.dir_index().contains(dir, name)` for sibling-lookup with zero extra syscalls (contract C6). - `walk_context.rs::SharedWalkerContext` — the reader-facing handle exposing `dir_index()`, `exclude_set()`, `push(reader_id, entry)`, and typed state retrieval. Ten contract clauses documented in `specs/664-single-pass-walker/contracts/registry-api.md` (C1 dispatch order, C2 sibling-lookup freshness, C3 sorted-filename invariant, C4 panic isolation, C5 safe_walk semantic preservation, C6 zero extra syscalls, C7 FR-006 byte-identity, C8 registration idempotency, C9 ReaderId uniqueness, C10 descend_into scope preservation). ## Migrations (21 readers + 3 originally-deferred, all resolved) US1 pilot (5 readers): haskell, scala, erlang, rpm_file, ipk_file. US2 remaining (16 readers): pip, cargo, gem, gradle, kotlin_dsl, npm, nuget, pants_jvm, pants_shell, pants_go, cocoapods, composer, dart, elixir, swift, vcpkg, conan, bazel, cmake, golang, yocto_layers. Originally deferred (all resolved 2026-08-23): - T029 yocto/recipe — resolved via `Option<Vec<PathBuf>>` precomputed- paths pattern (same shape as T047 pants_go / T058 golang). - T039 maven — resolved via C10 `descend_into: [target]` + ancestor- path filter in `finalize` for top-level-pom semantic. - T057 go_binary — resolved via two-phase pilot pattern (candidate-collection pilot + post-pilot finalize) plus C10 `descend_into: [build, dist, out, coverage, venv]`. Legacy `pub fn read()` shims retained under `#[allow(dead_code)]` across every migrated reader per FR-004 coexistence (deletion is a follow-up task after the coexistence period ends). ## Contract C10 — descend_into API extension (post-2026-08-23) `ReaderRegistration` gained an `Option<globset::GlobSet>` field. When set, the shared walker descends into normally-skipped directories matching any pattern. Dispatch under such subtrees is SCOPED — only the requesting readers receive callbacks, preserving byte-identity for non-requesting readers. ## Test coverage - 3 new contract-C10 unit tests in `walker.rs`. - Two integration test crates: `walk_registry_integration.rs` + `perf_walk_dispatch.rs`. - SC-005 microbenchmark: 10k-file synthetic tree, 5-warm-sample p95. - SC-001/002/003 wall-time tests (env-gated on `WAYBILL_PERF_ANSIBLE_DIR` / `WAYBILL_PERF_PYTORCH_DIR` / `WAYBILL_PERF_MONGO_DIR`). - Workspace test suite: 5183 passed / 0 failed (up from 5017 pre-m664 — the +166 includes the new C10 tests, wall-time tests, and misc scaffolding). ## Docs - `docs/design-notes.md` §"Shared-walker reader registry (milestone 664)". - `specs/664-single-pass-walker/spec.md` + `plan.md` + `research.md` + `data-model.md` + `contracts/registry-api.md` + `quickstart.md` + `tasks.md` + `perf-results.md` + `perf-comparison.md`. - `waybill-cli/src/scan_fs/walk.audit-allowlist.rationale.md` (T064 companion doc — categorizes retained walker-audit entries into FR-005 escape hatch / non-scan-tree / infra / shim-retention). ## CI - Walker-audit CI step (`.github/workflows/ci.yml`) gains the FR-008 diagnostic pointer in its failure branch (T065). ## Follow-up backlog Documented in `specs/664-single-pass-walker/perf-comparison.md §Follow-up backlog`: 1. `--no-binary-scan` flag (biggest single win — mongo would drop from 3.04s to ~640ms). 2. Extension-based cheap-reject in go_binary. 3. Startup-cost audit (~200-300ms fixed cost on small fixtures). 4. Parallel reader dispatch (FR-012 explicitly deferred by this milestone so single-pass baseline could be measured cleanly first). 5. `--discovery-only` mode (syft-style shallow scan). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
mlieberman85
added a commit
that referenced
this pull request
Aug 27, 2026
Cuts a stable v0.3.0. 43 commits since v0.2.0 with substantial feature additions: Feature highlights (grouped): * Resolvers + graph completeness: - m663 (#708, #709, #710, #712): local cache-probe resolver — reads ~/.m2, $GOMODCACHE, ~/.cargo, RubyGems, npm, pip caches for high-confidence PURL extraction without network. New C152 `waybill:resolver-tier` doc-scope annotation. - m235 (#688-#701): Gradle transitive dep-graph ladder — four-tier resolution (subprocess → cache → static → lockfile-only) with C146-C150 doc-scope + per-component annotations. - m236 (#703-#706): universalize `waybill:unresolved-reason` (C151) across all design-tier readers. * Architecture: - m664 (#715): single-pass filesystem walker — consolidates 21+ ecosystem-reader walk sites into one tree traversal per scan. 5-6× warm-cache perf improvement on large fixtures. - m665 (#719): `--no-binary-scan=<MODE>` flag — operator opt-out of Go-binary content probing. C153 `waybill:binary-scan- suppressed` doc-scope annotation across CDX/SPDX 2.3/SPDX 3. * eBPF: - m234 (#682, #686): un-pin bpf-linker to 0.11.0 via pre-built binary install + composite action + daily canary + verify script. * Fixes: - #711: verifier-friendly bound in tls_openssl::probe_ssl_read for kernel bumps. - #680: Go workspace-mode detection in mod_why preflight. - Several release.yml + cosign compat fixes (#674, #675, #681). Golden regeneration: * CDX goldens: 11 files, 1-line churn each — `metadata.tools[].version` bumped 0.2.0 → 0.3.0. * SPDX 2.3 goldens: 11 files. Version-string churn cascades into content-addressed SPDXRef-DocumentRoot-* IDs and documentNamespace URLs (both content-hashed). * SPDX 3 goldens: 11 files. Same content-addressing cascade into doc-/pkg-/anno-/rel- IRIs. Verified: normalized+sorted diff on all 33 goldens shows zero semantic change beyond the version string per project memory `feedback_verify_golden_churn_normalized`. Also updates `waybill-cli/tests/fixtures/pkg_alias_binding/image- baz.cdx.json` (same one-line version bump on an out-of-tree regression fixture). Skipping local pre-PR per project memory `feedback_release_bump_prepr_slow` — version bump invalidates the full compile cache so pre-PR takes 30+ min; CI will verify. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
safe_walk(20+ tree traversals per scan) with ONE traversal + per-file/per-dir dispatch to interested readers. New crate modulewaybill-cli/src/scan_fs/walk_registry/.pub fn read()retained under#[allow(dead_code)]per FR-004 coexistence.descend_intoAPI extension lets readers opt into descending shared-walker-skipped dirs (target/,build/,dist/, etc.) with C10-scoped visibility so byte-identity is preserved for non-requesting readers.specs/664-single-pass-walker/perf-results.md+perf-comparison.md.Perf validation (release-mode, warm-cache, macOS APFS, 2026-08-23)
Shared walker log confirms
passes=1on every scan. Walker cost per file: 4-12 µs. Seespecs/664-single-pass-walker/perf-results.mdfor the full write-up.Head-to-head vs cdxgen / syft / trivy
Also compared against cdxgen 12.1.5 / syft 1.44.0 / trivy 0.71.1 on the same three fixtures. See
specs/664-single-pass-walker/perf-comparison.md. Highlights:go_binary's 55kfs::metadata+read_binaryprobes (feature no other tool provides). Follow-up--no-binary-scanflag would close the gap.Pre-m664 vs post-m664 byte-identity check
Ran pre-m664 nightly (
v0.2.0-nightly.20260821) vs post-m664 HEAD on the three fixtures with identical flags. Two bugs surfaced and fixed same-session:walker.rs. Shared walker usedentry.file_type().is_file()(does NOT follow symlinks); legacysafe_walkusedPath::is_file()(follows). Dropped pytorch'sdocs/requirements.txt(symlink) + 22 sphinx/matplotlib/ipython deps. Fix: stat-follow symlink targets.dist/subtree on mongo. Their legacy skip predicates were narrower than the shared walker default. Fix: addeddescend_into: [target, dist, build, out, coverage, bower_components]per contract C10.Post-fix diff vs pre-m664: pytorch byte-identical; ansible +1
pkg:pypi/botocore(depth-10, coverage improvement from pilot depth 16 > legacy pip cap 6); mongo +1pkg:cargo/gluesmith(same class). Remaining diffs are strict supersets — post-m664 finds MORE packages, never fewer.Architecture (crate module
waybill-cli/src/scan_fs/walk_registry/)walker.rs::SharedWalker— single-pass tree walker with canonicalize-keyed visited-set (m054 loop guard), m113ExclusionSet, m114 permissive semantics.registry.rs::ReaderRegistry— dispatch table. EachReaderRegistrationcarries aglobset::GlobSet, optionalon_file/on_dir, opaque per-scanstate(Arc<dyn Any>), and newOption<globset::GlobSet> descend_into.dispatch.rs— insertion-order dispatch (C1) +catch_unwindper callback (C4 panic isolation).dir_index.rs::DirIndex— (dir → sorted-filenames) map for sibling lookup with zero extra syscalls (C6).walk_context.rs::SharedWalkerContext— reader handle withdir_index(),exclude_set(),push(), typedstate::<T>().10 contract clauses in
specs/664-single-pass-walker/contracts/registry-api.md.Migrations
Phase 1-2 (US1 pilot): haskell, scala, erlang, rpm_file, ipk_file — clean-shape criteria; collectively ~790ms savings on ansible baseline.
Phase 3-4 (US2 remaining): pip, cargo, gem, gradle, kotlin_dsl, npm, nuget, pants_jvm, pants_shell, pants_go, cocoapods, composer, dart, elixir, swift, vcpkg, conan, bazel, cmake, golang, yocto_layers. Patterns applied:
Option<Vec<PathBuf>>precomputed-paths (T037 cargo / T047 pants_go / T058 golang / T059 yocto_layers), sibling-lookupon_dir(T041 gradle / T052 swift), marker-detect (T045 pants_jvm / T053 vcpkg / T054 conan / T055 bazel / T056 cmake), ancestor-path filter for legacy-only skips (T048 cocoapods / T051 elixir / T058 golang).Phase 5-6 (US3 close-out): audit + escape-hatch annotations, walker-audit rationale companion doc, CI diagnostic pointer, SC-005 microbenchmark, docs updates.
Originally deferred, all resolved 2026-08-23:
Option<Vec<PathBuf>>precomputed-paths.descend_into: [target].descend_into: [build, dist, out, coverage, venv].Test plan
./scripts/pre-pr.shgreen (clippy + workspace test)Follow-up backlog (post-m664, out of scope for this PR)
Documented in
specs/664-single-pass-walker/perf-comparison.md §Follow-up backlog:--no-binary-scanflag (biggest single win — mongo would drop from 3.04s to ~640ms; ~1 day).go_binary(half-day; small win).--discovery-onlymode (syft-style shallow scan; ~2 days).🤖 Generated with Claude Code