feat(graph): L3/L4 program-slice engine core (Tasks 1–6 of #270)#271
Merged
Conversation
_intra applied the sdg (dataflow) overlay whenever the backend was L4 and interprocedural was wanted, ignoring the edges family filter — so a cfg- or cdg-only slice picked up dataflow vertices from foreign callables. Fold the family gate into want_inter so the overlay predicate and explain()["interprocedural"] stay one source of truth: no dataflow family requested means no boundary crossing. Subsumes the redundant max_level()>=4 check on the overlay branch; control_deps' explicit interprocedural=False stays as belt-and-suspenders.
…270) flows_to's full semantics are ddg + summary/param_in/param_out — an interprocedural (L4) capability — but it gated on require(3), so an L3 backend silently returned intra-only results as if they were complete. Raise the requirement to L4: non-strict now attaches the degraded note (absence is UNKNOWN, not safety) while still returning the intra ddg witnesses it can compute; strict=True raises CapabilityError.
flows_to built its dataflow graph from the source's callable only, so a sink inside a different callable (reachable via param_in into the callee interior) was unreachable and reported a false "no flow". _dataflow_graph now unions the intra ddg graphs of the given callables before adding the sdg overlay, and flows_to passes both endpoint callables. def_use keeps its single-callable scope with a NOTE — interprocedural completeness lands with the whole-program dataflow graph (deferred to Task 7).
…or (#270) resolve_location legitimately returns [] when no vertex sits at the given line, but every engine verb indexes resolve_vertex(...)[0], turning an ordinary user miss into an IndexError. Raise a descriptive ValueError at the source in resolve_vertex instead, converting all [0] call sites into a clean failure mode.
…#270) The graph is an nx.MultiDiGraph everywhere, but three annotations still said nx.DiGraph: ProgramGraphProvider.program_graph, GraphResult.subgraph (and engine._dataflow_graph, already corrected in the C2 commit). Fix the first two and document the provider contract: parallel cfg/cdg/ddg edges between the same vertex pair must stay distinct edges with their own family/var/prov/kind, and sub-L3 providers still answer the structural methods — the engine handles level gating via require(...).
The sdg overlay dropped e.kind (param_in/param_out/summary) on the floor in both _intra and _dataflow_graph, so a FlowPath hop crossing a callable boundary could only say "sdg" — not which boundary edge carried the flow. Carry kind on the overlay edges and have the hop dict prefer the edge kind over the family: intra hops still report cfg/cdg/ddg, boundary hops now report the concrete sdg kind.
…#270) _evidence stamped every non-seed vertex "def", which is wrong for control_deps (the guard CONTROLS the seed) and def_use (downstream vertices are USES of the definition). Thread a default_role through _evidence and _intra: slices and flows keep "def", control_deps passes "control", def_use passes "use"; seeds are always "seed".
flows_to enumerated simple paths with a hard-coded cutoff=64 and no path cap, and never told the caller when witnesses were dropped. Hoist the bounds to module constants (_PATH_CUTOFF=64, _MAX_PATHS=1000 — provisional, to be tuned on real graphs), stop collecting at _MAX_PATHS, and surface explain()["truncated"] so a partial witness set is never silently presented as complete.
…270) prov is a provenance set in list form; the exact-list comparisons (prov == ["points-to"] / == ["ssa"]) made the STRONGER combined provenance ["ssa", "points-to"] fall through to "unresolved". Rank by membership: points-to anywhere means resolved, else ssa means structural, else unresolved.
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.
L3/L4 program-slice engine — core (Tasks 1–6 of #270)
Adds
cldk/graph/: a shared, language-neutral program-slice engine that turns schema-v2 L3/L4 graphs (CFG/CDG/DDG + interprocedural SDG) into citable slice/flow evidence. This is the engine core; the facade/backend wiring (Tasks 7–10 of the plan) is blocked on F2 (#240) and lands separately. Part of epic #238 (2.0.0-rc.1). Design/plan:docs/superpowers/specs/2026-07-14-l3l4-consumption-api-design.md,docs/superpowers/plans/2026-07-14-l3l4-slice-engine.md(local).What's here
result.py—SliceResult/FlowResult/FlowPath(walkable subgraph + citable evidence +explain()), confidence fromprov(ssa→structural,points-to→resolved).provider.py— theProgramGraphProviderABC data-seam (the only per-backend surface) + polymorphicresolve_vertex(location string ∪can://id ∪ node).capability.py— honest-degrade /strictgating on analysis level.engine.py— the single shared traversal:slice_backward,slice_forward,flows_to,def_use,control_deps.Traversal is written once over the seam (parity by construction); the graph is an
nx.MultiDiGraph(coincident cfg/cdg/ddg edges stay distinct); theuris() == subgraph.nodes()invariant holds across every verb. This engine is also the executor the 2.1.0 fluent chain (#155) will reuse.Scope / safety
Purely additive — a new package with no imports into any existing facade; the frozen
get_*API is untouched. No production caller yet (the facade delegates arrive in Tasks 7–10), so nothing is wired until then.Tests & review
36 tests, all green. Built via subagent-driven TDD with a two-stage review (per-task + whole-branch on the most capable model). The review gate caught and closed 9 defects latent in the plan's own code that passing tests did not reveal — including
flows_togated at L3 not L4,flows_toignoring the sink's callable (false "no flow"), and a family-blind SDG overlay contaminating control-flow slices. Final whole-branch verdict: ready for scope-complete.Carry-forward for the wiring tasks (7–10, on #270)
kindmust stay family-first for intra edges — when the Task-7 provider materializes cpgEdge.kindonto intra ddg edges, the hop-kind fallback would otherwise silently change intra hops from"ddg", breaking facade parity.analysis.json):def_useinterprocedural reach; multi-hop (3+ callable)flows_to; multi-seed union (resolve_vertex[0]);_ddg_tierprov-shape verification.Part of #270. Do not merge as "F7 done" — this is the core; F7 completes when Tasks 7–10 wire it.