Imported from Servoy/servoy-client (
AGENTS.md). Install upstream withnpx skills add Servoy/servoy-client. Copyright stays with the author.
Agent Guidelines for Servoy Runtime Codebase
Welcome, AI Agent! This repository contains the core Servoy runtime code (including Servoy Developer, Eclipse integration, plugins, extensions, and various web/smart/headless clients). To ensure safety, consistency, and proper integration with the Eclipse workspace environment, you must adhere strictly to the following developer and automation workflows.
1. Repository Projects Analysis
This Git repository contains 8 core projects/plugins forming the Servoy runtime. Understanding their roles and relationships is crucial for making architectural and design-compliant edits:
1. servoy_base
- Type: Eclipse Plugin / OSGi Bundle (
eclipse-plugin) - Main Role: Base persistence, querying, and the Solution Model APIs.
- Key Focus: Defines the foundational data processing structures, querying frameworks (
com.servoy.base.query), persistent object mappings, and solution models (com.servoy.base.solutionmodel). - Crucial Detail: Does not contain any UI or platform-specific libraries. It forms the lowest-level layer of the application.
2. servoy_shared
- Type: Eclipse Plugin / OSGi Bundle (
eclipse-plugin) - Main Role: Shared runtime logic, database connectivity, and JavaScript scripting engine.
- Key Focus: Serves as the central engine shared between different clients/servers. Implements the script execution interface using Mozilla Rhino (
org.eclipse.dltk.javascript.rhino), core database processing (com.servoy.j2db.dataprocessing), serialization, i18n, and plugin APIs. - Crucial Detail: Re-exports
servoy_base. It acts as the backbone of both headless, desktop (smart), and web (NG) clients.
3. servoy_smart_client
- Type: Eclipse Plugin / OSGi Bundle (Manifest-first)
- Main Role: Java Swing-based Desktop client (Smart Client).
- Key Focus: Contains the complete GUI desktop framework, layout managers, wizards, UI preferences, and desktop-specific plug-in classes (
com.servoy.j2db.smart.*). - Crucial Detail: Inherits from
servoy_sharedand depends heavily on Java Swing packages.
4. servoy_headless_client
- Type: Eclipse Plugin / OSGi Bundle (Manifest-first)
- Main Role: Headless client execution for server-side processing.
- Key Focus: Allows executing Servoy logic and calculations on the server without any graphical user interface. Contains servlet integrations and HTML/web-adapter structures.
- Crucial Detail: Integrates with
jsoupfor HTML parsing and depends on thejakarta.servletspecification to run inside servlet containers.
5. servoy_ngclient
- Type: Eclipse Plugin / OSGi Bundle (
eclipse-plugin) - Main Role: Next-Generation (NG) Web Client (HTML5 / Angular / WebSockets).
- Key Focus: The modern, main web client engine. Integrates with the Sablo framework for WebSocket communication, Tomcat server, auth0 JWT/OAuth APIs, freemarker template engines, and handle client components, properties, styles (LESS compiling), and client event loop.
- Crucial Detail: This is highly complex with rich external dependencies (e.g., Tomcat, ScribeJava, Tus upload, auth0).
6. servoy_ngclient.tests
- Type: OSGi Fragment Bundle
- Main Role: Unit tests for
servoy_ngclient. - Key Focus: Contains JUnit tests specifically verifying WebSocket messaging, properties, component specifications, and behaviour of the NG client.
- Crucial Detail: Set as
Fragment-Host: servoy_ngclient, allowing direct access to package-private members inservoy_ngclient.
7. servoy_debug
- Type: Eclipse Plugin / OSGi Bundle (Manifest-first)
- Main Role: Debugger capabilities for Servoy developers.
- Key Focus: Implements debugging interfaces, layout extensions, and hooks integrated into the developer workspace environment.
- Crucial Detail: Specifically uses Eclipse SWT (
org.eclipse.swt) and runtime (org.eclipse.core.runtime) to tie debugger UI with the Eclipse workspace.
8. servoy_doc
- Type: Standalone Maven Build Project (
jar) - Main Role: Documentation XML generator from source code.
- Key Focus: Aggregates source code across
servoy_base,servoy_shared,servoy_headless_client, andservoy_ngclientto generate Servoy API documentation (such asservoydoc.xmlandservoydoc_jslib.xml) using a headless Tycho Eclipse application. - Crucial Detail: Not part of the runtime client, but part of the build-time SDK generation process.
2. Prioritize Eclipse MCP Tools Over Standard Tools
Since this workspace is a complex, multi-project Eclipse environment, always prioritize Eclipse-specific MCP/PDE tools over standard, general-purpose command-line or filesystem tools. This ensures that the Eclipse index, builder, and classpath are kept in sync.
- File Reading: Use
eclipse-ide_readProjectResourceinstead of the genericreadtool. - File Writing & Creating: Use
eclipse-coder_createFileoreclipse-coder_replaceFileContentinstead of the genericwritetool. - File Editing: Use
eclipse-coder_applyPatch,eclipse-coder_insertIntoFile,eclipse-coder_replaceString, oreclipse-coder_deleteLinesInFileinstead of the genericedittool. - File / Class Searching: Use
eclipse-ide_fileSearch,eclipse-ide_fileSearchRegExp, oreclipse-ide_findFilesinstead of genericgreporglob. - Git Operations: Use
eclipse-git_*tools instead of standard shellgitcommands inbash. - Testing: Prefer
eclipse-ide_runAllTests,eclipse-ide_runClassTests,eclipse-ide_runTestMethod, oreclipse-pde_runJUnitPluginTestsover generic shell test commands.
Refactoring
- Use
eclipse-coder_refactorRenameJavaTypeto rename classes, interfaces, enums, or records — this updates all references across the workspace. - Use
eclipse-coder_refactorRenamePackageto rename packages — updates all package declarations and references. - Use
eclipse-coder_refactorMoveJavaTypeto move types between packages. - For method, field, and variable renames: use
eclipse-ide_findReferencesfirst to find all usages, then apply the rename consistently. Prefer Eclipse refactor tools over manual find-and-replace to ensure all references are updated correctly.
Navigation & Discovery Tools
For quick codebase orientation and type/method lookup, use the JDT-powered search tools:
eclipse-ide_searchTypes— Fuzzy type search via JDT SearchEngine. Supports wildcards (*Payment*), CamelCase (PS→PaymentService), prefix, and package-qualified patterns. Equivalent to Eclipse's Open Type (Ctrl+Shift+T). Use this instead of grep/glob when looking for a class by partial name.eclipse-ide_searchMethods— Method name search with the same pattern support, plus optional declaring type filter. Use when you need to find where a method is defined without knowing the full class name.eclipse-ide_getPackageSummary— Returns each type's name, kind, Javadoc first sentence, method/field counts, and interfaces for a package — a table-of-contents in one call. Use to quickly understand what a package contains.eclipse-ide_getWorkspaceOverview— High-level architectural map of projects → packages → type names for immediate orientation. Use as the first step when exploring an unfamiliar part of the codebase.
3. Commit Message Convention [ai]
To maintain clarity and transparency about the origin of codebase changes, any Git commit consisting primarily of AI-generated or AI-assisted changes must follow this rule:
- The commit subject line must end with
[ai](case-insensitive, space followed by bracketedai). Examples:Fix NullPointerException during client initialization [ai]orImplement support for modern TLS protocols in server connection [ai] - Commit messages for cases: When a commit is related to a Jira case, the case number (e.g.
SVY-123,SVYX-456,SERVOY-293) must be included in the commit subject line. Example:SERVOY-293 fix NPE in WAR export copyRequiredBundles [ai]
4. Post-Modification Compilation & Quick-Fix Loop
After making any code modifications or creating files using the Eclipse MCP tools, you must execute a self-verification compile loop:
- Check for errors: Call
eclipse-ide_getCompilationErrors()immediately to check the build state. - Review quick fixes: If any compilation errors are introduced or identified, look at the returned quick fixes list.
- Apply quick fixes: If a quick fix is applicable and safe, immediately apply it using
eclipse-ide_executeQuickFixby passing the correspondingmarkerIdandproposalIndex. - Re-check: Verify compilation again to ensure the workspace is clean.
5. Known Design Decisions — Do Not Change
SecuritySupport: DESede with hardcoded passphrase (background.gif)
servoy_shared/src/com/servoy/j2db/util/SecuritySupport.java loads a DESede key from a JCEKS keystore embedded in the JAR as background.gif, using the hardcoded passphrase "passphrase".
This is intentional obfuscation, not a security vulnerability. Do not attempt to fix it.
- Its only purpose is to prevent passwords from appearing in plain text inside
servoy.properties. It is not designed to be cryptographically strong — the fact that the key is recoverable from the JAR is a known and accepted trade-off. - The passphrase cannot be externalised:
servoy.propertiesmust be decrypted at startup before any external key source is reachable, so there is nowhere else to put a decryption key. - Replacing the algorithm or key would silently break every existing customer deployment. All
servoy.propertiesfiles that contain encrypted passwords would fail to decrypt with no migration path short of asking every customer to re-enter all credentials. - Automated security scanners will flag
SecuritySupport.javaandbackground.gif. Those findings are accepted and should be suppressed/dismissed, not actioned.
OAuthHandler: sendRedirect(loginFailedUrl) is NOT an open redirect
OAuthHandler.handleLoginFailed() calls resp.sendRedirect(loginFailedUrl) without an explicit origin check. Scanners flag this as an open redirect. It is a false positive. loginFailedUrl is read from auth.optString("login_failed_url") where auth is a JSONObject retrieved from a server-side PassiveExpiringMap that was populated from the developer-defined solution custom properties (solution.getCustomProperties()). The state/nonce OAuth callback parameter is only a random UUID lookup key for that server-side cache — user-supplied request data cannot inject values into the cache. Do not add origin-check validation here based on scanner findings.
Refresh token embedded in Servoy JWT: intentional stateless design trade-off
SvyTokenBuilder.withRefreshToken() embeds the OAuth provider refresh token as a plain JWT claim (refresh_token). Because JWT payloads are Base64-encoded (not encrypted), any holder of the Servoy ID token can decode and read the refresh token.
This is an intentional architectural trade-off, not a bug to fix in application code.
- The Servoy stateless login design stores all session state inside the signed JWT so the server remains fully stateless. Adding server-side refresh-token storage (the textbook fix) would require a shared, persistent, cluster-aware store (Redis, database table) that Servoy does not control and cannot assume is present in all deployments.
- An in-process
PassiveExpiringMapis not suitable: refresh tokens from OAuth providers can live for days or weeks; a JVM-local map is lost on restart and invisible to other cluster nodes. - The additional risk over a stolen access token is that the attacker can silently renew the session after the 2-hour JWT expiry. The mitigations are infrastructure-layer: HTTPS (mandatory — prevents network interception), a strong Content Security Policy (prevents XSS reading localStorage), and short-lived access tokens.
- Do not add server-side refresh-token storage to
SvyTokenBuilder,OAuthHandler, orCloudStatelessAccessManagerwithout first providing a cluster-safe, persistent backing store. Do not remove therefresh_tokenclaim without a tested replacement for the refresh and revoke flows that depend on it.
Rate limiting: application-layer throttling is an infrastructure concern
Servoy does not implement in-process rate limiting or exponential-backoff delays on authentication endpoints. This is intentional:
- In-memory per-IP or per-user counters break in clustered deployments — each JVM node only sees its own traffic share.
- The correct layer is the infrastructure in front of Servoy: nginx/Apache
limit_req, a WAF, or a cloud load-balancer rule. - Failed authentication attempts are logged at WARN level via the
stateless.loginlogger, including username and source IP (never the password). SIEM or log-aggregators should tail this stream for alerting and blocking.
Do not add in-process Thread.sleep() or in-memory attempt counters to StatelessLoginHandler, DefaultLoginManager, or OAuthHandler to address scanner findings about brute-force exposure.
6. Spotbugs Error Resolution
Spotbugs is used to find bugs in Java code. You must pay special attention to Spotbugs issues:
- Identify Spotbugs Errors: Spotbugs errors of the two highest severity levels are treated as blocking errors.
- Proactive Fixing: Always try to fix these Spotbugs errors in any new or modified code to keep the codebase robust and clean.
7. Jira API
When asked to create, update, or link Jira issues, load the instructions from JIRA.md in this repository.
8. Testing
- SVY-21475 WebCustomType name property lost after restart:
servoy_ngclient.tests→com.servoy.j2db.persistence.WebCustomTypeNamePropertyTest[JUnit 5, plain] — mirrors theWebCustomTypeAddChildTestscaffolding (DummySolution,TestableWebComponent,PropertyDescriptionBuilder-builttabcustom type withname/textsub-properties, matching the realbootstrapcomponents/servoydefaulttabpanel specs) to cover the sharedWebCustomTypefix:SameSessionReadAfterWriteassertssetProperty("name", …)/getProperty("name")andsetName(…)/getName()round-trip within the same instance (sanity, already passed before the fix);AfterDeveloperRestartSimulationcaptures the parentWebComponent's own JSON and feeds it back intosetJson(...)(which internally callsinitCustomTypes()and reconstructs brand-newWebCustomTypechild instances purely from JSON, simulating an Eclipse restart/solution reload with no reflection) and asserts the freshly-reconstructed child'sgetProperty("name")andgetName()still return the persisted value — the assertion that returnednullbefore the fix — plus a no-regression check that an ordinary sub-property (text) still round-trips.WebCustomTypeAddChildTest's own 21 tests were re-run and continue to pass unmodified. - SVY-21257 duplicate UUID on AG Grid copy/paste (override/shared-reference path):
servoy_ngclient.tests→com.servoy.j2db.persistence.WebComponentCloneTest[JUnit 5, plain — lives besideWebComponentCloneMapIsolationTestand reuses itsDummySolution/anonymousAbstractPersistFactoryscaffolding, the factory returning aTestableWebComponentfor WEBCOMPONENTS so the clone keeps its testPropertyDescription] — covers the follow-up fix toWebComponent.cloneObj()where the initialcustomTypesInitialized=falsefix was insufficient. TheTestableWebComponentcarries a testPropertyDescriptionwith aCustomJSONArrayTypecolumnsproperty and an overridablegetFlattenedJson(), so the fullinitCustomTypes()→resetUUID()flow runs without OSGi.RealFormDuplicate#duplicateInSameFormGetsNewUUIDsreproduces the reportedsvyCloud/billingHistory.frmcase: an 8-columnaggrid-groupingtablewhose custom types are first realized (as in a rendered editor, so eachWebCustomTypechild shares the exactcolumns[i]JSONObject instance in the parent's stored JSON), then duplicated within the same form viacloneObj(parent, true, validator, true, true, true); asserts the clone's columnsvyUUIDs (child persists and stored JSON) are all new, the source is unchanged, and clone child/JSONsvyUUIDs stay in sync.SharedReferenceOverrideClonecovers theextendsID != nullpath wheregetFlattenedJson()returns column objects shared by reference with the source;cloneUUIDsDifferFromSourceis the assertion that failed before the fix (sourcesvyUUIDs were mutated by the clone'sresetUUID()),cloneChildUUIDsMatchStoredJsonis the intra-clone isolation guard. The fix deep-clones the flattened JSON (ServoyJSONObject.deepCloneJSONArrayOrObj) before storing it on the clone. Reverting the deep-clone makescloneUUIDsDifferFromSourcefail.WebComponentCloneMapIsolationTest(SVY-21282, 7 tests) andWebCustomTypeAddChildTest(21 tests) were re-run and continue to pass unmodified.
Thank you for keeping the Servoy Runtime codebase healthy, compilation-error free, and highly consistent!