Skip to content

P1: Add graph-core Python symbol-reference evidence #294

Description

@tomdps

Parent

Tracking parent #291; epic #243. This is the graph-provider prerequisite for complete Python dead-code evidence.

Goal

Make Rust graph-core emit truthful, contract-bound Python general symbol-reference facts in addition to existing CALLS, INHERITS, import, export, and containment facts, so consumers can distinguish a used callable/type/value from an unreferenced export without treating every name-shaped token as semantic proof.

Dependencies

Required behavior

  1. Add one public graph edge kind for general symbol use (proposed REFERENCES) through TypeScript contracts, validators, JSON schema, Rust protocol/store/query, graph handshake, fixtures, descriptors, package exports, and conformance tests. Keep CALLS, INHERITS, TESTED_BY, CONTAINS, and IMPORTS_FROM semantics unchanged.
  2. Extend the tree-sitter Python fact collector/resolver to emit REFERENCES only when the target declaration is uniquely resolved within canonical repo facts. Calls retain CALLS and also count as a general reference without duplicate-edge instability.
  3. Cover supported reads of module/class/function/variable declarations, direct and aliased from imports, qualified module attributes, decorators, default values, annotations, bases, assignments/right-hand expressions, comprehensions, pattern guards, and .py/.pyi symbols. Definition sites, assignment targets, parameter bindings, import bindings by themselves, keywords, and builtins are not use evidence.
  4. Preserve file/module origin for top-level expressions and symbol origin for nested bodies. Resolve local/enclosing/module/imported names in a deterministic documented order; shadowing and duplicate candidates must not attach a false edge.
  5. Handle __all__, re-export aliases, stub/runtime peer symbols, package __init__, src/flat/namespace layouts, and cross-file imports consistently with existing export/import facts. Where exact symbol identity cannot be proven, retain file reachability and emit no symbol edge rather than guessing.
  6. Make unsupported dynamic cells explicit in extractor coverage evidence/tests: getattr/globals, string annotations that cannot be safely resolved, wildcard ambiguity, monkey patching, runtime import hooks, reflection, metaclass-generated members, and receiver-dependent attribute dispatch.
  7. Keep extraction and storage bounded/deterministic. Reference collection must not recurse into nested definitions when attributing the outer definition, create quadratic global name scans, fabricate out-of-repo targets, or change source/config/cache state.
  8. Exact-state graph sessions and persistent graph builds must produce byte-equivalent normalized facts for the same visible universe; malformed parser/store/query/protocol evidence remains a provider failure, never an empty reference set presented as complete.

Acceptance criteria

  • Local/enclosing/module/imported direct reads, call references, classes/bases/decorators, values/constants, aliases, qualified module attributes, annotations/defaults, comprehensions, methods, stubs, re-exports, and tests emit the expected stable edges.
  • Shadowing, duplicate names, ambiguous star imports, unresolved attributes, builtins, assignment targets, parameters, definitions, strings/reflection/dynamic imports, and out-of-root candidates never fabricate use evidence.
  • Contract/schema/Rust enums or validators/store migrations/handshake/query selectors/fixtures/package exports are synchronized and old graph artifacts fail or migrate according to existing graph policy.
  • Graph build/update/exact hypothetical paths agree; edge ids/order/fingerprints are deterministic across repeated builds and platforms.
  • Large synthetic files/repositories remain within declared extraction/store/query bounds and cleanup leaves no temp roots or WAL/checkpoint regression.
  • Existing TypeScript, Rust, imports, relevant-tests, and Python checks remain behaviorally unchanged in this slice.

Observable outcomes

  • Supported symbol reads have positive REFERENCES evidence with stable source/target identities.
  • Ambiguous/dynamic cases remain absent and are documented as unsupported, not converted to clean semantic certainty.
  • Graph capability handshake advertises the edge only when the running artifact actually supports it.

Non-goals

  • dead-code diagnostics or severity changes (owned by the dependent child)
  • LSP-quality references/rename
  • type inference or dynamic dispatch guesses
  • a second Python parser in TypeScript
  • public readiness claims

Verification

  • npm run build
  • focused Rust Python fact collector/resolution/store/query/protocol tests
  • graph contract/schema/conformance and exact-state overlay tests
  • graph release artifact/receipt checks required by changed native code
  • npm run lint
  • npm run rust:check
  • npm run pack:check
  • configured zeroshot cmdproof prove opcore-ci

Attach normalized fact fixtures, unsupported-case evidence, artifact/protocol identity, repeated-build determinism, bounds/cleanup evidence, and packed-install graph proof.

Execution rule

Implement only after a JIT review against current dev. Keep exactly one implementation run active. PR base is dev; require green checks, clean scoped diff, merged PR, closed child, #291/#243 checklist update, focused reproductions, and opcore-ci proof before the dependent dead-code child starts. Do not claim Python readiness/parity, ASP authority, old-tool replacement, security/SAST, all-stack support, automatic fixes, or a blended score.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions