Skip to content

refactor(generation): the model-to-code pipeline is Java only - #6707

Merged
delchev merged 2 commits into
masterfrom
feat/generation-java-only
Aug 13, 2026
Merged

refactor(generation): the model-to-code pipeline is Java only#6707
delchev merged 2 commits into
masterfrom
feat/generation-java-only

Conversation

@delchev

@delchev delchev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Completes GENERATION_UTILS_JAVA_PLAN.md PRs 2 and 3. PR 1 landed the Java port plus a byte-for-byte parity harness with both paths live and no consumer switched; this switches every consumer to the Java pipeline and deletes the JavaScript one.

What moves

  • The endpointPOST /services/ide/generate/model/{workspace}/{project}?path=<model> on the existing GenerationEndpoint, body {template, parameters}. The plan's {*path} was dropped in favour of the ?path= query the JavaScript endpoint already used, so every caller was a base-URL swap. It answers 201 as before, and maps an absent model/template, a template with no sources, an unsupported template and a model a template cannot compile to 400.
  • One place in the browser — the GenerateService provider now builds both its URLs from /services/ide/generate, so editor-entity, editor-form-builder, editor-report and view-projects follow with no change of their own.
  • The intent Generate is one callIntentGenerationService runs the .settings recipes itself after writing the model files, and each codeGenerations entry comes back carrying its own outcome (generated, plus error). A failure is isolated to its entry: the models are already on disk, so a template that cannot render is a partial result to report, not a reason to fail the whole Generate. The replay loops in editor-intent and in the Builder's publish pipeline are gone; both read the report.
  • template-mapping-java is ported — its generate() was a compiler (criteria parsed into typed Java comparisons, a value expression per source kind, the mapper class name), and it was the one template the Java pipeline refused by name. It is now MappingCompiler. Without this, deleting the JavaScript path would have silently taken .mapping generation with it.

What is deleted

generate.mjs, generateUtils.js (1,896 lines) and parameterUtils.js (649 lines), plus the generate() export of all nine template modules — their getTemplate() descriptors stay. Comments across the repo that pointed at the deleted files now name the Java classes that replaced them.

Deliberately kept: the read-only service-template/api/templates.js that lists the templates. The plan defaulted to porting it, but the premise changed: template descriptors are still JavaScript modules (plan §1.1) and the Java pipeline reads them through GenerationService.getTemplateMetadata, which boots a JavaScript context per template — so a Java list service over JavaScript descriptors would add a second path to that boot rather than remove one. It goes when the descriptors do. Reasoning recorded in the plan's new §7.3.

Tests

GenerationParityIT compared the two pipelines byte for byte; with one pipeline left there is no oracle, so it becomes ModelGenerationIT and asserts what that oracle protected: every template renders, renders the same thing twice, produces its .gen descriptor free of keys an engine added to the parameter graph (the §6.4 Mustache-mutation finding, which would otherwise accumulate in a committed file), and really merges a cross-project entity extension. A mapping case joined it with the compiler port.

Verified locally:

Check Result
mvn -T 1C clean install -P quick-build (311 modules, fat jar) green
Unit tests, touched modules (410, incl. 7 new MappingCompilerTest) green
ModelGenerationIT (13 cases) green
IntentEngineIT — generate / glue / report-file / setField 4/4
IntentEditorLoadsIT + IntentBuilderShellIT (browser) 4/4
formatter:validate, javadoc -P release on touched modules clean

Not covered by a run: the four AngularJS editors' Generate goes through the provider function this PR re-points, and their coverage lives in the full UI suite rather than the smoke set. The change there is a URL swap onto an endpoint with the same request and response contract, but the nightly is what will exercise it.

🤖 Generated with Claude Code

// A model or template that is not there, a template declaring no sources, an unsupported
// template, a model the template cannot compile (an unparseable mapping criteria): all
// things the caller asked for wrongly, reported rather than generated incompletely.
logger.warn("Failed to generate [{}] of project [{}] with template [{}]", path, project, parameters.getTemplate(), e);
modelGenerationService.generate(workspaceName, projectName, path, templateId, parameters);
entry.put("generated", Boolean.TRUE);
} catch (IOException | RuntimeException e) {
LOGGER.error("Failed to generate code from [{}/{}] with template [{}]", projectName, path, templateId, e);
String text = str(column, "scale");
if (text != null && NUMERIC.matcher(text)
.matches()) {
double value = Double.parseDouble(text);
@delchev

delchev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

On the CodeQL run

I went through all 19 alerts. Four are this PR's to answer; the rest are pre-existing flows the migration made visible.

Fixed (pushed in a914273):

Alert Verdict
java/tainted-numeric-cast (critical) — JavaScriptJson:179 False positive: the branch above bounds the value to an integral magnitude under 10¹⁵ before the (long) cast, against a long range of ~9.2×10¹⁸. Rewritten as Math.round behind a named MAX_INTEGRAL — same output (JavaScriptJsonTest pins it), saturating rather than wrapping, and no narrowing cast to flag.
java/log-injection ×3 — GenerationEndpoint:128, IntentGenerationService:171, ModelGenerationService:250 Fair. These log request-derived strings; they now go through the sanitizeForLog idiom JavaEndpoint already uses.
java/uncaught-number-format-exception (note) — MappingCompiler:217 Fair as a pattern even though the regex guard means it cannot throw. Now a digits-only match plus Integer.parseInt in a try.

Left alone deliberately — the other 14 java/log-injection, in PublisherService, ActionsService and ProjectActionsPublisherHandler: three ide-workspace files this PR does not touch. They are flagged now because the new Java endpoint gives CodeQL a traceable source for the workspace / project / path strings that reach publisherService.publish(...). The same user data reached the same log lines before, through lifecycle.publish from generate.mjs — taint tracking just cannot follow it through GraalJS. So this is a side effect of the migration being an improvement: taking GraalJS out of the path made the flow analyzable, and the pre-existing sinks lit up. (GitHub's own summary hedges the same way: "alerts not introduced by this pull request might have been detected because the code changes were too large.")

Reaching them needs a caller already authenticated as DEVELOPER/ADMINISTRATOR, who can write arbitrary workspace files and publish them anyway — so the class is real but the reach is negligible. Happy to sanitize those three files here if a reviewer prefers the gate green in one go; otherwise they belong in their own change.

One thing the noise did surface, worth its own issue: RepositoryPath concatenates segments without resolving .., so a model path like ../other-project/x.model is not rejected at the boundary — equally true of the pre-existing /services/ide/generate/file/... endpoint and of the deleted generate.mjs. Not introduced here, and not something I would fix inside this migration.

delchev and others added 2 commits August 13, 2026 19:24
Completes GENERATION_UTILS_JAVA_PLAN.md PRs 2 and 3: every consumer now
generates through the Java pipeline, and the JavaScript one is deleted.

- POST /services/ide/generate/model/{workspace}/{project}?path= on
  GenerationEndpoint, body {template, parameters} - the contract the
  JavaScript endpoint had, so every caller was a URL swap.
- The GenerateService provider builds both its URLs from
  /services/ide/generate, so editor-entity, editor-form-builder,
  editor-report and view-projects followed with no change of their own.
- The intent Generate is ONE call: IntentGenerationService runs the
  .settings recipes itself after writing the model files, and each
  codeGenerations entry comes back carrying its own outcome (generated,
  plus error when it failed). A failure is isolated to its entry - the
  model files are already on disk, so a template that cannot render is a
  partial result to report, not a reason to fail the whole Generate. The
  replay loops in editor-intent and in the Builder pipeline are gone.
- template-mapping-java's generate() was a compiler rather than
  marshalling, and was the one template the Java pipeline refused by name;
  it is now MappingCompiler (+ MappingCompilerTest), so deleting the
  JavaScript path does not silently take .mapping generation with it.
- Deleted generate.mjs, generateUtils.js and parameterUtils.js, and the
  generate() export of all nine template modules. Their getTemplate()
  descriptors stay, and so does the read-only templates.js listing them:
  the descriptors are still JavaScript, so a Java list service over them
  would add a second GraalJS path rather than remove one (plan §7.3).
- GenerationParityIT had no oracle left once the second pipeline went, so
  it is now ModelGenerationIT, asserting what that oracle protected: every
  template renders, renders the same thing twice, produces its descriptor
  free of keys an engine added to the parameter graph, and really merges a
  cross-project entity extension. A mapping case joined it.

Verified: full reactor build; 410 unit tests in the touched modules;
ModelGenerationIT 13/13; IntentEngineIT (generate, glue, report-file,
setField) 4/4; IntentEditorLoadsIT + IntentBuilderShellIT 4/4;
formatter:validate and javadoc -P release clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Of the 19 CodeQL alerts on the migration, four are this PR's to answer; the
rest are pre-existing flows the migration made analyzable (the same user
data reached the same ide-workspace log lines through generate.mjs, where
taint tracking could not follow it through GraalJS).

- JavaScriptJson wrote an integral double with a (long) cast, which reads as
  an unbounded narrowing of request data even though the branch above bounds
  it. Math.round behind a named MAX_INTEGRAL says "the integral value"
  instead, and saturates rather than wrapping if that guard is ever relaxed.
  Identical output - JavaScriptJsonTest pins it.
- The three log statements this PR owns sanitize their request-derived
  values with the sanitizeForLog idiom JavaEndpoint already uses, so a
  crafted workspace, project or model path cannot forge a log line.
- MappingCompiler read a text scale through Double.parseDouble behind a
  regex; a digits-only match plus Integer.parseInt in a try says the same
  thing without the fragile pattern.

Left alone deliberately: the 14 log-injection alerts in ide-workspace
(PublisherService, ActionsService, ProjectActionsPublisherHandler). They are
three files this PR does not touch, reachable only by a caller who is
already authenticated as DEVELOPER or ADMINISTRATOR and can write workspace
files directly - worth their own change rather than sprawl here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev force-pushed the feat/generation-java-only branch from a914273 to ca479ec Compare August 13, 2026 16:28
@delchev
delchev merged commit 32e3fe6 into master Aug 13, 2026
7 of 9 checks passed
@delchev
delchev deleted the feat/generation-java-only branch August 13, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants