feat(entrypoints): report on the application root, framework attribution on the node - #235
Merged
Merged
Conversation
…ion on the node
`:JApplication` carried exactly `name`, `schema_version`, `analyzer_name` and
`analyzer_version`. The analyzer marked 2,604 `:JEntrypoint` nodes on a
daytrader8 + thingsboard projection and said nothing about how the pass that
found them behaved, so a consumer could not tell "this application has no
entrypoints" from "the detection pass found nothing". The five finders returned
bare booleans and `TypeBuilder`/`CallableBuilder` collapsed them with `anyMatch`,
so even which framework matched was discarded.
Adds `JEntrypointReport{frameworks_detected, rulesets, unresolved, errors}` —
codeanalyzer-python's four keys, so one SDK model parses either analyzer's
report — on `application.entrypoint_report`, plus `entrypoint_frameworks` on
`type` and `callable`, and the two properties python already projects onto its
application root.
Attribution lives on the NODE, not in a run-scoped tally, and that is the load-
bearing decision. `L1Extractor` reuses a cached module byte-for-byte and skips
the build, so the finders never run for that file; a tally kept during the walk
would under-report `frameworks_detected` on a warm cache — the exact ambiguous
empty this record exists to prevent. The tree carries the attribution whether the
module was rebuilt or reused, and the application-level union is taken over the
built tree at assembly time. It also happens to be more python parity, not less:
`PySymbol`/`PyCallable` already carry `is_entrypoint` alongside
`entrypoint_frameworks`.
`rulesets` is the five finder names — Java's finders are hardcoded classes rather
than rule files, so the finder set IS the ruleset vocabulary. Emitting it
alongside an empty `frameworks_detected` is what says the pass looked and came
back empty-handed.
The report is projected unconditionally, and `entrypoint_frameworks` is set on
the application node AFTER prune: it is an empty list on exactly the
applications the report was written for, and prune drops empty collections.
Verified on daytrader8 (`-a 1`, `--no-build`): 68 entrypoint classes, 135
entrypoint methods, `frameworks_detected` = [jakarta, jaxrs, spring] against five
rulesets, zero disagreements between the boolean and the attribution, and the
payload validates against `analysis.v2.schema.json`. A warm-cache run produces a
byte-identical report to the cold one.
Known limitation, named rather than left implicit: `unresolved` and `errors`
capture a finder that THROWS, and the finders currently swallow their own
resolution failures internally (`StrutsEntrypointFinder.java:41` logs and returns
false), so those near-misses are not yet counted. Finder accuracy is out of scope
here.
`schema.neo4j.json` regenerated with `--emit schema`.
Closes #234
Resolves three "both sides added adjacent content" conflicts by keeping both halves: the application-scope wiring in CodeAnalyzer, the two field groups on JApplication, and the new `$defs` in the conformance schema. Regenerates `schema.neo4j.json` with `--emit schema` so the checked-in snapshot carries both changes. It is a whole-catalog artifact, not a per-change diff, so each branch had regenerated it against itself and neither copy was complete. Verified on the merged tree: 535 tests green apart from the Docker-dependent integration test, and a daytrader8 `-a 1` run emits config_uses (15), the unresolved reads (12) and the entrypoint report together, validating against analysis.v2.schema.json.
Merged
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.
Closes #234. Spec:
codellm-devkit/.githubdocs/design/specs/2026-09-07-java-config-reads-and-entrypoint-report.md(decision D31, codellm-devkit/.github#63), epic codellm-devkit/.github#62.What
:JApplicationcarried exactlyname,schema_version,analyzer_name,analyzer_version. The analyzer marks 2,604:JEntrypointnodes and said nothing about how the pass that found them behaved, so a consumer could not tell "this application has no entrypoints" from "the pass found nothing". The five finders returned bare booleans andTypeBuilder.java:66/CallableBuilder.java:92collapsed them withanyMatch, so even which framework matched was discarded.JEntrypointReport{frameworks_detected, rulesets, unresolved, errors}onapplication.entrypoint_report— codeanalyzer-python's four keys, so one SDK model parses either analyzer's reportentrypoint_frameworks: string[]ontypeandcallableentrypoint_frameworksandentrypoint_report_jsonon:JApplication;entrypoint_frameworkson:JType/:JCallableAbstractEntrypointFinder.framework()so attribution survives the collapseThe load-bearing decision: attribution lives on the node
L1Extractor.java:139-145reuses a cached module byte-for-byte and skips the build, so the finders never run for that file. A tally kept during the walk would under-reportframeworks_detectedon a warm cache — the exact ambiguous empty this record exists to prevent. The tree carries the attribution whether the module was rebuilt or reused, and the application-level union is taken over the built tree at assembly time.It is also more python parity, not less:
PySymbol/PyCallablealready carryis_entrypointalongsideentrypoint_frameworks(schema.py:110-111,133-134).Two traps worth reviewing
Prune order.
entrypoint_frameworksis set on the application node afterRowBuilder.prune. It is an empty list on exactly the applications the report was written for, and prune drops empty collections — set it before, and the property silently vanishes from the only graphs that needed it.theApplicationNodeCarriesTheReportEvenWithZeroEntrypointspins this.rulesetsis not decoration. An emptyframeworks_detectednext to a populatedrulesetsis the signal. Emitting the report only when non-empty would defeat the whole record, so it is emitted unconditionally — the one field onJApplicationthat deliberately breaks the "absence means no fact" rule.Verification
daytrader8,
-a 1 --no-build:frameworks_detected[jakarta, jaxrs, spring], all ⊆ the fiverulesetsanalysis.v2.schema.json-c <dir>twice ⇒ byte-identical report:JApplicationcarries both properties;MATCH (a:JApplication) RETURN any(k IN keys(a) WHERE k CONTAINS 'entrypoint')is nowTRUEEntrypointReportTestcovers the populated case, the boolean/attribution invariant, rulesets containment, and — the ones that matter — the zero-entrypoint fixture and the pruning trap.Full suite: 525 tests, 1 failure —
CodeAnalyzerIntegrationTestcannot reach a Docker daemon on this machine and fails identically on a cleanmain.Known limitations, named rather than left implicit
unresolvedanderrorscapture a finder that throws. The finders currently swallow their own resolution failures internally (StrutsEntrypointFinder.java:41logs and returns false), so those near-misses are not yet counted. Finder accuracy is explicitly out of scope for Entrypoint report on the application root, and per-framework attribution on the node #234.springshows up on daytrader8, which is Java EE — the Spring finder matchesa.toString().contains("Component")against the whole annotation text. Pre-existing false-positive source, same out-of-scope note.schema.neo4j.jsonhere is regenerated against this branch only. It will need regenerating once this and feat(config): config-read literal tier — J_USES_CONFIG and J_READS_CONFIG_UNRESOLVED #233 both land.Contract version
Stays at
2.0.0. A re-baseline is codellm-devkit/.github#50.