Skip to content

Public-surface graph: surface_graph vs L5 header_graph node-id namespaces not unified; type_reachability stdlib query unmigrated

Index: Known gaps

Status (re-verified 2026-10-09 at 456989f): PARTIAL. The traversal half is superseded: the closure walk moved to policy/public_surface_closure.py. Still true: abicheck/type_reachability.py is unmigrated, and the compare/surface_graph.py and header_graph node-id namespaces are not unified.

policy/public_surface_index.py's PublicSurfaceQuery delegates to surface.compute_public_surface()/ export_surface.compute_export_surface() unchanged rather than reimplementing either as a literal graph traversal: both are exactly the kind of intricate, multi-round-corrected logic this same page's _paired_stable_indices incident (see "Primitive-level property tests" in AGENTS.md) shows costs several review rounds to get right even once already, and reimplementing one from scratch inside the same phase that also had to build every piece of graph infrastructure underneath it was judged materially higher-risk than landing the infrastructure now and migrating the algorithm as its own later, narrowly-scoped phase. Consequences: compute_public_surface()'s signature was never changed to accept a structured resolution parameter, so there is no lazy, graph-reading legacy-snapshot backfill path; type_reachability. directly_referenced_stdlib_types() was not migrated into policy/public_surface_index.py (doing so would reclassify type_reachability.py into the policy layer, which would introduce a genuine new policy -> extract architecture violation — that module imports two already-extract-classified siblings); and compare/surface_graph.py's own node ids (canonical_key(occurrence_id)/approx::/typedef:: fallbacks) are a namespace fully independent of buildsource/ header_graph.py's pre-existing L5 node ids (decl://<identity>/ type://<identity>) — one shared SourceGraphSummary instance carries both builders' nodes (real, tested — service_header_graph_attach. _attach_header_graph()), but the two schemes do not dedup onto a common node for a declaration both builders see. Each of these is a real, separate follow-up migration, not silently-abandoned scope — see the implementation plan's Phase 3 "Landed"/D5 "Amendment" notes (docs/contribute/plans/one-semantic-pipeline.md; ADR-063 itself no longer carries a duplicated per-phase status block, removed by PR 0, 2026-09-02) and compare/surface_graph.py's/policy/public_surface_index.py's own module docstrings for the exact reasoning each carries.

Correction (2026-09-01): the traversal-migration half of this entry's own title is now stale — the algorithm was migrated after all, just not in this phase's first landing. A later round did reimplement surface.py's closure walk as a real traversal rather than leaving it in place: _index_surface_types/_seed_public_roots/ _walk_type_closure/_walk_exact_type_closure/_record_exact_identities/ _record_nested_in_known_record/_record_is_confirmed_public_seed and the PublicSurface type moved to policy/public_surface_index.py (dataclass + indexing) and policy/public_surface_closure.py (the walk itself, plus resolve_public_surface()), and surface.py's own copies were deleted, not kept alongside — surface.compute_public_surface() is now a thin re-exporting wrapper. export_surface.py's own root-seeding stayed in place, but its final type-closure step now calls the same migrated _walk_type_closure the header domain uses, so that domain became graph-native for free. This is the risk the paragraph above named and chose to defer, not a different fix — it just didn't stay deferred through the whole phase. The next two bullets below give the full, three-review-round account of what that migration actually needed to get right (and what it does not touch — snap.surface_graph/ GraphNode.attrs, in the design that finally shipped). What is still correctly described by the paragraph above, unchanged: export_surface.py's own root-seeding logic, type_reachability.directly_referenced_stdlib_types() staying unmigrated (same policy -> extract reason), and the two node-id namespaces not deduping onto one node. See the implementation plan's Phase 3 "Landed"/D5 "Amendment" notes for Phase 3's final accounting — ADR-063 itself no longer carries this per-phase detail.