Skip to content

JVM feature parity reference

StatusMeaning
ImplementedAvailable and covered by source tests, contract tests, integration tests, or runnable samples.
JVM-specificSupported with an intentionally JVM-native shape.
PartialUsable behavior exists, with the stated boundary still remaining.
Not plannedOutside the Spring Boot, model-bound framework direction.
AreaStatusCurrent JVM contract
Model-bound commandsImplemented@Command plus one public instance handle; KSP generates reflection-free handlers.
Kotlin command asyncJVM-specificsuspend handlers run in an application-owned, bounded coroutine scope.
Java command asyncJVM-specificGenerated adapters await CompletionStage without blocking request threads.
Command provideImplementedRegular, suspend, and CompletionStage preparation supports ordered provided values, control short-circuiting, and service fallback without runtime method reflection.
Tuple and alternative returnsImplementedKotlin Pair/Triple, CommandProvidedValues, CommandResponseValues, and ArcOneOf alternatives are flattened in declaration order.
Aggregate command responsesImplementedRuntime and KSP recursively flatten Pair, Triple, ArcOneOf, and nested CommandResult values in declaration order. Source-visible @HandlesCommandResponseValues declarations classify custom server-consumed leaves, and built-in event/control leaves are handled consistently. Exactly one client leaf is projected into runtime, proxy, and OpenAPI metadata; multiple client leaves fail with ARCKSP0109, while handled-only commands expose no fake client response. Binary annotations alone remain undiscoverable: the JVM-specific dependency declaration resource and tracked index are required (see configuration); ArcResponseHandlerMetadataFunctionalTest exercises binary producer/consumer classification. Erased CommandResponseValues contents remain untyped metadata.
One-shot queriesImplementedStatic Java or @JvmStatic companion methods on @ReadModel; class-level @QueryHttpMethod defaults with method overrides; non-query companion/static helpers ignored unless explicitly query-annotated, while custom instance methods remain invalid; GET and optional RFC QUERY hosting with case-insensitive client argument names and fail-closed case collisions; generated performers inject interspersed service, QueryRequest, and QueryContext parameters without exposing them as client input. Omitted Kotlin client defaults execute at invocation, while supplied values, including explicit null, retain presence.
ConceptsImplementedKotlin classes and Java records implementing ConceptAs<T> serialize as their underlying scalar or enum value. KSP carries that shape through command, query, model, response, validation, TypeScript, OpenAPI, and Chronicle command-key contracts rather than exposing wrapper objects.
Polymorphic derived typesImplemented@DerivedType writes _derivedTypeId, and code generation records every base-to-derivative mapping it saw — the declared interface plus each base class — on the generated ArcArtifactModule as real class references. The Spring Boot starter registers them into a DerivedTypeRegistry bean before Jackson reads anything, and ArcArtifactModuleRegistry.registerDerivedTypes does the same for explicit in-process registration; a DerivedTypeRegistrar bean adds a hierarchy code generation never processed, such as one that arrives only as a dependency binary. Arc .NET scans every loaded assembly at startup instead, so the JVM needs that registrar where .NET needs nothing. Reading fails closed on an identifier the registry cannot resolve, which matches .NET, and also when the discriminator is absent, where .NET returns null — an intentional divergence.
Temporal and UUID proxiesImplementedDirect and concept-backed LocalDate, LocalTime, and UUID generate DateOnly, TimeOnly, and Guid from @cratis/fundamentals; UUID-to-Guid is an explicit .NET parity decision. Commands and GET query parameters serialize scalar strings, and returned generated models hydrate as class instances. Core and Spring disable WRITE_DURATIONS_AS_TIMESTAMPS, so Duration reads and writes ISO-8601 text, generates TypeScript string, and is OpenAPI string/duration; it is not Fundamentals TimeSpan because Java and C# wire formats differ. Tested Period behavior is limited to Core ISO-8601 round-trip and TypeScript string generation. OffsetTime generates as textual, untyped string; its offset-specific semantics are not hydrated into a class. LocalDateTime, Instant, OffsetDateTime, and ZonedDateTime remain JavaScript Date.
Observable queriesImplementedObservableState<T> is the Java-native counterpart of MutableStateFlow: a Flow.Publisher<T> that holds a current value, so a Java observable query answers a snapshot GET from it exactly as a Kotlin query returning a StateFlow does. Without it a Java application could only publish through a plain publisher, which has no current value and is therefore reported as not-ready on every snapshot however promptly it emits. ObservableStateTest and ObservableStateJavaConformanceTest cover replay, conflation, demand, cancellation and the asKotlinFlow unwrap. Primary reactive types are Kotlin Flow<T> / Flow<List<T>> and JDK Flow.Publisher<T> / Publisher<List<T>>; the optional arc-rxjava3 artifact also accepts RxJava 3 Observable<T>, ObservableSource<T>, and Subject<T>. Arc .NET uses IObservable<T> / ISubject<T>; the JDK primary equivalents are Flow.Publisher<T> and MutableStateFlow / SubmissionPublisher respectively, requiring no external dependency. reactor.core.publisher.Flux is rejected to preserve the Spring-free Source boundary; org.reactivestreams.Publisher is rejected because it is superseded by the JDK type. Observable performers use revision ordering, full/delta transfer with the omitted-mode legacy snapshot plus change set, renderers, interceptors, and per-emission guards; keyless or invalid-key collection deltas use Arc .NET-compatible serialized JSON set identity and surface field changes as remove plus add; infrastructure parameters are injected rather than subscription-bound, and omitted Kotlin client defaults remain omitted through every binder.
Observable HTTP and hubsImplementedBounded GET and enabled RFC QUERY HTTP snapshots that serve a StateFlow source’s current value with 200 and report a source without one as not-ready with 202, direct GET SSE/WebSocket, multiplexed SSE/WebSocket hubs, revisions, heartbeats, caller/tenant capture, and terminal authorization behavior. cratis.arc.observable-queries.allowed-origins names the browser origins allowed to open a WebSocket; empty keeps Spring’s same-origin default, which a deployment wants and which makes every handshake from a separate dev server fail with a 403 a browser reports only as a socket that never opens. ArcObservableQueryWebSocketOriginTests reads the origins off the handler mapping Spring built. Fixed multiplexed routes are connection-anonymous while successful credentials are captured; every subscription is independently authorized and isolated. WebSocket support remains optional by classpath and configuration.
Query paging and sortingImplementedGET reserved parameters, structured QUERY bodies, QueryPage, calculated paging metadata, and iterable fallback rendering. QueryPagingSortingExecutionTest, QueryPagingSortingJavaConformanceTest, and ArcQueryPagingSortingHostingTests execute nonempty data through the default pipelines: iterable results sort before zero-based paging and retain the pre-page total even when descriptor flags are false; provider-owned QueryPage items/order/totals remain unchanged. Original arrays are normalized for output but bypass iterable sorting/paging/counting. QueryablePropertyAccessTest, QueryablePropertyAccessJavaConformanceTest, and ArcQueryPropertyAccessHostingTests cover public-only accessor selection, singleton/mixed-type rejection, Kotlin declaration visibility across Java inheritance, public bean/record getter semantics, and cancellation/fatal propagation. Private storage is not read as a substitute for a public accessor; inaccessible and absent keys fail without a sorted payload and host exception details remain redacted. This is a JVM visibility safeguard, not a serialization-annotation or per-field authorization policy. Generated convenience methods remain separately capability-driven; no automatic array paging or general .NET backend equality is claimed.
Query extension pointsImplementedOrdered QueryRendererFor<T> and InterceptReadModel<T> contracts apply to one-shot and observable results; GuardObservableQueryEmission rechecks each observable emission, and a suppressed emission leaves first-delivery status to the next delivered one. Configured renderers match the original value and own data/paging; the automatic iterable renderer is a fallback only when none match. Explicit QueryableQueryRenderer stages consume current data and preserve null/non-iterable projections, never restoring original rows. QueryRendererOwnershipTest, its Java conformance counterpart, and ArcQueryRendererOwnershipHostingTests cover discarded rows, projections, provider totals/no premature source enumeration, explicit ordering, cancellation, and both query transports. Existing application renderers—including identity/logging renderers—must explicitly opt into in-memory processing where wanted; this is a JVM behavioral correction, not a claim of .NET renderer equivalence.
Query healthImplementedQueryHealthTracker exposes snapshots and a Flow; /.cratis/queries/health exposes the current connection/subscription health. Intentional divergence: Arc .NET declares the health read model [AllowAnonymous], while the JVM host requires an authenticated caller on both GET and QUERY whenever authentication handlers are registered, because the snapshot reports connection and subscription identifiers, remote IP addresses, user agents, and user identities. Applications with no authentication handler are unaffected.
Spring Data JPAJVM-specificRepository injection, generated QueryRequest/Pageable/Sort injection, exact Page<T> normalization, DECLARED contextual command read-model ownership, tenant-certified persistence units, and cold/shared observable Flow snapshots with demand-aware Java publishers. JPA notifications are explicit and in-process by default. Imperative command transactions are fixed-store, thread-bound opt-ins.
Spring Data MongoDBJVM-specificRepository injection, generated QueryRequest/Pageable/Sort injection, exact Page<T> normalization, FALLBACK contextual command read-model ownership, tenant-certified operations, and cold/shared observable Flow snapshots backed by reconnecting change streams with demand-aware Java publishers. Change streams require a replica set or sharded cluster. Imperative command transactions are fixed-store, thread-bound opt-ins.
Command and query-model validationImplementedArc CommandValidator and QueryValidator contracts, severity thresholds, command validate routes, and stable envelopes apply in the real pipelines. When a Spring Validator bean exists, Jakarta validation automatically covers command and client-supplied typed one-shot/observable query-argument graphs; nested @Valid collections/maps and cycles retain stable member paths, while infrastructure parameters are neither required nor client-validated. An omitted Kotlin default has no pre-invocation value, so executable violations for that slot are ignored and the default executes at the model boundary.
Concept validationImplementedReusable ConceptValidator<TConcept> rules apply across command and query object/record/collection/map graphs when supplied to the default validation filters. Spring auto-discovers ordered concept-validator beans alongside command/query validators, while replacement default filter beans remain authoritative (ArcConceptValidationTests, ConceptValidationJavaConformanceTest, and ArcConceptValidationHostingTests in Integrations/SpringBoot). Imperative bean rules remain server-only, separate from generated Jakarta validation metadata.
Shared fluent model validationJVM-specificFluentModelValidator<T> supports thirteen bounded literal rules, Kotlin/ordinary Java constructor authoring, generated module registration and concrete acyclic nested client composition. ContractTests/FluentValidationContractTest, runtime.fluent.contract.ts and ArcFluentValidationNativeFunctionalTest prove generated registration, real JVM/client rejection and indexed library invalidation. Dynamic/async predicates, warning severity, polymorphic/cyclic graphs and shared-model GET/observable transport remain unsupported; legacy annotation credit-card constraints remain server-only. See shared validation.
Member validation opt-outJVM-specific@IgnoreValidation cuts a logical member edge before getter/extractor access, including direct rules and descendant validation, without pruning authored fluent fingerprints or wire/binding metadata. Source/IgnoreValidationTest, Integrations/SpringBoot/ArcIgnoreValidationHostingTests, ContractTests/IgnoreValidationContractTest, IgnoreValidationJavaContractTest, and runtime.ignore-validation.contract.ts cover runtime, real Jakarta/HTTP, generated scenarios and real client/server behavior. ArcFluentValidationNativeFunctionalTest proves annotation/dependency edits, recovery and fresh-output equivalence. Owner imperative/class/group-sequence callbacks and executable parameter constraints remain active. Runtime bean-only getters are an explicit expansion; active shared graph limits and the fail-closed binary-record boundary remain. Manifest 8 requires producer/consumer regeneration; custom opaque validators require explicit factory integration. See the bounded contract.
Validation metadataImplementedKSP preserves exactly representable Jakarta constraints, recursive validation, and inherited concept rules in manifests/runtime descriptors. TypeScript validators merge concept and owning-member rules; unrepresentable client rules fail with ARCKSP0301. Phone and Url have client rules; credit-card constraints remain server-only because the pinned client runtime lacks one. JVM annotation coverage is intentionally broader than .NET DataAnnotations (Required, StringLength, MinLength, MaxLength, Range, RegularExpression, EmailAddress, Phone, Url, CreditCard): Hibernate @Range, Hibernate @Length, and Jakarta @Digits are additionally supported on the JVM. Annotation and DSL rules deliberately union rather than following ValidationRulesExtractor.MergeValidationRules precedence, because on the JVM both Jakarta and the DSL execute server-side — precedence would make the generated client weaker than the server. See conjunction, duplication and contradictions.
AuthorizationImplementedAnonymous, policy, role, and scheme metadata works through Arc pipelines and Spring principal capture. Operation-level metadata — @AllowAnonymous, @Authorize or @Roles — replaces the class declaration wholesale, matching Arc .NET’s AuthorizationEvaluator.IsAuthorized(MethodInfo), which resolves the method’s own metadata first and falls back to the declaring type only when the method declares none. An operation therefore both narrows and opens: a @Authorize read model can expose one @AllowAnonymous query, which is what a login screen needs, and an @AllowAnonymous class can close one operation with @Roles. ArcSymbolProcessorAuthorizationPrecedenceCompilationTest covers both directions through generated metadata and the real AuthorizationEvaluator, and the Kotlin sample’s AuthenticationQueryItem exercises the widening case over HTTP. Remaining intentional divergence, narrower than before: @AllowAnonymous combined with @Authorize/@Roles on the same declaration is a compile-time ARCKSP0108 error, where .NET silently prefers anonymous.
AuthenticationImplementedOrdered coroutine AuthenticationHandler and Java AsyncAuthenticationHandler chains run before Arc endpoints; the first handler that recognizes the request decides, a recognized failure is terminal and cannot be overridden by a later handler, and anonymous is the outcome only when no handler recognized the request. Failures are generic, the chosen principal is propagated, and the identity cache cookie is excluded from credentials. Multiplexed observable transport connections may remain anonymous, but protected query subscriptions still fail through the query authorization pipeline.
Host correlationImplementedA servlet filter on /* establishes one correlation identifier per request for every route in the host, Arc-owned or not, matching the placement of the Arc .NET CorrelationIdMiddleware. It is ordered ahead of Spring Security’s filter chain and the Arc authentication filter, reuses an inbound X-Correlation-ID only when it is a UUID, re-emits the canonical value as the request and response header, exposes it through ArcCorrelation.of(request), and binds it to SLF4J MDC for the servlet thread only. Coroutine-visible correlation still comes from CommandContext, QueryContext, and the observability starter’s thread-context element.
IntrospectionImplemented/.cratis/commands and /.cratis/queries return deterministic generated route, schema, authorization, transport, paging, and documentation metadata and omit infrastructure-owned query parameters. Defaulted client parameters report hasDefault, are absent from schema required, and expose no expression or invented value. UUID and supported terminal textual java.time values (LocalDate, LocalTime, LocalDateTime, Instant, OffsetDateTime, ZonedDateTime, OffsetTime, Duration, and Period) are scalar string schemas, including collection elements, rather than recursively described objects.
IdentityImplementedCoroutine or Java asynchronous details provider, /.cratis/me, schema generation, client-readable identity cookie, and optional-provider startup validation.
Trusted platform identity headersJVM-specificDefault-off Spring Security AuthenticationConverter and once-per-request chain filter consume the three x-ms-client-principal headers only through an explicit trusted-ingress policy (default deny). Canonical ID, name, roles, and provider collision handling follow the inspected Arc.Core/Identity/MicrosoftIdentityPlatformAuthenticationHandler.cs at Arc .NET 03200c1c52020e60e5d55c5ed38b5b189a899504; stricter bounded parsing and partial-submission rejection are deliberate JVM boundaries, not raw handler equivalence. SpringBoot’s ArcPlatformAuthenticationConverterTests, ArcPlatformIdentityAutoConfigurationTests, ArcPlatformIdentityHostingTests, ArcPlatformIdentityDisabledTests, ArcPlatformAuthenticationFilterTests, and compiled PlatformIdentityJavaConformanceTest/PlatformApplicationChainJavaConformanceTest prove parsing, optionality/backoff, real /.cratis/me, role/scheme/claim authorization, CSRF, existing identity preservation, and once-only execution. Application chains remain authoritative; the opt-in default protects every route. Unsigned base64 never proves sender identity; deployment ingress isolation/authentication remains the application’s responsibility.
Users and tenantsImplementedAnonymous /.cratis/users and /.cratis/tenants aggregate ordered coroutine and Java asynchronous providers, deduplicate by identifier, and return redacted failures.
TenancyImplementedFixed/header/query/claim/subdomain/development resolvers, ordered composition, fail-closed required mode, claim-membership checks, request/subscription capture, and application override beans without thread-local state.
Kotlin ergonomicsJVM-specificReified service lookup; result fold/getOrThrow/onSuccess/validationOrNull; ordered response factories; and property views for Java-method contracts are Kotlin-only conveniences that preserve the Java ABI.
Java Core adaptersJVM-specificBlocking and CompletionStage adapters cover command/query filters, authorization filters and policies, validators, command scopes and response handlers, and manual artifacts. JavaAsyncScope provides cancellable command/query/authentication/observable/health facades over a caller-owned executor. Caller-thread BlockingCommandPipeline execute/validate and BlockingQueryPipeline perform facades require explicit per-call or constructor-bound options; Spring supplies backing-off unbound beans. Source’s BlockingPipelineTest, BlockingRealPipelineInterruptionTest, and BlockingPipelineJavaConformanceTest, plus Integrations/SpringBoot’s ArcBlockingPipelineTests and BlockingPipelineJavaConformanceTest, cover context/results, validate nonexecution, cancellation/interruption cleanup, migrated bounded-scope/reentry rejection, and Java service injection. Blocking invocations normalize directly observed callback InterruptedException (including propagated worker exceptions) to cancellation after cleanup and set the caller interrupt flag; this does not prove physical caller interruption, and swallowed interruption is undetectable. Legacy coroutine and guard-only calls retain their behavior. These are opt-in blocking calls, not another async bridge: no hidden timeout or ambient identity, no arbitrary external-coroutine detection, and no guarantee that cancelling a stage stops external work; see Java calling limits.
TypeScript one-shot proxiesImplementedCommands, queries, models, interfaces, derived types, enums, flags, paging hooks, validators, authorization metadata, and Kotlin/Java concepts erased to their underlying client types compile in strict mode against real @cratis packages. Query interfaces, descriptors, required arguments, properties, routes, validators, hooks, and imports include only client parameters; service, QueryRequest, and QueryContext parameters are omitted. Defaulted Kotlin parameters generate optional fields and call arguments and are excluded from requiredRequestParameters without copying a server literal. Strict contracts enable verbatimModuleSyntax, and generated interfaces use type-only imports where appropriate. Aggregate command metadata selects the single client leaf’s exact type and enumerable shape; handled-only commands use the response-less command contract. Concept validation rules merge with owning command/query rules. The DateOnly/TimeOnly/Guid change is source-breaking for consumers that assigned Date or scalar strings.
TypeScript observable proxiesImplementedEnumerable and single-result observable classes, React hooks, paging/sorting/change-stream helpers, conditional queries, validation, transport, and HTTP preference metadata omit infrastructure parameters; Kotlin defaults are optional capability metadata without emitted values. For both query transports, named sorting helpers use return-model properties (including represented class-base properties), not request arguments; capability flags remain authoritative. QuerySortHelperTest and ArcProxyMappingRuntimeTest cover parameterless/parameterized queries, inherited fields, request-field exclusion, collision-safe helper storage and actual sorting actions; GeneratedTypeScriptProxiesTest asserts the real KSP-produced Kotlin/Java Spring Data clients select returned value rather than request label. The pinned .NET model-bound generator derives helper names from parameters, so this is a documented JVM correctness divergence. Provider-specific field support and unrepresented interface inheritance are not inferred.
.NET-derived proxy differential gateImplemented:GradlePlugin:test compares sorted paths for all regular output files, then untouched JVM bytes (including headers) against the prepared repository-local expected fixture. Expected-only preparation validates 16 source identities against a literal table and fixture descriptors, reconstructs uppercase SHA-256 headers independently from expected bodies, and leaves three indexes headerless. Queries use declaring-model identities. The complete preparation inventory includes expected LF/trailing-whitespace handling, FixtureModel quote/indent preparation, CreateFixtures quote/import/layout/hook preparation and exact-site suppression, and five literal type-only import rewrites for verbatimModuleSyntax. Historical namespace/query-name casing and removed timestamps/hashes remain embedded capture-time transformations. SetCommandValues, ClearCommandValues, and query helper types such as PerformQuery, SetSorting, SetPage, SetPageSize, and ChangeSet become type-only imports. At Commands/CreateFixtures.ts, one additional expected-side correction changes Command<ICreateFixtures, FixtureModel> to Command<ICreateFixtures, FixtureModel[]>; .NET already calls super(FixtureModel, true), so the scalar generic contradicts its enumerable runtime behavior. Only Models/Observe.ts also receives one exactly anchored expected-only formatting change: the zero-space blank line immediately before ObserveParameters.filter: string; becomes four spaces; ObserveOne.ts is excluded. ExpectedSortHelperPreparation additionally replaces the exact helper pairs in Models/All.ts, Models/Search.ts, and Models/Observe.ts with a literal return-field inventory after verifying unique anchors and fixed original block hashes; only All gains SortingActions imports, and the helper constructor no longer exposes its owner as query. Request parameters, routes, hooks and capability flags remain unchanged. This is an explicit semantic correction for the JVM’s returned-row helper behavior, not raw .NET output parity. Missing/duplicate correction anchors and misplaced suppressions fail preparation. Actual byte/path mutation tests include a changed body with a valid recomputed hash. Contracts/Shape.ts is a class, not interface-emission proof. No normalization transforms JVM output. Capture SDK/tool versions remain unverified. The fixture currently contains no Guid, DateOnly, or TimeOnly, so focused generator and contract tests cover that mapping. This remains a drift gate for the normalized fixture, not an exact raw .NET-output comparison; capture-time fixture preparation is not yet reproducible tooling.
TypeScript proxy and runtime gatesImplemented:ContractTests:typeScriptBuild installs the locked real @cratis packages, verifies deterministic regeneration, and compiles generated proxies in strict mode with verbatimModuleSyntax and type-only interface dependencies. The runtime harness has five wired unit tests plus 33 behavioral runtime tests: four ignore-validation tests, four shared-fluent tests and 15 general tests in separate UTC processes, five calendar tests in separate UTC, and five in separate America/Los_Angeles processes. Parsed TAP summaries require exact test/pass totals and zero fail, cancelled, skipped, or todo results; Spring spawn errors fail cleanly. The behavioral gate verifies validation, typed and hydrated command responses, safe malformed envelopes, correlation, GET/QUERY, paging/sorting, identity, multiplexed/direct WebSocket, direct SSE, scalar calendar/UUID transport, timezone stability, and TimeOnly millisecond truncation; :ContractTests:check depends on it.
Chronicle event transactionsImplementedReturned events are staged until the final command result succeeds and committed as ordered appendMany batches per selected store. Generated command event metadata supplies optional source type, stream type, stream ID, and subject defaults to immediate and staged appends; explicit routed values win and command causation is appended. A collection may mix plain events routed by the captured CommandContext.commandKey with explicit EventForEventSourceId values; item order and explicit source IDs are preserved and malformed mixtures fail before append. ChronicleCommandKeyRoutingTests and its Java conformance counterpart prove stateful key providers are not resolved a second time, including null-key rejection and explicit-route preservation. Separate enrollments from one execution frame reuse one generated command causation link. ChronicleStagedCausationTests exercises the pinned real SDK preflight with mocked gRPC transport: different explicit or nested-frame chains remain rejected under its single-chain batch contract, not flattened or split. This does not prove real-kernel acceptance or heterogeneous nested-command lineage support. Failed commands discard staged events. No cross-store distributed transaction is provided.
Chronicle concurrencyImplementedEventsWithConcurrencyScopes carries ordered routed events and exact per-source Chronicle scopes through Kotlin and Java builders; violations map to machine-readable validation feedback.
Chronicle read modelsImplementedChronicle-discovered read models are declared to the command resolver registry, resolved by generated command key and tenant store, and released through a query interceptor before one-shot or observable results leave the pipeline.
Chronicle command side effectsImplementedReactors can explicitly hand registered command values to ChronicleCommandSideEffectHandler; nested aggregates run sequentially through the real pipeline and stop on failure. Causing identity is preserved without roles unless the reactor declares exact system roles.
Chronicle scenariosImplementedA ServiceLoader extender gives CommandScenario an in-memory event log, ordered given history, append assertions, and deterministic constraint/concurrency rejection, with Kotlin extensions and Java static bridges. No Chronicle kernel is started.
OpenAPIImplementedThe optional starter generates and caches OpenAPI 3.1 from artifact metadata, including reusable underlying scalar/format/enum schemas for Kotlin and Java concepts, and omits infrastructure-owned query parameters. Kotlin defaulted client parameters are non-required and carry no invented OpenAPI default. UUID, date, time, and duration wire values are scalar strings with uuid, date, time, and duration formats. Command result schemas expose only the unique client response leaf from ordered aggregate metadata and omit response for handled-only commands. The starter serves /v3/api-docs plus /.cratis/openapi.json; application beans/routes win. RFC QUERY is omitted because OpenAPI Path Items do not define it. Imperative concept validators are runtime behavior, not OpenAPI keywords.
ObservabilityImplementedThe optional starter decorates command execute/validate, one-shot and observable query open/subscription/emission, authentication, and identity-details execution with Micrometer observations. Stable low-cardinality tags exclude tenant, user, command values, and query arguments; correlation uses observation context plus optional OpenTelemetry baggage and SLF4J MDC.
Source documentationImplementedKSP reads Kotlin KDoc and Java Javadoc for commands, query methods, client query parameters, model types and properties, interfaces and properties, and enum declarations, and the manifest carries them as single-line summaries that runtime introspection and generated TypeScript JSDoc render. A member falls back to a Kotlin @property or Java @param tag; enum members, setters, backing fields, runtime query fields, and barrel files are deliberately undocumented, and OpenAPI keeps conventional descriptions. Only the first paragraph is captured, bounded at 512 Unicode code points. Comparable Arc .NET behavior has not been demonstrated here and is not claimed.
KSP diagnosticsImplementedStable ARCKSP codes cover configuration, commands, queries, proxy shapes, Jakarta validation metadata, enum values, interoperability warnings, and unclassified failures.
Measured cross-runtime proxy agreementPartialArcGradlePluginFunctionalTest generates JVM proxies for Kotlin declarations mirroring the captured .NET fixture and compares them file by file against differential/captured, with both generators at segmentsToSkip 0. Confirmed from real output on both sides rather than from JVM-side assertion: the generated artifact sets match; routes match exactly; Address.ts and OrderKind.ts are byte-identical apart from the namespace casing each runtime echoes into the header Source field, with identical content hashes, which the test asserts so it cannot silently regress; and Guid, DateOnly, TimeOnly, DateTimeOffset/OffsetDateTime, enum, and nested-model mappings agree. Cosmetic divergences remain: .NET sorts members alphabetically while the JVM preserves declaration order, .NET emits one import per @cratis/fundamentals type while the JVM combines them, and the JVM emits type-only imports for verbatimModuleSyntax. Generated paging/sorting remains capability-driven on the JVM: QueryPage and exact Spring Page returns also advertise paging without adapter parameters. Core’s default renderer already sorts/pages runtime Iterable values independently of those flags, but KSP does not advertise that in-memory capability. Arc .NET’s enumerable helper generation is not proof of server behavior: at v22.14.0 its default renderer handles runtime IQueryable, not an arbitrary list or array. The inspected implementation and documentation establish this distinction; the empty capture fixture itself cannot prove pagination. CapturedProxyContract now checks exact relative paths (not basenames), capture checksums, fixed header identities, model/enum full bytes after expected-header-only preparation, and independent literal command/query contracts for routes, members, constructors, collection shape, parameters and methods. CapturedProxyContractTest mutates those contracts and file inventories to prove mismatches fail, including duplicate basenames and renewed hashes. This is Partial: command/query comparison is a bounded token contract with explicit import/order and capability exceptions, not raw byte equality, type-soundness of ignored hook calls, or general equivalence. JVM output bytes are never rewritten.
Reproducible Arc .NET proxy capturePartialGradlePlugin/src/test/resources/differential/capture/capture.sh regenerates real Arc .NET proxy output from published NuGet packages at pinned Cratis.Arc and Cratis.Arc.ProxyGenerator 22.14.0, matching Arc .NET’s v22.14.0 tag. It never reads, builds, or copies from a sibling checkout. The hardened harness pins SDK 10.0.400, both reflection/runtime frameworks at 10.0.11, the locked NuGet graph and archive/payload hashes; it preserves raw bytes and writes format-2 provenance with input hashes. Output must be a new lifecycle-managed .ai-work destination; existing paths and symlinks are refused and publication is atomic without replacement. test_capture.py covers corruption, exact normalization, failures and output safety. Two fresh normalized trees were verified equal to each other and the checked-in seven-file comparison fixture; complete raw trees/manifests are intentionally not byte-identical because they retain timestamps. Exactly one normalization is applied, to .NET output only: the generator’s wall-clock Time: header field, which the JVM generator does not emit and which otherwise makes the capture differ from itself every run. The newly authored fixture carries Guid, DateOnly, and TimeOnly, confirming from real .NET output rather than assertion that Arc .NET maps them to Guid/DateOnly/TimeOnly from @cratis/fundamentals, camel-cases enum members behind a Number descriptor, and imports nested models relatively. This is Partial: the older hand-prepared differential/dotnet drift gate remains separate and unchanged. The seven-file captured contract check above is narrower than a whole-generator or server equivalence proof. :GradlePlugin:verifyCapturedProxyBaseline now runs before the ordinary GradlePlugin tests and checks the reviewed seven-file baseline against its input hashes and SDK/runtime/package/tool pins, with mutation tests for stale scripts/locks, byte changes even after renewed snapshot hashes, metadata drift, symlinks and path inventories. This offline consistency gate needs no .NET installation, network, package-cache reads or retained .ai-work capture. verify_baseline.py --candidate requires a full raw/normalized capture and matching current inputs before emitting a review candidate. The receipt is not an attestation: its evidence digest does not by itself prove tool execution, and coordinated changes still require review. Fresh capture/provenance verification remains explicit, not a hidden network operation in ordinary JVM tests, and Linux capture execution has not been verified. No raw .NET output equivalence follows from this harness.
External TypeScript package mappings and type overridesImplementedproxies { mapType(...) } maps a JVM type to a TypeScript type with an optional npm package, and mapPackage(...) maps every model type under a JVM package to an npm package it is imported from by simple name. Mappings are consulted ahead of the generator’s built-in type map, so an entry corrects an existing mapping as well as declaring an unknown one; concepts unwrap first, so the underlying type is what a mapping matches, as in Arc .NET’s TypeExtensions.GetTargetType. A mapped type is excluded from emission, matching .NET’s IsFromMappedAssembly filter, so a local declaration and an external import never collide. The longest matching package wins. Blank components are named in warnings; unsafe or unrepresentable nonblank mappings and import collisions fail before output writes. Unpackaged overrides are limited to string, number, boolean, object, and Date with actual runtime constructors; manifest interfaces and numeric enums retain Object/Number descriptors and type-only imports. External classes require compatible Fundamentals field metadata; mappings never change server JSON. External map-value and polymorphic-base/derivative hydration remain unsupported. The standalone CLI takes the same values as repeatable --type-to-typescript and --package-to-npm options, with the same bounded three-part split. ArcProxyTypeMappingTest covers resolution, diagnostics, collision rejection and stale-file transitions. ArcProxyMappingRuntimeTest strictly compiles generated clients against the locked packages and executes primitive overrides, nested/list/interface/enum hydration and mapped command/query responses through a local HTTP fixture. ProxyMappingsJavaConformanceTest executes the public Gradle task/DSL from Java. Dependency declaration checking uses the repository’s existing skipLibCheck setting; generated TypeScript remains checked. The JVM keys package mappings on a JVM package prefix because it has no assembly identity to key on; .NET’s --exclude-type, --exclude-namespace, and --namespace-root options have no equivalent and none is claimed.
Artifact roots without commands or queriesJVM-specific@ExportedType makes a public top-level concrete class, enum class, interface, or concept an explicit artifact root; unsupported targets fail with ARCKSP0306, covered by ArcSymbolProcessorExportedTypeCompilationTest and Kotlin/Java negative fixtures. Public concrete source identity providers and typed factory returns now automatically contribute supported details roots, including inherited generic specialization and anonymous providers behind typed factories. ArcSymbolProcessorIdentityCompilationTest covers Kotlin, ordinary compiled Java and later rounds; ArcIdentityDiscoveryFunctionalTest covers native correction, addition/removal and fresh-output equivalence. GeneratedIdentityArtifactsTest, IdentityArtifactsJavaContractTest and GeneratedTypeScriptProxiesTest cover the module, artifact manifest and generated client. The Kotlin sample generates SampleIdentityDetails.ts without an explicit export annotation. Identity-only compilations still emit the module, service entry and empty command/query lists. Erased, wildcard/star, unspecialized, inaccessible or unsupported details boundaries fail with ARCKSP0307. Discovery reads declarations, not bodies or runtime beans; dependency-only providers are not scanned wholesale, though source-visible bindings can use dependency generic signatures. Explicit export remains a fallback for supported source DTOs without such a declaration. No .NET output equivalence, assembly scanning, bulk DTO export or --library-mode equivalent is claimed.
Binary compatibilityImplementedChecked .api baselines and apiCheck cover published runtime modules, integrations, testing, arc-ksp, and arc-gradle-plugin; changes require an intentional baseline update. Manifest format 8 adds explicit property validation-ignore metadata after format 7’s optional typed command event metadata, while retaining recursive type shapes, canonical query parameter sources, legacy JVM programmatic constructors, and source documentation. IgnoreValidationMetadataJavaConformanceTest calls the earlier property constructors and round-trips the explicit flag; IgnoreValidationMetadataTest rejects prior formats and missing flags. Rebuild metadata producers and consumers together. Earlier command constructor descriptors, including Kotlin default-argument bridges, remain available.
Runtime hardeningImplementedBounded coroutine admission/queueing, request bodies, timeouts, scope completion, observable connections/subscriptions/buffers/messages/tombstones, safe error redaction, and 413/429/503 fail-closed responses are implemented.
TestingImplementedKotlin scenarios and blocking/asynchronous Java bridges exercise real command, query, and observable pipelines with services, filters, validators, authorization, renderers, interceptors, guards, and tenant context. Command-side read models can be pinned to a known state, optionally per command key, through Arc’s own ownership registry rather than a store. A Java-authored temporal contract executes a generated Java command and query through the public JavaAsyncScope and real pipelines. Chronicle adds its in-memory scenario extender rather than a separate fake pipeline.
Roslyn analyzersJVM-specificKSP compile-time diagnostics are the JVM substitute; Roslyn does not apply.
ControllersNot plannedArc generates model-bound Spring MVC endpoints.
Non-Spring hostingNot plannedArc targets Spring Boot; the Spring-free compiler/Gradle boundary does not promise another host.
Static file / SPA fallback hostingNot plannedUse Spring Boot static-resource serving and application-configured Spring MVC fallback facilities, not Arc hosting APIs.
@FromRequestNot plannedExisting QueryRequest/QueryContext injection and Spring binding cover the supported request inputs; no arbitrary request-injection API is promised.
@IgnoreAutoRegistrationJVM-specificCommand/query generation entry points are annotated declarations (@Command and @ReadModel), so those entry points need no opt-out annotation. Identity details additionally become automatic roots through public concrete source providers and typed factory returns, as covered by ArcSymbolProcessorIdentityCompilationTest; this discovery does not register runtime beans. Reachable-type traversal and diagnostic discovery also occur; this does not claim all discovery is annotation-only. ArcSymbolProcessor.process defines these entry points. In CodeGeneration/KSP, ArcSymbolProcessorCommandEventMetadataCompilationTest and ArcSymbolProcessorQueryDefaultsCompilationTest cover annotated command/query generation; ArcSymbolProcessorNegativeCompilationTest covers the missing-command warning.
@GenerateOneOfNot plannedRuntime ArcOneOf alternatives already exist; generating union types is a separate capability and is not planned.
ScreenplayNot plannedNo JVM equivalent is provided; Screenplay is outside this product’s scope. Existing in-process scenarios are not Screenplay parity.
Transparent query and Spring Data framework parametersImplementedGenerated query performers inject exact QueryRequest, QueryContext, Spring Data Commons Pageable, and Sort values in declaration order, normalize exact non-null Spring Page<T> returns, and emit explicit paging/sorting capability flags. Command handlers resolve exact JPA/Mongo read-model parameters through ownership arbitration and captured tenant context; owned absence maps to Kotlin null, Java Optional.empty(), or deterministic dependencyUnavailable validation for required parameters without changing ordinary-service and resolver exception behavior.
Cross-store transactionsNot plannedChronicle, JPA, and MongoDB scopes cannot form one distributed atomic transaction; applications must design for partial completion and compensation.
Paired command/query HTTP conformancePartialExplicit :ContractTests:httpConformanceTest runs nine common cases against three real processes: a newly authored ASP.NET task board from published Arc 22.14.0 (SDK 10.0.400, runtime 10.0.11, locked package/archive checks), plus the existing generated Kotlin and ordinary-Java Spring task boards. ContractTests/HttpConformance/contract.py asserts two distinct create IDs and typed responses, GET/QUERY identifier binding, complete enumerable snapshots, validate-route nonexecution, completion response/state, malformed-command 400 validation without mutation/parser details, and 404 status. Success flags/correlation UUIDs and QUERY cache policy are checked. Raw bodies are retained, not rewritten. The gate is opt-in, database-free, loopback-only, bounded and outside ordinary build/check. Harness tests cover mutation detection and process cleanup. This does not prove equivalent validation rules, authentication/authorization rejection, null/default handling, temporal precision, paging requests, streaming, persistence or aggregate lifecycle. Paging counts and framework 404 bodies demonstrably differ and remain outside shared equality assertions; overall parity is still Partial.
Full Arc .NET parityPartialThe semantically normalized .NET-derived proxy fixture covers selected generated shapes, but neither that fixture nor the project claims exact raw-output compatibility or every Arc .NET feature, analyzer, persistence seam, or hosting model.
  1. Calendar and UUID proxy fidelity — complete. LocalDate/LocalTime/UUID now use DateOnly/TimeOnly/Guid, with scalar command and GET transport, generated-model hydration, strict import contracts, and timezone-separated runtime coverage.
  2. Tenant-safe Spring Data command read models — implemented. Generated Kotlin/Java handlers resolve exact mapped read models through JPA DECLARED and MongoDB FALLBACK ownership. Tenant routing is certified and fail-closed; compatibility adapters remain fixed-store only.
  3. Nested Chronicle transaction ownership — complete. Nested structured executions share one explicit root staging area with no savepoints; ignored child failure/cancellation makes the root rollback-only, late joins fail, and the root emits one ordered appendMany only after local scopes succeed.

The completed ownership slices do not create a distributed transaction. Imperative JPA and MongoDB scopes remain thread-bound opt-ins. MongoDB may commit before JPA fails, and both local stores may commit before Chronicle fails or has an indeterminate external outcome. Applications must continue to design for reconciliation, idempotency, and compensation.

Raw 08:09:10.1235567 hydrates through the pinned TimeOnly as 08:09:10.123; because rounding would yield .124, the runtime contract proves truncation rather than rounding. Arc accepts and emits LocalTime values with up to seven fractional digits for 100 ns compatibility. Deserialization rejects eight or nine fractional digits, and serialization rejects values finer than 100 ns rather than rounding or truncating them. This server binding is distinct from the shared @cratis/arc generated-client limitation: explicit RFC QUERY bodies pass DateOnly or TimeOnly component objects to native JSON.stringify because those classes have no toJSON(). Prefer GET until upstream serialization uses the typed serializer or toJSON(). Guid is unaffected because it has toJSON(), and the JVM server continues to require scalar date/time strings.

JavaScript Date also cannot preserve every remaining JVM temporal distinction: mapping LocalDateTime invents a zone, while mapping OffsetDateTime or ZonedDateTime loses original offset or zone identity.

LocalTime is the only temporal type Arc guards at the 100 ns boundary .NET counts in. Every other type goes through JavaTimeModule as ISO-8601 text and keeps whatever the JVM holds, which for the date-time types and Duration is a full nanosecond — finer than a .NET tick can carry. The table states what the Kotlin mapper does; each row is demonstrated by a test.

JVM typeArc wire valuePrecision the JVM server keepsPrecision the .NET counterpart supports
LocalTimeHH:mm:ss with up to seven fractional digitsExactly 100 ns. A finer value is refused on write, and eight or nine fractional digits are refused on read.TimeOnly writes the round-trip format, which is seven fractional digits, or 100 ns ticks.
LocalDateyyyy-MM-ddNo sub-day component exists to lose.DateOnly writes the same round-trip date.
Instant, ZonedDateTime, LocalDateTimeISO-8601 textFull nanoseconds, so nine fractional digits survive a round trip.DateTime and DateTimeOffset have no Arc converter and use the System.Text.Json default, which round-trips at most seven.
OffsetDateTimeISO-8601 text with an offsetFull nanoseconds are written and read, but a read normalizes the value to UTC, so the original offset does not survive even though the instant does.Same seven-digit ceiling.
DurationISO-8601 textFull nanoseconds.TimeSpan has no Arc converter and uses the System.Text.Json default, which is 100 ns ticks.
UUIDLowercase dashed textExact. A read accepts upper case and normalizes it.Guid has no Arc converter and uses the System.Text.Json default dashed form.

A JVM value with more than seven fractional digits is therefore representable on the Arc wire for every type except LocalTime, and a .NET peer reading it cannot hold the extra precision. Arc does not detect or reject that today outside LocalTime. Applications that must interoperate at exact precision should keep date-time values at or coarser than 100 ns themselves, or model the value as a LocalTime where the guard already exists.