Skip to content

Capability reference

This page is the single status and parity reference for Arc for TypeScript. The shared Arc capability matrix covers C#, Kotlin, and Java; this page records, for TypeScript, whether each capability exists, where its boundary lies, and which spec, contract test, or sample proves it. The wire behavior every implementation follows is the Arc HTTP contract. Other pages link here instead of restating status.

StatusMeaning
SupportedImplemented and covered by passing specs, contract tests, or runnable samples in this repository. Supported describes TypeScript behavior; it is not a parity claim.
BoundedSupported within the boundary the row states.
ExperimentalImplemented and checked, but the API and guarantees can change.
Not implementedNot available.
Not applicableBelongs to another platform’s host or toolchain.

Evidence paths are relative to the repository root. Spec folders follow for_<Subject>/when_<action>. Each row states the contract in brief; where a capability has more boundaries, a bullet under its table, headed with the capability name, lists them.

CapabilityStatusTypeScript contractEvidence
Model-bound commandsSupported@command() classes with Fundamentals @field declarations and an instance handle(); defineCommand with Zod remains the low-level path. See Model-bound commands.Source/Core/commands/modelBound/for_compileCommand, Source/Core/for_ArcApplicationBuilder/when_building_model_bound_artifacts, Samples/Tasks/Features/Tasks/Registration/for_RegisterTask
Artifact discoveryBoundedbuilder.add(...) takes an explicit catalog; discover() imports exported artifacts from a dedicated file-URL folder, derives namespaces from paths, and rejects conflicting namespaces and mixed JS/TS output. No bundler or assembly scanning.Source/Core/for_ArcApplicationBuilder/when_discovering_artifacts, .../when_adding_artifacts
Validate without executingSupportedPOST <route>/validate runs authorization, global command filters, and validation only; a command named Validate executes on its own route.Source/Core/for_ArcServer/when_validating_a_command, ContractTests/Http/conformance.test.mjs
Global command filtersBoundedScoped AuthorizationCommandFilter services always precede CommandPipelineFilter services, independent of registration order. Context-value/key resolution on valid input precedes context-aware authorization filters; these providers/resolvers must not perform protected mutations. Decorators and builder registration support both; result fragments merge and stop on failure on execute and /validate. Express, Fastify, and Hono execute and /validate denial/allow are paired with .NET; TypeScript traces verify ordering and that denial skips validator construction and handling. Existing per-definition CommandFilter<T> callbacks still aggregate. See Command filters.Source/Core/for_ArcApplicationBuilder/when_running_command_filters, Source/Core/for_ArcServer/when_authorizing_by_operation_name, ContractTests/Http/conformance.test.mjs
provide() and outcomesSupportedprovide() runs after validation and may short-circuit with rejected(...) or denied(...). Only helper-created values are outcomes; isOutcome recognizes them. See Command outcomes.Source/Core/for_ArcServer/when_providing_a_command, Source/Core/for_ArcApplicationBuilder/when_providing_a_model_bound_command, Source/Core/commands/for_Outcome
Several return values and response value handlersBoundedtuple(...) flattens branded groups; at most one unhandled value becomes the response, and scoped CommandResponseValueHandlers process every other value in deterministic name order.Source/Core/commands/for_tuple, Source/Core/for_ArcServer/when_processing_command_response, Source/Core/for_ArcApplicationBuilder/when_registering_a_response_handler
Execution scopesSupportedLow-level scopes complete once in reverse order, including a scope whose begin threw; a failed completion removes the response. Model-bound commands use command execution runners instead.Source/Core/for_ArcServer/when_beginning_a_command_scope, .../when_completing_a_command_scope, Source/Core/for_ArcApplicationBuilder/when_executing_command_runners
Command operationsBoundedCommandOperation declarations are preflighted, executed in order, and compensated in reverse after a known uncommitted failure; Unknown and Mixed fail closed. No distributed transaction, durable recovery, or crash guarantee. See Command operations.Source/Core/for_ArcServer/when_executing_an_operation, .../when_an_operation_fails, .../when_a_commit_is_unknown, .../when_declaring_nested_operations
Command context and keysBoundedCommandContext carries the command, a key resolved once, case-insensitive values, and request identity. @key(), getKey(), and scoped CommandKeyResolvers; abortSignal(), commandContext(), and provided(Type) markers. See Command context.Source/Core/for_ArcServer/when_resolving_command_keys, Source/Core/for_ArcApplicationBuilder/when_resolving_command_keys, .../when_resolving_command_arguments
Command read models by keyBoundedcommandReadModel(Type) on handle() or provide() loads by command key through a registered resolver; MongoDB, Chronicle, and Drizzle provide resolvers. Equal ownership claims fail.Source/Chronicle/for_ChronicleReadModelForCommandResolver, Source/MongoDB/for_MongoCollection, Source/Drizzle/for_DrizzleReadModelForCommandResolver
Calling from codeSupportedexecuteCommand, execute(instance), performQuery, and handle(request). See Calling commands from code.Source/Core/for_ArcServer/when_executing_a_command, Source/Core/for_ArcApplicationBuilder/when_executing_an_instance
Controller-based commands and queriesNot applicableASP.NET Core MVC only.
  • Several return values and response value handlers. Ordinary arrays are not flattened. At generation time, Chronicle event types/arrays and integration wrappers plus Arc operations are omitted from command responses. Each alternative path through an Outcome, union, alias, promise, or tuple must have at most one client-visible value, and all paths must use the same client decoder and cardinality; otherwise generation fails with guidance to return one response DTO with an application-owned status field. There is no general client-visible response union or implicit branch discriminator. The paired .NET 22.45.0 fixture pins DTO, primitive, validation, denial, arbitrary error DTO (a 200 success), and tuple alternatives. TypeScript matches those HTTP envelopes, but unlike .NET’s lossy proxy generator it rejects distinct DTO or cardinality alternatives rather than silently picking one decoder; see Command outcomes.
  • Command read models by key. Drizzle command injection requires exactly one column-level .primaryKey() with an Arc @field on that key; without @field on an existing column-level key, the model can serve queries but injection fails at build (no resolver claims it). Table-level primaryKey({ columns }) is not recognized, and withDrizzle rejects such a model at registration. Custom codec columns bind typed keys; plain columns bind primitives, with invalid GUID and integer keys rejected. Command injection is tested against SQLite, live MySQL 8.4, and live PostgreSQL 16 with node-postgres. PostgreSQL checks typed GUID and concept keys, tenant isolation, and missing required and optional rows. A type registered with both Chronicle and Drizzle fails at build when injected with commandReadModel (two resolver claims). Chronicle additionally supports readModelForValidation(Type, { optional: true }) in model-bound command validators.
CapabilityStatusTypeScript contractEvidence
Model-bound queriesBounded@readModel() with static @query(...) methods and complete ordered argument, service, and queryOptions descriptors; with generated metadata, a bare @query() infers ordered arguments, concrete services, and observable returns; without it, legacy emitted metadata infers only class-valued services. Identity includes namespace, read model, and method.Source/Core/queries/modelBound/for_compileQueries, Source/Core/for_ArcApplicationBuilder/when_serving_model_bound_http, Samples/Tasks/Features/Tasks/Listing/for_TaskItem
Global query filtersBoundedScoped AuthorizationQueryFilter and QueryPipelineFilter services return result fragments; authorization precedes validator and performer dependency construction. They run once at observable admission, not per emission, including opt-in health. GET/QUERY/snapshot/direct SSE filter denial and allow are paired with .NET on Express, Fastify, and Hono. Direct WebSocket denial and allow, plus continued service to a second SSE/WS subscription after an early disconnect, are checked on all three TypeScript adapters; the .NET fixture does not expose a direct WebSocket upgrade. TypeScript traces verify authorization-before-ordinary-before-validation and that denial skips dependency construction and the producer. See Query filters.Source/Core/for_ArcApplicationBuilder/when_running_query_filters, Source/Core/for_ArcServer/when_filtering_observable_health, ContractTests/Http/conformance.test.mjs
GET and HTTP QUERYSupportedQUERY answers with Cache-Control: no-store and can be turned off with generatedApis: { enableQueryHttpMethod: false }.Source/Core/for_ArcServer/when_handling_a_query, ContractTests/Http/conformance.test.mjs
Argument bindingSupportedCase-insensitive names, number and boolean conversion on GET, repeated keys for array arguments.Source/Core/for_ArcServer/when_handling_a_query, Samples/Tasks/Features/Tasks/Listing/for_TaskItem/when_performing/with_a_concept_argument.ts
Paging and sortingSupportedArrays are sorted and paged in memory; a provider returns queryPage(items, totalItems, sorting?). Page offsets clamp to signed int32. See Paging and sorting.Source/Core/for_ArcServer/when_sorting_a_query, Source/Core/queries/for_queryRendering, Samples/Tasks/Features/Tasks/Listing/for_TaskItem/when_performing/with_sorting_and_paging.ts
Renderers and read-model interceptorsBoundedOrdered scoped QueryRenderers own provider results; exact-class ReadModelInterceptors transform snapshots and each emission, including HTTP snapshots. No automatic provider pushdown.Source/Core/queries/for_queryRendering, Source/Core/for_ArcApplicationBuilder/when_registering_read_model_interceptors
Services and dependenciesSupportedClass and serviceToken tokens with singleton, scoped, and transient lifetimes; the build preflights declared graphs. Scopes snapshot every declared execution-context field at creation (including inherited getters) but retain the original principal for ordinary factories. Trusted hosts can borrow a live scope only when its principal is strictly plain data; borrowed work uses a separate deep-frozen principal copy and may override only correlation and link an additional signal. This is not authorization. Host integrations can register shutdown participants to stop admission and drain external work before Arc disposes scopes and singletons. See Dependency injection.Source/Core/dependencyInjection/for_ServiceRegistry, .../for_ServiceScope, Source/Core/for_ArcServer/when_borrowing_a_scope, Source/Core/for_ArcApplicationBuilder/when_resolving_model_bound_services, yarn test:legacy-decorators
  • Paging and sorting. GET rejects nonpositive integer pageSize with a paging rule; nonnumeric and out-of-int32 GET paging values default, while leading zeros, +, and surrounding whitespace parse as integers. Structured QUERY treats nonpositive sizes as unpaged and rejects invalid sort directions with an owning member.
CapabilityStatusTypeScript contractEvidence
Observable definitions and HTTP snapshotsBounded{ observable: true } model-bound queries and defineObservableQuery accept RxJS BehaviorSubject, ReplaySubject, Subject, Observable, async iterables, and structural subscribables. RxJS is an optional peer of core for async-iterable-only consumers.Source/Core/for_ArcServer/when_handling_observable_snapshot, Source/Core/queries/observable/for_CurrentValueSubject, Source/Core/for_ArcApplicationBuilder/when_serving_model_bound_observables
Direct SSE and WebSocketBoundedThe query route streams direct result frames through the Express, Fastify, and Hono Node adapters and the standalone host; generated installed-client subscriptions receive initial and later results.Source/Core/queries/observable/for_directWebSocket, ContractTests/Client/observable-direct-sse.test.mjs, ContractTests/Client/observable-upgrade-lifecycle.test.mjs
Multiplexed WebSocket and SSE hubsBounded/.cratis/queries/ws and /.cratis/queries/sse accept anonymous connections, with revisions, configurable keep-alive, and tombstones on all three adapters and the installed 22.45.0 client (including the React provider’s default SSE transport). Query authorization is checked per subscription.Source/Core/queries/observable/for_HubConnection, Source/Core/for_ArcServer/when_subscribing_to_sse, .../for_SseHubTransport, .../for_SubscriptionRevisions, ContractTests/Client/observable-hub.test.mjs
Full, delta, and legacy transferBoundedfull, delta (initial full, then change sets), and legacy full plus change set. The installed client does not rebuild delta arrays in subscription callbacks.Source/Core/queries/observable/for_ObservableTransfer
Emission guardsSupportedScoped guards Allow, Suppress, or DenyAndTerminate every rendered result; failures deny and are logged.Source/Core/queries/observable/for_ObservableQuerySession, Source/Core/for_ArcServer/when_opening_observable_query
Query healthSupported, with a deliberate differencequery: { enableObservableHealth: true } exposes the authenticated caller’s own hub connections only. Global query filters run at health admission, not per emission. .NET exposes broader anonymous metadata.Source/Core/for_ArcServer/when_reporting_caller_scoped_health.ts, .../when_filtering_observable_health, .../when_handling_observable_health, Source/Core/queries/observable/for_FilteredHealthSource
Resource limitsSupported, configurableSubscription, connection, frame, queue, tombstone, handshake, and shutdown limits; exhaustion answers 503 or WebSocket 1013/1009. No request-rate limit.Source/Core/queries/observable/for_ObservableLimits, ContractTests/Client/observable-capacity.test.mjs
  • Observable definitions and HTTP snapshots. CurrentValueSubject remains supported but is deprecated. BehaviorSubject current values answer 200; sources without readable current values answer 202; bounded waits answer 408 or 500.
  • Multiplexed WebSocket and SSE hubs. SSE control requests must match the opener’s authenticated identity or anonymous state and tenant; anonymous callers are also matched by peer address when available, unlike .NET. Anonymous per-caller connection and subscription budgets are kept per tenant and peer address. SSE streams send Cache-Control: no-cache.
CapabilityStatusTypeScript contractEvidence
Command and query validatorsBounded@validator(Target) classes or validators with a generated target extending CommandValidator or QueryValidator with constructor-authored ruleFor rules; query validators target an explicit argumentsModel. One validator per exact target.Source/Core/validation/for_validator, .../for_ModelGraphValidator, .../for_RuleBuilder, Samples/Tasks/Features/Tasks/Registration/for_RegisterTask/when_validating
Concept and model validatorsBoundedConceptValidator and ModelValidator apply through declared @field graphs, with collection paths; ignoreConceptRules() skips only the direct member. No getter reflection or DataAnnotations.Source/Core/validation/for_ModelGraphValidator
Result shape and failuresSupportedResults carry severity, message, members, reason, and optional state and reasonDetail. ValidationResult.information/warning/error(message, options?) constructs results with members, state, reason, and reasonDetail; optional metadata reaches command and query HTTP envelopes. Malformed input is 400 malformedRequest; a throwing validator is 400 validatorFailed, logged, without exception text.Source/Core/validation/for_ValidationResult, Source/Core/for_ArcServer/when_handling_a_command, Source/Core/for_ArcServer/when_handling_a_query, ContractTests/Http/conformance.test.mjs
Severity filteringSupported, with a deliberate differenceHTTP caps X-Allowed-Severity at Warning; see Deliberate differences.Source/Core/for_ArcServer/when_selecting_http_severity, ContractTests/Http/conformance.test.mjs
Rules shared with the clientBoundedThe proxy generator emits literal, unconditional client-safe rules; server-only rules produce diagnostics. See Validation rules in proxies.Source/Tools/ProxyGenerator/for_renderRecordedRules, Source/Tools/ProxyGenerator/for_analyzeSource

Security, identity, tenancy, and correlation

Section titled “Security, identity, tenancy, and correlation”
CapabilityStatusTypeScript contractEvidence
Declared authorizationSupported@authorize, @roles, @allowAnonymous, or authorization; roles in one declaration are OR, stacked declarations on one target AND. Explicit query method declarations replace the read-model class declaration; command authorization is class-only. Contradictions fail at startup. See Authorizing commands and queries.Source/Core/for_ArcServer/when_handling_a_protected_command, .../when_handling_a_protected_query, Source/Core/for_ArcApplicationBuilder/when_authorizing_a_model_bound_command
Per-request authorizationSupportedauthorize(input, context) runs after the schema; allowed severity never affects it.Source/Core/for_ArcServer/when_handling_a_protected_command
Named policies and schemesBoundedFunction or scoped class policies; named Arc handlers selected per operation. Schemes on observable queries fail at build. See Authorization policies and schemes.ContractTests/Http/conformance.test.mjs (policy allow and deny against .NET 22.45.0), Source/Core/for_ArcServer/when_authorizing_named_schemes.ts, .../when_authenticating_a_handler_scheme.ts, .../when_rejecting_observable_scheme_configuration.ts
Authentication handlersSupportedOrdered chain; the first recognizing handler decides, and a failure is terminal with 401.Source/Core/authentication/for_authenticate
JWT bearer and EasyAuthSupported, opt-injwtBearer() verifies signatures against an HTTPS JWKS with pinned issuer, audience, and algorithms (TypeScript only). microsoftIdentityPlatform() maps forwarded EasyAuth headers like .NET and requires a trusted ingress. See Authentication.Source/Core/authentication/for_jwtBearer, .../for_microsoftIdentityPlatform, Source/Core/for_ArcServer/when_receiving_non_bearer_authorization.ts
Native principalSupported, opt-innativePrincipal: true plus a trusted adapter callback; mutually exclusive with handlers.Source/Express/for_identityHosts, ContractTests/Client/observable-native-auth.test.mjs
Identity detailsSupported, opt-inZod or model-bound detailsType providers, discovered or explicit; /.cratis/me answers 401, 403, or 200 with a display cookie of at most 4096 bytes.Source/Core/for_ArcServer/when_handling_identity_request, .../when_issuing_identity_cookies, Source/Core/for_ArcApplicationBuilder/when_discovering_identity_details.ts
Development users and tenantsSupported, opt-inFixture providers require development: true and are capped at 100 entries and 32 KiB. Their endpoints follow the discovery access policy, anonymous only in Development by default.Source/Core/for_ArcServer/when_discovering_development_tenants, .../when_resolving_development_tenant.ts
Tenant resolutionBoundedDefault header, authoritative tenancy.resolve, a .NET-compatible tenancy.resolverType, and ordered header, query, claim, fixed, and strict subdomain sources with required and membership checks.Source/Core/for_ArcServer/when_configuring_tenancy, .../when_resolving_tenants, .../when_checking_tenant_membership, .../when_resolving_tenant_claims
Correlation IDsSupportedValid non-zero UUIDs are reused, others replaced; the header name is configurable. W3C trace context stays host-owned.ContractTests/Http/conformance.test.mjs
Exception redactionSupportedOutside development, HTTP results carry a generic message and no stack trace; direct calls are not redacted.Source/Core/for_ArcServer/when_logging_a_failure, ContractTests/Http/conformance.test.mjs
CapabilityStatusTypeScript contractEvidence
TypeScript proxy generationBoundedarc-proxygenerator reads decorated source and emits commands, queries, observable queries, models, hooks, derived types, and client-safe rules for the published @cratis/arc 22.45.0 client in strict Bundler mode. Full .NET template parity remains unverified. See Proxy generation.Source/Tools/ProxyGenerator/for_analyzeSource (including duplicate-name tsc compilation), .../for_renderSource, .../for_SourceTypeResolver, yarn test:client-generation (ContractTests/Client/source-generation.test.mjs)
Low-level client manifestBoundedexportClientManifest plus the positional JSON CLI for define* definitions with explicit clientOutput: flat DTOs, basic inputs, and command responses. No hooks, nested DTOs, or shared rules. See Low-level manifest.Source/Core/introspection/for_exportClientManifest, ContractTests/Client/generation.test.mjs
IntrospectionSupported/.cratis/commands, /.cratis/queries, and /.cratis/identity-details/schema describe input JSON Schema under the discovery access policy: anonymous in Development, authenticated elsewhere by default. introspection.enabled: false (Cratis:Arc:Introspection:Enabled) disables catalogs and HTTP OpenAPI, not identity discovery or in-process metadata. See Turn discovery off.Source/Core/for_ArcServer/when_introspecting_queries.ts, .../when_discovery_is_disabled, Source/Express/for_identityHosts/when_discovery_is_disabled.ts, ContractTests/Http/conformance.test.mjs
OpenAPISupported/openapi.json is OpenAPI 3.1 with input schemas, result envelopes, generated typed result schemas when metadata is registered, command POST <route>/validate with an untyped envelope, query paging/sorting options only for declared array or queryPage results, and bearer security declarations for configured authentication handlers. Unknown query return cardinality does not advertise paging even though the runtime can page an array or queryPage result; renderer-backed (QueryRenderer) queries aren’t automatically advertised as pageable. info.version can be set with generatedApis.openApiVersion (default 0.1.0). See OpenAPI.Source/Core/for_ArcServer/when_describing_observable_query_in_openapi.ts, .../when_serving_openapi, Source/Core/openApi/for_renderOpenApi, ContractTests/Client/source-generation.test.mjs (JSDoc summary over HTTP)
Concepts, derived types, and wire formatBoundedPrimitives, Date, Fundamentals Guid, DateOnly, TimeOnly, TimeSpan, concepts, nested models, arrays, and field modifiers; _derivedTypeId polymorphism with oneOf schemas; .NET-style acronym-preserving camel case. See Wire format.Source/Core/reflection/for_wireSchema, Source/Core/for_ArcServer/when_binding_a_polymorphic_command, ContractTests/Client/polymorphic.test.mjs
Code analysisBounded@cratis/eslint-plugin-arc-core for ESLint 10: .NET-mapped ARC rules and TypeScript-only binding rules, with recommended and recommended-type-checked presets. No automatic fixes. See Code analysis.Source/CodeAnalysis/for_rules, yarn lint:tasks:arc
Build-time checksBoundedadd() rejects undecorated classes; build() rejects decorators with no effect, missing services, cycles, and captive lifetimes. Generated binding metadata checks runtime-verifiable shapes at registration; --check-metadata catches source-only changes.Source/Core/for_ArcApplicationBuilder/when_building, yarn test:decorator-types
Screenplay generationNot applicable.NET only.
  • TypeScript proxy generation. Optional server metadata infers service and argument bindings with an explicit --metadata option. Same-named models in distinct namespaces, decorated identity-provider result types, and symbol-resolved Chronicle event/operation command return filtering are supported, as are static query HTTP method and warning preferences. Source-only identity configuration also remains unverified. Identity details models outside the artifacts root are skipped with a diagnostic.
CapabilityStatusTypeScript contractEvidence
MongoDBBoundedTenant-scoped collections with BSON mapping, .NET-compatible naming policies, database-side paging, change streams, and command read models. No cross-store transactions. See how it is checked.Source/MongoDB/for_MongoCollection, .../for_MongoDocumentCodec, .../for_MongoReadModels, .../for_withMongoDB, bash Source/MongoDB/run-integration.sh (live replica set, Docker)
SQL with DrizzleBoundedTenant-scoped Drizzle handles, column codecs, and SQL count, sort, and page for model-bound queries. SQLite, PostgreSQL 16, and MySQL 8.4 tested with real databases. MySQL live coverage includes tenant routing, codecs, stable paging, limits, sort rejection, and command lookup. PostgreSQL command lookup with node-postgres covers tenant routing, typed keys, and missing rows. No migrations, change tracking, or transactions. See how it is checked.Source/Drizzle/for_DrizzleReadModelForCommandResolver, Source/Drizzle/for_DrizzleReadModels, .../for_ColumnCodec, .../for_DrizzleModelCodec, .../for_withDrizzle, bash Source/Drizzle/run-integration.sh (live PostgreSQL 16 and MySQL 8.4, Docker)
SQL table observationExperimentalDrizzleObservation.InProcess observes explicitly announced changes in one process. PostgreSQLNotify uses application-installed statement triggers and one dedicated listener per active tenant for committed cross-process invalidation. Listener loss pauses reads; bounded reconnect (five delayed attempts) revalidates live relations and forces fresh snapshots, including unused primes. Exhaustion errors subscriptions; later subscriptions may start fresh. Eventual committed state, not durable intermediate-state delivery or atomic page snapshots. No MySQL/SQLite cross-process observation or polling.Source/Drizzle/for_DrizzleReadModels/when_observing, .../when_serving_a_sqlite_observation, .../when_observing/with_postgres.integration.ts, .../for_DrizzleHandle, bash Source/Drizzle/run-integration.sh postgresql
ChronicleExperimentalNot published to npm. withChronicle appends returned model-bound events through a response value handler, with routing, subject, and causation resolved from the command. Full .NET parity is unverified. See how it is checked.Source/Chronicle/for_ChronicleResponseHandler, .../for_ChronicleUnitOfWork, .../for_ChronicleReadModelForCommandResolver, .../for_reactorCommandResultHandler, .../for_AggregateRoot, Source/Chronicle/testing/for_ChronicleCommandScenario, bash Source/Chronicle/run-integration.sh (live kernel, Docker)
Chronicle complianceBoundedSubject resolution on appends and @notAudited and @pii exclusion from the causation chain. Arc releases protected models decoded into the exact read-model class, and raw MongoReadModels documents typed with readModel, at the query edge. A failed release fails the result.Source/Chronicle/for_ChronicleReadModelInterceptor, Source/Core/queries/for_interceptReadModel, Source/Chronicle/for_ChronicleReadModelForCommandResolver/when_resolving_a_private_projection, Source/Chronicle/Integration/live.test.mjs
Reactor replay exclusionBounded (SDK 6.9.0+)The SDK replays reactors by default and supports class- or handler-level @onceOnly() and alternate @replay() handlers. Arc executes returned commands under those SDK rules; type-checked ARCCHR0006 warns on returned commands without a replay decision. Failed-partition recovery can re-deliver effects even with @onceOnly(). See Reactors.SDK decorators; Source/CodeAnalysis/for_rules/when_linting_artifacts/with_reactor_replay_decisions.ts checks the lint rule; Source/Chronicle/Integration/LiveArtifacts.ts and bash Source/Chronicle/run-integration.sh exercise a once-only reactor returning a command on normal delivery, not replay exclusion
Chronicle code analysisBoundedARCCHR0003 checks reactor store fields initialized from this.client/this.runtime (ownership is not proven); type-checked ARCCHR0006 flags returned commands from live handlers without a replay decision; ARCCHR0007 flags direct default-log appends from command handle/provide, including injected Chronicle services; ARCCHR0009 checks unmasked secret-looking fields; type-checked ARCCHR0010 flags Guid values beside direct decorated events on keyless commands. Other .NET diagnostics are inapplicable, checked at runtime, or require review.Source/CodeAnalysis/for_rules/when_linting_artifacts/with_chronicle_rules.ts, Source/CodeAnalysis/for_rules/when_linting_artifacts/with_reactor_replay_decisions.ts
Transactions and units of workExperimentalChronicle stages returned and aggregate-applied events from nested commands in one tenant, correlation, and event store, and sends one appendMany after the outer command succeeds. It participates in Arc command-operation failure handling; it is not a transaction across stores or immediate SDK appends.Source/Chronicle/for_ChronicleUnitOfWork, Source/Chronicle/for_ChronicleCommandScope
Event source definitionsExperimental@eventSourceDefinition on a command or aggregate routes events through a registered Chronicle definition and stream: startup validation, per-event source and stream replacement as a unit, definition-derived concurrency when no explicit scope or flags are set, and aggregate rehydration limited to the declared source and stream. Needs @cratis/chronicle 6.49.0 or later; string routing works with older SDKs. Verified against Chronicle kernel 19.30.0 and SDK 6.49.0. Screenplay grammar and generation for definitions are not part of this package.Source/Chronicle/for_ChronicleResponseHandler/when_routing_through_an_event_source_definition, Source/Chronicle/for_withChronicle/when_validating_event_source_definitions, Source/Chronicle/for_AggregateRoot/when_declaring_an_event_source_definition, ARC_CHRONICLE_TEST_SUITE=event-source-routing bash Source/Chronicle/run-integration.sh
  • MongoDB. Also joined observation, a scoped watcher, GeoJSON geometry, bounded transient read retries, MongoDB driver metrics for Arc-owned clients, and Cratis:MongoDB:{Server,Database} configuration binding. No durable watcher checkpoint; nonresumable stream failures terminate subscriptions. .NET’s process-wide watcher, general-purpose resilience interceptors, and comprehensive metrics for supplied clients are not implemented.
  • SQL with Drizzle. bash Source/Drizzle/run-integration.sh exercises live PostgreSQL and MySQL for existing SQL reads and command lookup, not observation. In-process observation is checked using SQLite (sql.js), gated race and burst specs, and SSE/GET through Express, Fastify, and Hono. Command read models load by a single column-level .primaryKey() with @field in the tenant scope; models without @field on their column-level key still serve queries but cannot be injected into commands. Tables without a column-level primary key are rejected at registration. Custom columns bind typed keys, plain columns primitives. Command read models are tested against SQLite, live MySQL 8.4, and live PostgreSQL 16 with node-postgres. PostgreSQL coverage includes typed GUID and concept keys, tenant isolation, and missing required and optional rows.
  • Chronicle. It also resolves Chronicle read models by command key and in validators, batches nested returned events, and executes Arc commands returned from reactors through the SDK reactor result hook. SDK 6.49.0 loads in native Node ESM. Keyed aggregates and returned reactor commands are experimental.
  • Chronicle compliance. Mark projected read-model properties @pii() to encrypt them at rest; event-only marking does not protect the materialized field. Chronicle kernel reads already release values (including command injection), and Arc skips releasing those instances twice. For protected Chronicle models decoded into the exact read-model class by MongoCollection, Arc releases at the query edge, including snapshots, pages, and observable emissions. Raw MongoReadModels documents typed with readModel are released with the request’s tenant and each document’s subject: subjectFor(document) when given, otherwise the stored __subject, otherwise a string or numeric _id. It must match any stored __subject and the model’s @subject() or id. Kernel bookkeeping fields are stripped. The path fails closed on undeclared fields, non-JSON BSON values (including Guid fields stored as Binary), per-property __subjects, a subject or tenant mismatch, typed documents nested in another shape, and MongoDB projections. Unreleased instances of a protected class nested in another returned shape, such as a joined select result, fail the query. Specs check snapshots, pages, and observable SSE through Express, Fastify, and Hono against a Chronicle substitute; the kernel integration reads a real materialized document through MongoReadModels. Untyped raw documents, typed documents of models not registered with Chronicle, codec-selected derived subtypes, MongoDBWatcher.changes() payloads, documents hidden behind toJSON(), getters, or private fields, and DTOs, mapped objects, or copies are served as stored and need explicit readModels.release on the tenant store. Nested, array-item, class-level PII and @encrypted() security metadata are detected. A directly read protected model needs @subject() or id matching the event subject to release; one without either is served only when it holds no protected value, and fails otherwise. The kernel integration checks raw MongoDB ciphertext, Chronicle delivery, and Arc query-edge release for a direct MongoDB read.
CapabilityStatusTypeScript contractEvidence
Pipeline scenariosBoundedCommandScenario, QueryScenario, and ObservableQueryScenario run decorated artifacts through real pipelines with wire encoding and fakes; ArcScenario covers low-level definitions and HTTP. ChronicleQueryScenario resolves pinned, reducer-built, or supported flat projection-backed keyed read models in memory, but not lists or subscriptions. A node:test recipe runs one command scenario with disposal.Source/Testing/for_CommandScenario, .../for_QueryScenario, .../for_ObservableQueryScenario, .../for_ArcScenario, .../for_withCommandAssertions, Source/Chronicle/testing/for_ChronicleCommandScenario, Source/Chronicle/testing/for_ChronicleQueryScenario, Samples/Library/kernel-scenarios.test.mjs (live kernel, Docker), yarn check:node-test
  • Pipeline scenarios. ChronicleCommandScenario records command-produced events separately from seeded event history and resolves reducer-backed and supported flat projection-backed command read models with the SDK’s ReadModelScenario; pins take precedence. ChronicleQueryScenario uses the same seed store for keyed Chronicle query reads and rejects unsupported lists and observations. Unsupported projection operations, aggregate replay, and observer-driven updates require ChronicleKernelScenario, which seeds events and runs against a live kernel. The in-memory scenario does not enforce concurrency or update reduced state after command appends.
CapabilityStatusTypeScript contractEvidence
Express 5, Fastify 5, and Hono 4 adaptersSupportedcratisArc accepts an ArcServer or built ArcApplication: Express middleware plus listener attach; Fastify plugin registers HTTP and WS in either shared-plugin order; Hono sub-app plus Node serving helper. The host keeps listener ownership. See Host adapters.Source/Express/for_cratisArc, Source/Fastify/for_cratisArc, Source/Hono/for_cratisArc, ContractTests/Client/observable-origin.test.mjs
Node configuration and Fetch dispatchBoundedThe default @cratis/arc.core entry keeps Node file configuration, discovery, HTTP host, static files, and WebSocket upgrades. The separate @cratis/arc.core/fetch entry registers artifacts without file discovery or configuration and serves commands, queries, and SSE. Next.js App Router next build && next start on Node.js is checked with two catch-all routes and server minification disabled; Bun has a smoke check only. Workers and Next.js Edge are not supported. See Fetch API runtimes.Source/Core/configuration/for_loadConfiguration, Source/Core/for_FetchArcApplication, yarn check:fetch, yarn check:fetch:next, yarn check:fetch:bun
Cratis compositionExperimental@cratis/cratis composes Arc and Chronicle without installing an authentication handler; CratisApplication.createBuilder and builder.addCratis. It is not published to npm, and the composition itself has not been exercised against a live kernel.Source/Cratis
Cancellation on client disconnectSupported for Express, Fastify, and NodeHono passes the signal of the request it received.Source/Express/for_cratisArc, Source/Fastify/for_cratisArc, Source/Core/http/for_createArcNodeHandler
Unsupported methodsSupported405 with Allow for methods that reach Arc; the standalone host rejects TRACE and CONNECT.ContractTests/Http/conformance.test.mjs, Source/Core/http/for_runArc
Standalone host and static filesBoundedHTTP or HTTPS listener or request handler, public files, SPA fallback, path base, cache validators, and graceful shutdown. No private file authorization, directory listing, multiple roots, or byte ranges.Source/Core/http/for_runArc, .../for_createArcNodeHandler, Source/Core/for_ArcApplication
Tracing and metricsBoundedVersioned Cratis.Arc OpenTelemetry API spans and command/query duration histograms in seconds with canonical attributes (cratis.arc.command.type, cratis.arc.query.name); the operation-duration histogram remains emitted but is deprecated; the application supplies the SDK. Host-owned HTTP instrumentation produces a SERVER span above Arc’s command HTTP span on real Express, Fastify, and Hono Node servers; Express and Fastify instrumentation also produces framework spans in the ancestry. Foreign routes have no Arc spans. A host-owned pino recipe verifies callback correlation, redacted HTTP errors, and omission of a command payload from structured logs.Source/Core/for_ArcServer/when_tracing_operations, yarn check:observability-recipes
  • Node configuration and Fetch dispatch. app.fetch(request) answers 404 on non-Arc paths and app.handle(request) falls through. A neutral bundle allows only node:async_hooks, with no Node filesystem/crypto/stream imports, and passes in a restricted VM. Next.js 15.5.14 next build && next start on Node.js 26.8.1 passes commands, GET, validation, authorization, snapshot and streamed direct/hub SSE, direct client abort and subscription release, and tenant/correlation headers; Next.js itself returns 404 for unmatched routes. This is a local production-server check, not a deployment check. Server minification must be disabled to preserve model-bound class names. Next.js rejects HTTP QUERY with 400 before Arc sees it; use GET. Bun 1.3.10 Bun.serve passes the shared scenario including QUERY, as an optional smoke check, not a deployment guarantee. The older Deno 2.9.7 check passed the smaller pre-expansion scenario; the expanded scenario has not been rerun there. Arc targets Node-compatible servers for Chronicle gRPC, MongoDB TCP, and live queries; Cloudflare Workers and Next.js Edge are not supported, and these checks do not verify deployed production hosting or optional integrations.
  • Ambiguous command response alternatives. Both runtimes serialize only the selected value; an arbitrary Result<TSuccess, TError> error DTO is a 200 success response, not a rejection. The TypeScript proxy generator rejects alternatives requiring different client decoders or cardinalities, while .NET 22.45.0 picks one response type and hydrates all alternatives through that decoder. Use one DTO with an application-owned status field for several client-visible business outcomes.
  • Malformed command input and filters. TypeScript runs authorization filters with raw input when binding fails, allowing a denial to take precedence over a 400 response. .NET 22.45.0 returns 400 before those filters for malformed command input. The paired fixture checks both execute and /validate modes; authorized malformed input returns malformedRequest on both with different message text.
  • Global filter warnings and errors. TypeScript applies severity filtering to each command and query fragment before short-circuiting; .NET short-circuits before filtering. Otherwise a filtered-out warning could skip later authorization filters. A thrown global command or query filter produces a 500 exception result, not .NET’s 400 IValidationFailure result; ordinary per-definition validation failures still produce 400.
  • Severity 3 over HTTP. On a command, X-Allowed-Severity accepts 0, 1, and 2. A request that sends 3 is treated as 2 (Warning), so error-severity results still block with 400 and the handler does not run. Arc on .NET 22.45.0 accepts 3 and runs the command unless it declares [BlockOnValidationSeverity]. Queries ignore the header. A trusted caller of executeCommand can still pass Severity.Error.
  • Anonymous caller on a protected operation. With authentication handlers configured, Arc for TypeScript answers 401; Arc on .NET answers 403.
  • Method authorization on commands. TypeScript rejects @allowAnonymous(), @authorize(), or @roles() on handle(), provide(), or another command method at build; declare authorization on the class. The pinned .NET 22.45.0 command pipeline evaluates only the type and ignores authorization attributes on Handle(). Query methods on both runtimes replace, rather than combine with, their read-model class declaration; stacked attributes on one target are AND requirements, with OR roles within one attribute.
  • Identity denial and display cookie. /.cratis/me returns a JSON { error } on 401/403 in TypeScript; the pinned .NET host sends an empty body. Both return the same successful identity fields and a base64 display cookie, but cookie attribute casing and base64 escaping are host-dependent. TypeScript never reads the unsigned cookie as a credential; .NET 22.45.0 reads it before checking the authenticated principal. Identity-details schemas describe the same required fields but use different schema generators. Both runtimes require authentication outside Development by default for discovery (/.cratis/commands, /.cratis/queries, /.cratis/users, /.cratis/tenants, and /.cratis/identity-details/schema), with an anonymous opt-out and optional roles. The paired Production fixtures assert anonymous 401 and authenticated 200 across Express, Fastify, and Hono for all five routes; denial bodies remain empty on .NET and JSON on TypeScript. TypeScript additionally protects /openapi.json; see Discovery access.
  • Policy context and authentication schemes. .NET 22.45.0 evaluates named policies with scoped DI classes, a reflected target, and a command or query context; TypeScript also offers function policies, and its class policies receive an operation definition and an input and execution resource. Node scheme selection uses Arc handlers, not ASP.NET Core scheme composition, and scheme-protected observable queries are rejected at build. jwtBearer() is TypeScript-only. The paired 22.45.0 fixture checks named-policy allow and deny on commands and queries, but does not compare policy context objects or scheme selection.
  • Query health. Caller-scoped and opt-in, instead of .NET’s anonymous cross-caller view.
  • Anonymous SSE hub ownership. Like .NET, anonymous callers can open an SSE hub connection but have no identity that distinguishes them from other anonymous callers. TypeScript additionally binds anonymous controls to the opening peer address when the host supplies one; without an address, callers in the same tenant who know the random connection ID cannot be distinguished. Authenticated connections require the same authenticated identity, and anonymous callers cannot control them.
  • Observable wait bounds. waitForFirstResultTimeout is capped at 120 seconds, and an unrecognized boolean is rejected instead of ignored.
  • Message texts. Malformed requests say Malformed request, and redacted exceptions say An unexpected error occurred; Arc on .NET uses different texts. An unreadable QUERY body returns an exception envelope on both, with these different redacted texts. Exposing exception details reveals TypeScript’s reader error and stack.
  • Preparation and binding. A model-bound provide() passes one value as the first handle() argument; several values need tuple(...) and provided(Type) markers, where .NET infers assignable types. Without generated metadata, standard-mode queries need ordered descriptors.
  • Compiler metadata. Standard decorators can use generated metadata or explicit tokens; legacy decorators infer only class-valued dependencies from design:paramtypes. Generated metadata infers observable declarations from the return type; without it, use { observable: true }.
  • Enum fields. Use @field(String) or @field(Number) with @enumeration(Enum); numeric reverse mappings are ignored.
  • Hosting defaults. The Node host binds loopback by default. File discovery imports a dedicated artifacts folder, and moving a folder changes its derived route.
  • Invalid GUID query arguments. Both runtimes return 400 malformedRequest. .NET 22.45.0 identifies the argument and query in its message and reports the argument member; TypeScript returns Malformed request with no members.
  • Structured QUERY argument names. TypeScript rejects undeclared argument names with malformedRequest so typos do not silently change query results; .NET ignores them. Both ignore unknown members of the envelope, paging, and sorting objects, and a direction without a field.
  • QUERY numeric tokens. JavaScript JSON parsing treats 2.0 and 2e0 as integer 2 and nested null paging numbers as missing values. .NET’s int32 body reader rejects all three; unlike JavaScript, its JSON reader retains the original numeric token. A top-level null body or a null envelope member is absent in both implementations.
  • Invalid QUERY sort fields. TypeScript rejects names outside the declared field-name syntax with 400 malformedRequest; the pinned .NET array renderer fails with a redacted 500 for an invalid name such as name!. This is a fail-early input guard.
  • Body size limit. TypeScript’s hosting.maxBodyBytes rejects oversized QUERY bodies with 400 malformedRequest; the pinned .NET fixture has no corresponding configured limit and accepts an otherwise valid large body.
  • Bounded provider reads. MongoDB and Drizzle read at most their configured maximum page size; when an unpaged result exceeds it, they return a 400 paging-required rule on Size. A requested page larger than that maximum also returns a 400 rule. Arc on .NET returns all matching rows without a bound (QueryableQueryRenderer.Execute, MongoCollectionExtensions.cs).
  • Sorting collation. In-memory string sorting uses localeCompare, not .NET invariant-culture collation.

The integration pages describe behavior. This section records the checks behind it.

The specs run with the published Chronicle TypeScript SDK, @cratis/chronicle 6.49.0, and @cratis/fundamentals 7.22.0; both load in native Node ESM with NodeNext resolution. The live suite was last verified with SDK 6.10.0; a 6.14.0 attempt could not start the local kernel because its MongoDB connection was refused. The ordinary yarn test specs use typed substitutes or the SDK’s in-memory ReadModelScenario and never start a kernel. An opt-in suite, bash Source/Chronicle/run-integration.sh, runs Source/Chronicle/Integration/live.test.mjs against a real development kernel and checks:

  • returned-event batches, readback, and tenant isolation;
  • a reactor that returns an Arc command, executed in the triggering event’s tenant;
  • before-first concurrency rejection, and aggregate rehydration, commit, and concurrency rejection;
  • operation compensation after a concurrency rejection;
  • command-key read models for existing and missing keys;
  • a projected read-model query, through a read-model interceptor; raw MongoDB ciphertext for a PII-marked projected read model, and plaintext delivery through Chronicle HTTP snapshots, observable SSE, command injection, and an Arc query reading the materialized MongoDB collection directly;
  • all of it through real Express, Fastify, and Hono HTTP adapters.

The suite needs Docker. Its image, cratis/chronicle:latest-development, is mutable, so pin a compatible image for reproducible deployment testing. Validator read models (readModelForValidation), subject resolution, and @notAudited are covered by substitute-based specs, not by the kernel suite. The Library sample has its own kernel run, bash Samples/Library/run-integration.sh.

bash Source/MongoDB/run-integration.sh starts a task-owned MongoDB 7 replica set in Docker and removes it afterward. The integration spec exercises initial snapshots, insertion, deletion, tenant isolation, dependency injection, and provider paging, and a second spec serves sorted pages through Express, Fastify, and Hono. The script exits with 2 when Docker is not available, which means the check did not run. Unit specs cover the codec against .NET-shaped documents, naming policies, burst coalescing, the item cap, and stream failures.

yarn vitest run --project @cratis/arc.drizzle runs the SQLite specs on sql.js. The adapter HTTP spec serves a sorted page through Express, Fastify, and Hono and checks that an unknown sort field answers 400; the tenant-isolation spec checks databaseFactory routing. bash Source/Drizzle/run-integration.sh runs the PostgreSQL spec against PostgreSQL 16 and the MySQL spec against MySQL 8.4 in disposable Docker containers. The MySQL check covers databaseFactory tenant isolation, stable SQL counts and sorted pages (including ties), concept/GUID/JSON/date/time codecs, invalid sort field rejection, paging limits, and command read-model lookup by GUID key (including a missing key). The PostgreSQL command spec checks node-postgres command lookup with typed GUID and concept keys, tenant-scoped tables, and missing required and optional rows. run-integration.sh exits with 2 when Docker is unavailable. Neither suite verifies concurrent writes between count and page.

The generated proxies compile against @cratis/arc and @cratis/arc.react 22.45.0 with @cratis/fundamentals, in strict Bundler mode with skipLibCheck: false. yarn test:client-generation builds the workspace, generates the Tasks proxies, compiles them, and runs commands, queries with arguments, paging, sorting, observable snapshots, and hub updates against live model-bound Express, Fastify, and Hono servers. Nullable command types and interface-only model mode have compile coverage only, not live-client equivalence.

The shared Arc pages show a TypeScript tab beside C#, Kotlin, and Java. Its snippets live in Documentation/client-snippets/, and yarn docs:snippets compiles each one against this repository’s packages with strict settings and standard decorators. Every shared snippet is a real TypeScript example. The in-memory ChronicleCommandScenario builds reducer-backed and supported flat projection-backed read models from seeded events and rejects projections outside that boundary with UnsupportedProjectionOperation; test those with ChronicleKernelScenario. The shared validator examples inject an application-owned repository. Arc for TypeScript validators do not take read models as constructor parameters; read one inside an async rule with readModelForValidation. A compiled snippet shows that the API exists with that shape; it is not a parity claim for the page around it.

A conformance suite, yarn test:conformance, runs paired checks against a .NET host built on the published Cratis.Arc 22.45.0 package and Arc for TypeScript mounted in Express (and, for filter checks, Fastify and Hono). Global command and query filter HTTP requests are paired with .NET across Express, Fastify, and Hono; direct WebSocket filter admission and filter-stage traces are TypeScript-only checks because the .NET fixture does not expose a direct WebSocket upgrade or fixture stage counters. It covers command execution, validation-only requests, authorization before validation including validation-only denial, business-rule and malformed-input rejection, tuple response validation, GET and QUERY binding, paging, sorting, exception redaction, unsupported methods, correlation IDs, model-bound commands and validation, acronym naming, enum and named-float output, an overridden query path, and a conventional GUID query route with valid and invalid inputs. It also checks named-policy allow and deny, numeric concept GET arguments, validation severity, and observable snapshots with current (200) and pending (202) values. Invalid sorting, GET and structured paging boundaries, malformed QUERY bodies (redacted 400, no-store, and no handler execution), and int32 page-offset clamping are paired against the published fixture. It also pairs identity details and the display cookie (while explicitly rejecting .NET’s unsigned-cookie authentication), header/fixed/verified-claim/subdomain tenant routing, class and method authorization, malformed numeric/enum/concept inputs and execution counters, query validation and exceptions, correlation IDs on 400/403/500/202, and observable first emissions, waits, timeouts, and completion. The query reader’s exception envelope matches; its redacted text differs as noted above. Numeric concept GET arguments bind successfully on both runtimes, and both apply GET sorting and reject invalid sort directions. It pins the deliberate differences above and a host-specific difference that is not a choice of Arc for TypeScript: Express answers an unknown path with its own HTML 404 without an Arc correlation header.

The suite needs the .NET 10 SDK and is not part of yarn ci. It is a bounded check of those routes, not a claim of full parity.