Skip to content

Epic: Java config-read edges and the entrypoint report #62

Description

@rahlk

Spec

docs/design/specs/2026-09-07-java-config-reads-and-entrypoint-report.md

Summary

codeanalyzer-java projects 9,688 :ConfigKey nodes and 2,604 :JEntrypoint marks, but nothing that says which code reads a key and nothing about how the entrypoint pass behaved. This epic adds application.config_uses[], application.config_reads_unresolved[] and application.entrypoint_report{} to analysis.json, and J_USES_CONFIG, J_READS_CONFIG_UNRESOLVED plus two :JApplication properties to the Neo4j projection.

Contract impact: additive in both projections. No shared field renamed, no shared kind repurposed. The graph contract version does not move — see the release plan.

Affected repos

  • codeanalyzer-java — the detection pass, the entrypoint report, both projections. Children 1–3.
  • python-sdk — the Java facade has no config accessor and no entrypoint-coverage accessor at all today, so consuming this data is new surface, not a read-the-new-field change. Child 4, deferred to a later cycle.
  • docs — contract note, folded into the last analyzer child.

Design decisions

  • J_ prefix (J_USES_CONFIG, J_READS_CONFIG_UNRESOLVED), matching codeanalyzer-python's PY_-prefixed pair. The unprefixed spelling was genuinely tempting — :ConfigKey is a cross-language merge target, so one MATCH would answer "who reads this key" across every analyzer — but python has shipped PY_USES_CONFIG, and one concept spelled two ways is the parity failure the clause names. Accepted cost: a cross-language config-read query needs one MATCH per language.
  • src widens to the annotated element. Python anchors a config read on a call body node because Python's config reads are calls. Java's dominant idiom is @Value("${db.url}") / @ConfigurationProperties, an annotation with no call site. So J_USES_CONFIG runs from JBodyNode (call-site reads) or JCallable/JField/JType (annotation reads). Rejected: minting a synthetic body node per annotation — it invents a body-node kind with no AST region behind it, a larger addition to the shared vocabulary than widening one endpoint list. Rejected: call sites only — literal parity that reports almost nothing on a Spring app, which is most of the daytrader8 corpus this was measured on.
  • Literal tier at L1, dataflow tier at L3/L4. Python gates its literal tier at -a 2; Java's JBodyNode already carries argument_expr/receiver_type/method_name at L1 and annotations are L1 data, so nothing in the tier needs a call graph. Monotonic by construction: config_uses(-a 1) ⊆ config_uses(-a 4), same additive contract as the DDG's ssapoints-to widening. Divergence stated: the same flag gives different availability on the two analyzers.
  • Unresolved reads follow python exactlyJApplication → JExternal, props {key, reason, prov, _k}, ghost minted at detection time so a read stays visible at L1 and with --external-calls off. _k = "<key>|<reason>" because one callee (System.getenv) reads many undeclared keys and a plain endpoint-pair MERGE keeps only the last.
  • Entrypoint report is python's four keys, honestly populated: rulesets is the five hardcoded finder names (that is Java's ruleset vocabulary), unresolved counts per-finder near-misses, errors records a finder that threw. Always present, even when empty — the pass under-approximates by design, so an absent report and an empty one must not read the same.

Scope guard — explicitly OUT:

  • Comment nodes. codeanalyzer-java#231 listed this as a third goal on the stated grounds that codeanalyzer-python projects comment nodes. It does not: there is no :PyComment node type in codeanalyzer/neo4j/schema.py, and project.py:843 collapses comments to a docstring property exactly as V2GraphProjector.docstringOf does. python-sdk documents it as a deliberate ceiling (neo4j_backend.py:73). No parity gap, no term to match — closed by fact. Projecting comments remains available later as a new capability, not as this parity fix.
  • CRUD (codeanalyzer-java#187).
  • The can:// id missing from the :JApplication root (codeanalyzer-schema#5).
  • codeanalyzer-typescript's identical config gap.

Release plan

  • codeanalyzer-java 3.0.3 → 3.1.0 (MINOR — additive JSON fields, additive graph relationships) carries children 1–3. Nothing gates the analyzer.
  • The graph contract version stays 2.0.0. V2SchemaCatalog.SCHEMA_VERSION is deliberately held there; the class javadoc's "2.2.0" is stale text. This analyzer already drifted to 2.1.0/2.2.0/3.0.0 alone while codeanalyzer-python stayed at 2.0.0, and that drift was undone. Moving it is a coordinated re-baseline across all three analyzers — Epic: scope every destructive Neo4j statement on the can:// id prefix; retire _module #50, not this epic. Recorded consequence: a consumer cannot detect J_USES_CONFIG or the entrypoint properties from schema_version alone. Detection is by presence until Epic: scope every destructive Neo4j statement on the can:// id prefix; retire _module #50 lands, the same gap the _module removal already lives with.
  • python-sdk is deferred, by decision. Stated plainly: the analyzer will emit data nothing consumes, and codeanalyzer-java#231's actual complaint — get_config_readers() returning an unconditional [], get_entrypoint_coverage() reporting entrypoint_report_unavailable — stays unfixed until child 4 ships in a later cycle.

Planned children, filed just-in-time as each is picked up:

  1. codeanalyzer-java — literal tier: detectors, resolver, both new JSON lists, both new relationship types.
  2. codeanalyzer-java — dataflow tier over the L3/L4 DDG, prov: ["dataflow"].
  3. codeanalyzer-java — entrypoint report: model, finder plumbing, both projections, docs.
  4. python-sdk — Java facade accessors. Deferred.

Definition of done (epic-level)

  • Every sub-issue closed with its gate green.
  • On a daytrader8 projection: J_USES_CONFIG is non-empty, every dst resolves to a projected :ConfigKey, and every src resolves to a projected :JBodyNode/:JCallable/:JField/:JType. No dangling endpoints.
  • MATCH (a:JApplication) RETURN any(k IN keys(a) WHERE k CONTAINS 'entrypoint') returns TRUE.
  • Monotonicity holds on a fixture carrying both a literal and a dataflow-only read: config_uses(-a 1) ⊆ config_uses(-a 3) ⊆ config_uses(-a 4).
  • analysis.v2.schema.json admits the three new application-level members; the L1 conformance gate stays green.
  • entrypoint_report_json parses with the same model shape as codeanalyzer-python's, and is present on every projection including one with zero entrypoints.
  • CHANGELOG updated; codeanalyzer-java 3.1.0 released.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    EpicCross-repo coordination epic

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions