Chronicle.Go parity
Use this reference when moving a C# Chronicle workflow to Go: find the matching API, its executable evidence and the differences your application must handle. Chronicle.Go uses explicit ownership, contexts and typed registration. The implemented subsets below are not overall client parity or production lifecycle completeness.
Start with baselines and evidence, then the relevant capability. Migration guidance covers connections, constraints and read models; remaining work separates gaps from delivered APIs.
Identity display-name observation
Section titled “Identity display-name observation”| C# authority | Go surface and status | Evidence and limits |
|---|---|---|
Source/Clients/DotNET/Identities/{IIdentityManager,IdentityManager}.cs; Source/Kernel/Core/Identities/{RenameIdentity,IdentityConverters,RenameIdentityValidator}.cs, Storage.MongoDB/Identities/IdentityStorage.cs, Contracts/Commands/CommandResult.cs at 2e31b0dfba489159b3db323238f16d0f277056b4 (unchanged from pinned ae5e00a8abaa688138b2c2f689e2b4659cccb4fd) | Partial: EventStore.Identities().Rename(ctx, subject, identities.Name, ...metadata.CorrelationID) updates only a namespace-scoped display name. No registration, subject/username/stream movement, event append, catalog discovery or read-model release | TestIdentityRenameObservedWireSequence, TestIdentityRenamePreReadSelection, TestIdentityRenameCommandEnvelopes, TestIdentityRenamePostReadKeepsAcknowledgment, TestKernelIdentityRenameObservedName. The actual pinned single-kernel command acknowledgment and two internal reads precede independent Mongo-backed listing, preserving subject/username/unrelated identities. Historical cache convergence, multi-node behavior, classified metadata, SQL and role isolation remain unqualified |
C# storage UpdateOneAsync(Subject == subject) ignores matched count | Go-specific: pre-list → one command → post-list; IdentityRenameResult preserves acknowledgment separately from observed name; payload-free not-found/refused/unknown contracts | Empty/alias/duplicate/corrupt pre-lists prevent commands; failed query data is unusable; all validation entries refuse, including warnings. TestIdentityRenameWirePresenceFailsClosed, TestIdentityRenameFailedQueryDataIsNotAbsence, TestIdentityRenameDecoderBypassCannotAcknowledge. Exact authorization presence is required, unknown fields fail closed. Observation is neither an atomic matched-row receipt nor proof that this command caused the name; two unfiltered O(namespace identity count) reads require listing permission and message capacity |
| Go generation/root registration and audit-provider contracts | Go-specific: already-ready stable-generation operation, no automatic readiness or retry; invocation-local error privacy without shared producer changes | TestIdentityRenameIncompleteBarrierNeverJoins, TestIdentityCompleteReadinessChecksObserversAndStages, TestIdentityRenameNamespaceAckDoesNotStartPendingObserver, TestIdentityRenameGenerationRootAndCancellationRaces, TestIdentityRenameCommandFlightReleasesAndNotifies, TestIdentityRenameCallbackCloseReentry, TestIdentityRenameHostileFailuresOpaqueAndLeasesReleased, TestIdentityRenameTokenAndInvalidatorDiscardFailures, TestIdentityRenameFrozenAuditOnceAndExplicitZero. Separate readiness calls and independent background operations retain their ordinary contracts. Actor/causation are frozen context only; rename wire lacks them. No privacy promise for borrowed implementations’ own logging or explicit reentrant operations |
See Rename a stored identity display name for prerequisites, errors, listing cost and acknowledgment limitations.
Runtime projection additions
Section titled “Runtime projection additions”| C# authority | Go surface and status | Evidence and limits |
|---|---|---|
DotNET/Projections/Projections.cs:363–405 and DotNET.Specs/Projections/for_Projections/when_registering_explicitly at 2e31b0dfba489159b3db323238f16d0f277056b4 | Partial: EventStore.RegisterProjection(ctx, projections.Declaration), ProjectionRegistration.Published plus existing RegistrationOutcome; one new ordinary model and projection using known exact event generations | TestRuntimeProjectionPublishesStoreRootAndRetainsOldReaders, TestRuntimeProjectionFalseWirePresence, TestRuntimeModelFailureRetriesCompleteModelsBeforeCumulativeProjections, TestKernelRuntimeProjectionAdditionRetainsDefinitionsAcrossReconnectAndNamespace. Same-model replacement, passive/variant/global/protected/relationship/derived shapes and runtime registration of other artifact families remain unsupported. C# supports replacement; Go deliberately rejects it |
| C# local composition followed by model/projection registration; no atomic remote transaction | Go-specific: per-client/store publication and destructive-flight fence; sticky ErrDestructiveRegistrationUnknown refuses later mutations, not ordinary acknowledged readiness | TestRuntimeUnknownInitialFullSetPreservesReadyButRefusesAdd, TestRuntimePublishedCumulativeUnknownReturnsRealAcknowledgedOutcomeAndSentinel, TestRuntimeDestructiveDispositionIsRecordedBeforeFlightRelease, TestRuntimeQueuedFullSetSupersededBeforeAndInsideAuthorization, TestRuntimeConcurrentAddCannotDispatchObsoleteSubset, TestRuntimePublishedCancellationRetainsRootAndReportsActualStage. Unknown requests cannot omit a later locally admitted addition; no remote quiescence, same-root idempotence or protection from independent clients is claimed |
| Go decision-read evidence and immutable catalogs | Go-specific: store-wide fixed expected epochs stale issued/enrolled guards across namespaces; old ordinary readers remain coherent snapshots | TestRuntimePublicationStalesAllNamespaceGuardsAndOldReaders; Declaration.IsVariant preserves authoring identity even when wire definitions are identical (TestVariantMetadataSurvivesIdenticalOrdinaryWireShape). Tokens remain unauthenticated SDK evidence, not server revision or history/ABA fences |
See runtime registration for retained-state, retry and error semantics. Earlier deferred-runtime entries below still apply to replacement, general registry mutation and other artifact families, not this bounded addition workflow.
Declared Int32 enum codecs
Section titled “Declared Int32 enum codecs”| C# authority | Go surface and status | Evidence and limits |
|---|---|---|
Actual Chronicle 19.29.4 (ae5e00a8), Fundamentals 7.19.6 (14037b1f), runtime/STJ 10.0.12: client options initializer, EventSerializer, Generate and GenerateForReadModel | Partial: serialization.EnumMember[T ~int32], Enum, Flags, existing immutable Codecs; exact named type and declared values only | TestEnumPackagedProfileRegression compares all captured scalar/nullable/array observations for five Int32 declarations under both naming policies; both schema APIs retain integer enum/name arrays without an int32 format. The supplemental actual .NET decoder verifies 262 Go payloads; historical raw capture and the distinct Fundamentals source corpora are unchanged |
| Chronicle schema-driven Expando and Mongo conversion | Partial, Go-specific narrower admission: reject unknown values and undeclared combinations, including with All=-1 | TestKernelDeclaredInt32EnumProfile checks captured C# and Go writes, numeric event history, named model reads, scalar/array/nullable values, signed limits, declared composites and updates to zero. Actual .NET typed decoding verifies 96 exported event/model readbacks. Unknown C# writes/string reads are not safe admission: scalar default fallback, disappearing nullable values and null array elements remain hazards |
Frozen enum representations and mapping; kernel ProjectionFactory.GetMergedFromProperties / GetMergedJoinProperties at ae5e00a8, C# client variant lowering at 2e31b0d | Implemented within the admitted root-member profile; Go-specific ASCII-only enum AutoMap qualification; no callback or constant discovery | TestEnumDeclarationAndRepresentationAreFrozen, TestEnumHooksAreNeverCalled, TestEnumProjectionCopyLiteralsAndRefusals, TestEnumHistoricalTablesAndMigrationBoundary, TestEnumUndeclaredValuesIssueNoAppendRPC. Final-graph case-insensitive AutoMap, EntersOn/global handlers, enum variant keys/generated joins and invalid literal targets have focused regressions; TestEnumInvalidProjectionRegistrationIssuesNoRPC asserts local refusal. TestKernelEnumProjectionCaseAndLoweredVariants checks differently cased From/Join and EntersOn/generated joins with named model readbacks. Copy requires the same table/profile; literals validate membership before hooks; arithmetic and cross-integer conversions fail. Ambiguous or Unicode enum AutoMap names and enum correlation keys fail explicitly. TestEnumAutoMapLiteralPropertyExpressions and TestEnumReboundExplicitPropertyExpressionsRefuseBeforeRPC reject literal-looking source expressions, including rendered naming policies. TestEnumCollectionAutoMapValidatesEncodedMode and TestEnumNestedAutoMapUsesContainingProjection check source-enum-only conversions against encoded collection/nested modes; TestKernelEnumExpressionAndInheritanceControls exercises safe automatic reads, literal-name exclusions/overrides and Inherit/Disabled collection controls. Collection Inherit remains enabled in the pinned merger; nested Inherit consults the containing Projection, not the immediately enclosing nested override. This is enum-boundary validation, not qualification of all C# inheritance or admission of nested enum placements. TestEnumSparseEveryUnusedCatalogRebindRefusesBeforeRPC covers absent-source enum targets after catalog-wide rebinding; TestEnumDirectDottedRootAutoMapRefusesBeforeRPC, TestEnumDottedRootJoinRegistrationRefusesBeforeRPC and TestEnumDottedRootGlobalGeneratedJoinRefusesBeforeRPC refuse raw dotted root names selected by enum-involved mappings. Field-index ownership distinguishes these from genuine nested ordinary paths without changing generic metadata APIs. TestKernelEnumSparseEveryMissingThenKnown checks a safe sparse mapping remains nil before known enum One/Zero updates, with processed markers and an ordinary nested literal control. Enum endpoint migrations return ErrUnsupported |
See declared enum codecs for placement restrictions, missing/null semantics and migration guidance. This is not broad enum parity: concepts, protected/derived/nested enum shapes, alternate backing types, aliases and undeclared combinations remain unsupported. No shared Fundamentals.Go enum API or root dependency change is involved.
Registered derived codecs
Section titled “Registered derived codecs”| C# authority | Go surface and status | Evidence and limits |
|---|---|---|
DotNET/Events/EventSerializer.cs:40–55, DotNET/Schemas/JsonSchemaGenerator.cs:323–332 at 2e31b0dfb; Fundamentals DerivedTypeJsonConverter.cs:26–75 at 14037b1f (7.19.6) | Partial: immutable serialization.NewCodecs / Derived[Family, Concrete], CompileWith, events.WithCodecs, readmodels.WithCodecs; unprotected open-object wire schemas and a closed variant graph | TestDerivedCapturedDotNETPayloadsAndOpenSchema compares actual .NET bytes under both naming policies; fixtures and source/options provenance are in serialization/testdata/derived. TestKernelDerivedCodecsCollectionsOrdinaryChildrenAndReplay covers Go and captured C# event payloads, copied collections, live ordinary model child updates, relationship-free replay and fail-closed relationship replay admission. The executed .NET probe also decodes both producers’ kernel read-back bytes under both naming policies. No protected-family claim; derived children are the next row |
| Fundamentals converter’s immediate reflected properties | Go-specific: immediate acronym-preserving camelCase; nested ordinary objects use configured naming; explicit Go tags retain priority | TestDerivedMetadataSnapshotsAndTagPolicy, captured unsafe-direct fixtures. C# ignores immediate JsonPropertyName; Go does not. Direct family-valued derivative properties fail before I/O, including aliases/pointer layers/promoted fields: C# loses the nested discriminator and silently decodes null (Fundamentals #1149). Arrays are not an equivalent substitute for single-child domain semantics |
| Converter dispatch and kernel schema-unknown numeric conversion | Go-specific fail-closed validation: exact dynamic types, required unique exact discriminator, typed-nil rejection, ±2^53 integer subtree bounds, all-variant security audit | TestDerivedRegistrationValidationAndUnusedVariantAdmission, TestDerivedExactDiscriminatorAndFailureAtomicDecode, TestDerivedNilNumericMapAndCycleBoundaries, TestDerivedSecurityAuditsUnusedVariantsAndProtectedSiblings, TestDerivedCallbacksContainPanicsAndPreserveOrdinaryCauses. Classified families and active protection walks over open families remain unsupported; no metadata stripping or coarse protection promotion |
| Frozen catalogs, typed consumers and serialization snapshots | Implemented for admitted families: variant-qualified metadata, original historical plans, callback-free snapshot rebinding and frozen provider results | TestHistoricalDerivedCatalogRetainsOriginalDiscriminators, TestDerivedDescriptorsFreezeOptionsProvidersAndHistoricalGenerations, TestDerivedReconnectReusesFrozenClassificationsAndCodecs, TestDerivedModelReadsReleaseWatchWindowsAndFrozenProviders, TestDerivedEventReadsAndReactorDispatchUseCompiledFamily, TestActiveReducerUsesDerivedEventAndInitialStatePlans, TestDecisionDerivedDecodeFailureIssuesNoModelOrToken, TestDerivedProjectionCopiesWholeFamiliesButDoesNotInferChildren. Variant fields are not ordinary migration paths |
Derived children / DotNET/Projections/ModelBound/ChildrenDefinitionExtensions.cs (ProcessChildrenFromAttributeCore, ResolveConcreteChildType, AddDerivedTypeDiscriminatorMapping) at v19.32.3 (e72af7ea) | Partial: model-bound children(...) on a slice/array of a family with exactly one registered derivative resolves that concrete type for fields, key/identity and parent inference, mapping tags, joins, removals and AutoMap exclusions, and writes _derivedTypeId: $value(<id>). Recursion identity is family plus concrete type. Go-specific: every non-join child From carries the discriminator in both front ends, where C# stamps only the model-bound [ChildrenFrom] creator; kernel ProjectionEventContextExtensions.Project at v19.32.3 adds a child for any non-join From whose identity is absent, so a keyed update after removal or before creation would otherwise write a child the family codec cannot read, and on an existing child the constant rewrite is idempotent. The captured C# definition has only the creator From, so the golden comparison is unaffected. A derivative’s key, no-auto and not-projected tags are type metadata: they neither make HasMappings discover a projection nor refuse a whole-family property, so a fluent derivative’s key tag does not block reuse of its family elsewhere. Grandchild collections tagged on the derivative are compiled, whereas C# ProcessNestedChildren walks the unresolved interface and so drops constructor-parameter grandchildren; fluent Children accepts the family with the derivative’s builder (C# fluent cannot name a concrete child type); zero or several derivatives, non-literal discriminators, globals targeting any casing of _derivedTypeId, and derivative declarations outside a children collection (including nested families) fail compilation, where C# keeps the interface and writes no discriminator or silently drops the declarations | TestSingleDerivedChildFrontendsAndCapturedCSharpDefinition compares both front ends to definitions captured by running the packaged C# 19.32.3 client (projections/testdata/derived-children, with capture harness and provenance); TestDerivedChildPayloadMatchesCapturedCSharpSerialization compares payloads and decodes the C# bytes. Qualified differences: C# DefaultNamingPolicy emits PascalCase child targets although its derived serializer writes camelCase; Go uses the derivative plan’s names, and explicit Go json tags keep their spelling under both policies. TestDerivedChildrenUseConcreteDeclaredIdentityAndPathLocalRecursion, TestDerivedChildInferenceRejectsMissingAmbiguousAndUnrepresentableFamilies, TestDerivedDiscriminatorCannotBeWrittenThroughFluentOrGlobals, TestDerivedDeclarationsOutsideChildrenCollectionsAreRefused, TestDerivedChildPreparationAuditsUnseenVariantsAndRejectsBeforeTransport, TestDerivedChildSnapshotsRebindWithoutCallbacksOrRootIndexCollisions, TestDerivedChildInitialValuesKeepCollectionsWithDiscriminatorAndRejectTypedNil, TestDerivedChildKeyedUpdatesCarryTheDiscriminator, TestDerivedChildRebindWithMissingEventFailsWithoutPanicking, TestFluentDerivativeKeyTagIsNotAProjectionDeclaration, TestNewClientAdmitsFluentDerivativeReusedAsWholeFamilyProperty. TestKernelSingleDerivedChildrenCreateJoinRemoveReaddAndReplay on 19.32.3-development covers registration, create, join update, removal, a join that does not resurrect a removed child, re-add, raw and typed read-back with the discriminator, ReplayProjection refusal, and an observer replay that records a read-model occurrence, under both naming policies; TestKernelDerivedChildKeyedUpdateRecreatesADecodableChild shows the kernel recreating a removed child, and creating a never-created one, from a keyed WithNodes update, and both read back with the discriminator. Protected families stay refused (#64) |
Duplicate-member rejection is a Go-specific safety contract, not broader C#
parity: TestDerivedDuplicateMembersFailBeforeDecodeOrSnapshotRebinding and
TestDerivedModelDuplicatesFailBeforeIDNormalizationAndDelivery cover the entire
input before lossy normalization, including unknown arrays and maps.
TestDerivedHistoryUsesFrozenPlanAndRejectsAmbiguousRawDocuments also covers raw
collections and snapshots without partial progress, and
TestDerivedInitialStateRebindKeepsCapturedPlansAndOriginalHandles covers captured
client plans, stable model handles and shadowed embedded fields.
TestDerivedCaseFoldedDiscriminatorPropertiesAreNeverAdmitted reserves metadata
spellings, while TestDecisionErrorCallbacksRemainInsideRecovery covers panicking
error traversal and retained ordinary error identities.
See Register unprotected derived types. Enum profiles beyond the declared Int32 subset, binary map/derived/concept/protected placements, custom representation, complex map-key and GeoJSON work stays under #64. Derived children are described in project derived children.
Patterns queries
Section titled “Patterns queries”| C# authority | Go surface and status | Evidence and limits |
|---|---|---|
Source/Clients/DotNET/Patterns/{Patterns.cs:21–119,IPatterns.cs,PatternConverters.cs} at 2e31b0dfb; Source/Kernel/Concepts/Patterns/{FacetSet,FacetName,PatternConfidence,TimeBucketExtensions}.cs | Implemented typed query workflow: EventStore.Patterns, patterns.Service.GetPatterns/GetUsualActions/GetPatternsAt/GetPatternsForScope/GetScopes, immutable facets, context-first cancellation and server-default thresholds/limits | TestCSharpQueryRequestGoldens, TestCriteriaDefaultsZerosAndNoInventedValidation, TestTimeBucketsAndExplicitZeroMoment, TestOmittedMomentUsesLocalNowAndRejectsUnrepresentableMoment, TestResponseGoldensAndSnapshotOwnership, TestScopeResponseLabelsAndEmptyFacts; fixtures are hand-derived, not captured .NET output. No SDK inference, sorting, discovery, training or scheduler |
Source/Kernel/Protobuf/patterns.proto, Contracts/Queries/QueryResult.cs, Contracts/Validation/ValidationResult.cs, Core/Patterns/BehaviorPatternDetails.cs at pinned kernel ae5e00a8 (19.29.4); same query/default contracts at C# baseline above | Implemented wire queries and explicit default semantics: zero confidence/limit request server defaults; local per-call codec preserves omitted default-true authorization and default-Error severity without changing generated contracts | TestCSharpScalarDefaultWireGoldens, TestCSharpRequestZeroWireAndExplicitLimits, TestDefaultCodecThroughActualGRPC, TestEnvelopeFailuresRedactedAndUnknownSeverities, TestMalformedResponsesFailAtomically, TestResponseTimestampGrammar, TestMalformedTimestampResponsesFailAtomically, TestNilReplyAndMalformedCodec. Any validation diagnostic fails as in C#; unknown severities retain numeric values. Repeated nil/empty data are wire-equivalent; missing replies/required dates fail. Source/Kernel/Contracts/Primitives/SerializableDateTimeOffset.cs at 2e31b0dfb emits seven fractional digits using "O"; response dates accept zero to seven fractional digits with Gregorian local/UTC years 1–9999 and signed whole-minute offsets within ±14 hours. Raw offset minutes and precision are checked before Go parsing can normalize/truncate them; malformed dates fail the entire result without exposing timestamp text |
C# selected-store transport and PatternsNotSupported.cs; kernel Services.Patterns | Partial runtime evidence: all five public query workflows available on 19.32.3-development, with actual empty answers in a fresh namespace | TestKernelPatternsQueries uses real TLS/OAuth and selected-store queries; no seeded pattern provider or local inference. Nonempty mining, ranking and prediction accuracy remain unverified. Unsupported servers return typed UnsupportedError, not empty success; TestTransportErrorsKeepOriginalGraphWithoutSensitiveFormatting, TestCancellationDeadlineAndConstruction, TestStorePatternsCoordinatesMetadataAndNoCrossStoreCache, TestStorePatternsGenerationCancellationJoinsInflightQuery cover failure graph, lifecycle, authentication/correlation and isolation |
C# PatternConverters derives specificity and discards DTO labels/correlation | Go-specific: QueryResult[T] preserves response correlation; Scope additionally retains name/user name; BehaviorPattern.ID retains the server’s string facet-key ID; finite DateTimeOffset-representable time inputs and immutable copied collections | TestResponseGoldensAndSnapshotOwnership, TestScopeResponseLabelsAndEmptyFacts, ExampleEventStore_Patterns (compiling, kernel-dependent). Grouping keys are strings, not UUIDs or event-source IDs. Actor facets are facts, not authorization |
See Query established behavior. Server source resolves confidence as
requested > 0 ? requested : configuration.MinimumConfidence and result limits
as requested > 0 ? requested : 10; the SDK does not duplicate those defaults.
Zero is not a request for zero answers or a zero-confidence override. C# performs
no confidence range/finiteness validation, so Go forwards those values unchanged.
Client diagnostic routing
Section titled “Client diagnostic routing”| C# authority | Go surface and status | Evidence and limits |
|---|---|---|
DotNET/ChronicleClient.cs:105–129 at 2e31b0dfba489159b3db323238f16d0f277056b4, optional ILoggerFactory | Go-specific: chronicle.WithLogger(*slog.Logger) captures a borrowed logger at CaptureClient (immediately for NewClientContext); scalar last-wins and nil validation; per-client logger/handler identity capture, not destination-state capture | TestClientLoggerCapturePrecedenceAndArtifactOverride, TestClientLoggerDefaultIsCapturedAfterDeclarationAndBeforePreparation, TestClientLoggerValidationAndImmediateCapture, TestClientLoggersIsolateSharedRegistryConcurrentFailures; explicit reactor/reducer/read-model logger overrides survive pointer equality, store binding and reconnect. TestClientLoggerPristineDefaultBridge uses a fresh subprocess and actual preparation failure to demonstrate that the captured pristine default follows the standard logger’s current writer and a later custom-default bridge. Strict destination isolation requires an explicit stable handler/writer. No full C# logging DI parity or framework adapter dependency |
| Observer and lifecycle diagnostics / C# client logger collaborators | Go-specific bounded SDK records: fixed operation/stage/category only, no arbitrary error formatting/traversal or sensitive metadata; handler panics contained without changing operational outcomes | TestClientLoggerReconnectRetainsCaptureAndRedactsStatus, TestObserverResubscriptionUsesFrozenArtifactRoute, TestClientPreparationLoggingDoesNotInspectCauseOrPanic, TestArtifactLoggingContainsPanicValuesAndReporterPanics, TestDiagnosticsNeverInspectArbitraryErrors, TestLoggingHandlerPanicsDoNotEscape, ExampleWithLogger; TestClientLoggingAfterHookPanicPreservesRuntimeEffectAndCheckpoint verifies the actual returned-effect append RPC and exact success checkpoint despite after-hook failure and handler panic. TestClientLoggingReplayFailureRetainsArtifactRouteAndGeneration verifies notifier-failure resubscription on the same generation, explicit override/client fallback and custom-handler identity across later default changes, with recording and panicking handlers. These use the production runtime over bufconn, not a live kernel; no exactly-once guarantee is implied beyond controlled invocations. Borrowed handlers are never closed. Callers own concurrency, cooperative handler lifetime (no self-joining client Close), handler-added fields/mutable state, application logs and explicit error reporters. Kernel exception-message fields and returned error graphs are outside the log-redaction contract |
| Constructor dependencies / C# service provider | Go-specific: the diagnostic option does not bind *slog.Logger; explicit constructor closures or provider bindings remain required | TestLoggerOptionIsNotAConstructorService; the container-free resolver rejects zero logger construction. Provider API and root dependency graph are unchanged |
See Use one application-owned logger.
Owned gRPC stats-handler injection
Section titled “Owned gRPC stats-handler injection”Go-specific: WithGRPCStatsHandler(stats.Handler) borrows a trusted handler
for every owned connection generation, including reconnect. This is the bounded
extension approved in #53, not
C# interceptor parity. The C# connection ownership/lifecycle comparison below
remains authoritative for existing behavior.
TestGRPCStatsHandlerValidationAndLastWins covers nil/typed-nil rejection,
borrowed-channel conflicts in both option orders, last-wins precedence and
handler ownership. TestGRPCStatsHandlerOwnedGenerationsUnaryAndStream exercises
real validating TLS connections, unary and streaming callbacks on initial and
replacement owned channels, and verifies that neither handler is closed.
Trusted callbacks must preserve contexts, return promptly and support concurrent
use; arbitrary callback safety is not enforced. No arbitrary dial options,
interceptors, retries or stronger write-outcome guarantees are added. The pinned
otelgrpc consumer remains separate under
#80.
Baselines and evidence
Section titled “Baselines and evidence”C# reference: Cratis/Chronicle revision 2e31b0dfba489159b3db323238f16d0f277056b4, paths below relative to Source/Clients/. Generated contracts independently pin Chronicle 19.32.3, commit e72af7ead164425141f29db61a9fe7b4915767e6. That release adds generated contracts/alerts and contracts/eventsources packages and EventSource fields on append requests and event contexts; the SDK has no public surface for alerts or event-source registration yet and leaves EventSource unset. Real-kernel evidence uses 19.32.3-development, including test-owned TLS/TCP connection interruption and recovery against a running kernel. Whole-kernel restart and a real multi-node deployment remain unverified. SDK release numbers are independent of protocol versions.
Registration, namespace, serialization and concurrency corrections follow v19.29.4 (ae5e00a8): Source/Clients/DotNET/Events/EventTypes.cs, Source/Clients/DotNET/EventStoreNamespaceName.cs, Source/Infrastructure/Schemas/{TypeFormats,JsonSchemaExtensions}.cs, Source/Infrastructure/Json/ExpandoObjectConverter.cs, Source/Kernel/Storage.MongoDB/ExpandoObjectConverter.cs, and Source/Kernel/Core/{EventTypes/EventTypeRegistrar,EventSequences/Concurrency/ConcurrencyValidator}.cs (repository-root paths).
Statuses: Implemented means the named behavior has executable regression evidence; Partial names an implemented subset; Not implemented claims no usable surface; Go-specific identifies an intentional translation. The evidence column names test functions or the actual deterministic generation check, not source reading alone. Status applies to the row’s stated contract, not the entire C# feature family.
Read evidence at its actual scope:
- Compiler and source-derived goldens check declarations and wire shape, not execution of the C# client.
- Captured .NET fixtures are identified explicitly with their source and serializer profile; they do not qualify unrelated codecs or generations.
- Go transport tests exercise production SDK paths against controlled replies. Named kernel tests exercise their stated profiles on the pinned server, not every protection route, restart or multi-node deployment.
- Compiling examples and SDK adapter tests do not establish an Arc.Go consumer workflow. Borrowed-facade adoption is tracked separately in Arc.Go#29.
- Native minimum-Go build/unit checks, integration checks and the hosted CI matrix answer different questions. Test references here are not a new run report or a claim that every gate passed on the latest source state.
Admitted binary shapes
Section titled “Admitted binary shapes”The binary slice admits only these acyclic, ASCII-named shapes:
- Event binary leaves (
[]byte, including named slices over built-inbytewithout custom codecs) and one nullable pointer: capturedBinaryEvent.Payload/Optionalwrites, reads and schemas inserialization/testdata/binary/profile.json, plusBinaryChanged.Payload/Optionalkernel readbacks. - Binary event leaves in acyclic nested objects, including pointers to objects:
captured
BinaryEvent.Nested.Innerand the kernel event-history readback ofBinaryChanged.Nested.Inner. - Read-model binary leaves or one nullable pointer emitted at the root, including
promoted embedded leaves: captured
BinaryModel.Payload/Optionalschemas and Expando conversion pairs, plusBinaryWitnessModel.Payload/Optionalkernel Get readbacks. Named/promoted declarations reuse the qualified emitted representation; they are not new kernel shapes. - Ordinary From AutoMap between identical binary leaf representations with
ASCII names:
TestKernelBinaryEventsProjectionAndReadModeluses only From for rootPayload/Optionalunder Preserve and CamelCase. All kernel readbacks above belong to that same pinned witness.
Refused: binary copies through Join or VariantOf (including entering handlers
and generated joins), any recursive graph containing binary (even with recursion
only in an ordinary sibling), non-ASCII serialized names anywhere in the graph,
nested read-model binary, whole-object and explicit binary copies, binary
collections/maps/derived placements/concepts/protection/indexes/unique constraints/
subjects, migrations, literals/initial values/arithmetic, identity/key/correlation
uses and runtime ordinary-scalar profiles. Collection and map capture controls
are not admission evidence. Kelvin sign and long s names are refused rather
than relying on different CLR ordinal and Go EqualFold classes. Reducer payload
conversion retains the captured root-leaf profile; no reducer policy changes.
| Surface / C# reference | Go surface and status | Executable evidence / remaining boundary |
|---|---|---|
Canonical protobuf / Kernel/Protobuf (repository root) | Implemented: public contracts/, canonical embedded descriptor, immutable source/tool pins | TestCanonicalDescriptorIsEmbeddedAndCopied; scripts/generate-contracts.py --check; TestKernelRegisterAppendRead. No .NET/sibling checkout needed to generate |
URI/discovery/balancing / Connections/ChronicleConnectionString*, ChronicleServerAddressResolver, ILoadBalancerStrategy, DotNET/ChronicleOptions.LoadBalancerStrategy, ChronicleConnection.cs:133,299–300 at 2e31b0dfba489159b3db323238f16d0f277056b4 | Partial overall: multihost/SRV, name-server override and built-in strategies; Implemented borrowed LoadBalancer via WithLoadBalancer, once per attempt including single addresses and reconnect | Existing URI/discovery tests plus TestLoadBalancerSelectsEveryGenerationAndCopiesURICandidates, TestLoadBalancerSRVRefreshesCandidates, TestLoadBalancerReceivesSRVPriorityWeightOrderWithoutMutation, TestLoadBalancerFailuresRetryBeforeOAuthOrRPC, TestLoadBalancerAttemptCanceledByClose, TestLoadBalancerValidationAndLastWins; kernel witness TestKernelCustomLoadBalancerSelectsOAuthAndGRPCRelay. Go rejects URI-strategy and borrowed-channel conflicts instead of overriding URI selection, never disposes custom strategies, and always retries selection failures even for wrapped authentication statuses. API-key/certificate URI modes remain unsupported; discovery is bounded to 64 endpoints |
TLS/OAuth / Connections/OAuthTokenProvider, AuthenticationClientInterceptor | Implemented for TLS and selected-endpoint client-credentials acquisition/cache/refresh | TestOwnedTLSAndOAuthShareTrustPolicy, TestOAuthSingleFlightAndRefreshFallback, TestTokenWaitHonorsCancellation, TestInvalidTokenResponsesFailClosed, TestRefreshFailureUsesCompletionTime, TestConnectionLossAndRejectedOAuthRecover, TestExternalTokenInvalidation; rejected credentials are invalidated, with no transparent auth retry, disk token cache or external authority URI |
Connection lifecycle / Connections/ChronicleConnection, ConnectionWatchdog, CompatibilityValidator; ConnectionLifecycle.cs:102–156 at 2e31b0dfba489159b3db323238f16d0f277056b4 | Implemented generation supervision, stale-heartbeat detection, reconnect, readiness and joined shutdown; Go-specific WithOnConnected/WithOnDisconnected, ConnectionEvent, ConnectionHook | Existing supervision/shutdown tests plus TestConnectionHooksPairReconnectAndClose, TestConnectionHooksConcurrentEventSerialTransitionsAndCoalescing, TestConnectionHooksCanCallClientWhileRegistrationPending, TestConnectionHooksCloseContextBoundsJoin, TestConnectionHooksPanicsContainedAndLogged, TestConnectionHooksFailedStartupFiresNothing, TestConnectionHooksSkipKeepAliveOnlyPairsAtShutdown, TestConnectionHooksNilRejectedAndCaptureFrozen; kernel witness TestKernelConnectionHooksPairRealReconnectAndClose. Construction-time accumulating registration replaces runtime +=/-= (dedupe is moot; Go functions have no handler identity). Async dispatch never gates reconnect and Connected cannot fail the connection, unlike C# AggregateException rollback. Unstarted generations coalesce; delivered Connected pairs with Disconnected even on Close/Shutdown. Connected means authenticated protocol readiness, not registration or attachment. Unary dispatch still rechecks generation cancellation after token callbacks; registration sends are not attachment or durable checkpoint evidence |
Store/namespace / DotNET/ChronicleClient, EventStore, Registrations/* | Implemented for cached handles, ensure/list, separate store/namespace barriers, event registration outcomes/retries and automatic replay | TestStoreRegistrationCacheAndNamespaceIsolation, TestRegistrationFailureDoesNotPoisonCache, TestAutomaticReplaySharesStoreDefinitionsAndSeparatesNamespaces, TestFailedPassReportsProgressAndRetriesInBackground, TestReplayBarrierBlocksAppendUntilAcknowledged, TestSingleFlightRetriesAndSnapshots, TestCanceledWaiterDoesNotCancelOwnerAndFailureCanRetry, TestKernelReconnectReplaysRegistrationAndPreservesHandles. Bounded runtime projection additions are implemented; runtime registration of other artifact families, replacement and eviction remain unsupported. Startup preparation and registration of supported artifact families are described below |
Offline selected-store catalogs / DotNET/ChronicleClient.cs:GetEventStore, DotNET/EventStore.cs:EventTypes,ReadModels (baseline revision above) | Go-specific: Client.Catalogs(StoreName) exposes the existing frozen registry without constructing a store; connected handles share the same store binding | TestCatalogsMatchConnectedStoreAndBindProducer, TestCatalogsAreFrozenStoreReplacementsWithoutIO, ExampleClient_Catalogs; naming, producer identity, ForStore sequence binding, store replacement and immutability match connected descriptors. No registration/readiness claim; C# has no equivalent offline accessor |
Event declarations / DotNET/Events/EventTypeAttribute, EventTypes | Partial: Registry, RegisterEvent[T], immutable events.Type, Descriptor, Catalog | TestRegistration, TestConcurrentDuplicateRegistration, TestStoreRegistrationCacheAndNamespaceIsolation, TestRegistrationValidationOptIn, TestKernelSchemaValidationPreservesExistingGeneration, TestKernelGenerationWithoutMigrationsMatchesCSharp; historical generations and migrations are detailed in the event-evolution rows below; model-bound and descriptor metadata are detailed separately |
JSON/schema / DotNET/{ChronicleClient,ChronicleOptions}, DotNET/Events/EventSerializer, schema generator | Partial: one shared field plan; declaration-order objects and default System.Text.Json string escaping, preserved spelling, explicit policies/tags, Fundamentals scalars/concepts, nesting, promoted fields and typed collections | TestDeclarationOrderMatchesCSharpGolden, TestDefaultEscapingMatchesCSharpGolden, TestDefaultEscapingAllASCII, TestEmbeddedFieldsFollowEncodingJSONOrder, TestOrderedEmbeddedFieldsHonorNamingAndOmission, TestSchemaAndSerializationShareNames, TestNumericFormatsMatchKernel, TestNullableScalars, TestDictionaryIntegerPrecision, TestUnsignedMongoDBRange, TestOmitZeroMatchesEncodingJSON, TestKernelScalarAndNullableRoundTrips, TestKernelDictionaryPrecisionGuard, TestUnsupportedShapesFailBeforeRegistration; enum profiles beyond the declared Int32 subset, custom codecs, unsupported binary placements, unregistered/protected polymorphism and GeoJSON fail explicitly. Explicit unprotected derived families are detailed below. Protection classifications are detailed below. Dictionary ordering and encoder limits are described below. Concrete recursive schemas/codecs are covered under projections part 3 |
Binary (byte[]) / Source/Infrastructure/Schemas/TypeFormats.cs:43,101, Source/Clients/DotNET/Schemas/JsonSchemaGenerator.cs:358–370, Source/Clients/DotNET/ReadModels/ReadModelJsonSerialization.cs:129–134 at Chronicle ae5e00a8 (19.29.4); Source/DotNET/Fundamentals/Json/JsonValueExtensions.cs:127–129,210–211 at Fundamentals 14037b1f (7.19.6) | Partial: built-in []byte and named slices over built-in byte are base64 string leaves; one nullable pointer and nested event object members share the frozen plan; read models admit only emitted root binary leaves (including promoted embeddings); nested read-model binary and binary arrays are refused before registration | Actual packaged binary fixture; TestBinaryPackagedWritesMatchCSharp, TestBinarySchemaMatchesPackagedGenerators, TestBinaryReadsFollowQualifiedProfile, TestBinaryMissingNullEmptyAndNullable, TestBinaryMissingFieldsPreserveAbsentEmbeddedPointers, TestBinaryFreeArrayDecodeAllocationsIgnoreAbsentItemGraph, TestBinaryDecodedValuesDoNotAliasInput, TestBinaryFieldMetadata, TestScalarArrayFieldsHaveNoElement, TestBinaryCaptureSourcesRemainOptInAndPinned, TestBinaryCaptureValidationRejectsIncompleteEvidence. C# class versus record required-list controls are captured; Go retains existing slice/pointer omission and value-struct required rules. Go refuses captured C# whitespace reads and null array elements; no unpadded/URL-safe/nonzero-padding-bit acceptance. [N]byte remains an integer array |
Binary kernel conversion / Source/Kernel/Storage.MongoDB/BsonValueExtensions.cs:34–36,224–233 at Chronicle ae5e00a8; Source/Kernel/Core/Projections/Engine/Expressions/EventValues/EventValueProviderExpressionResolvers.cs:113–119,139–158, Source/Kernel/Storage.MongoDB/BsonValueExtensions.cs:176–182 at the same revision | Partial: binary uses event payloads (including nested objects), emitted root read-model leaves and qualified identical-representation leaf-to-leaf From AutoMap copies only; Join and VariantOf binary copies and recursive binary graphs are refused; field metadata distinguishes Binary from primitive String, with IsPrimitive and precomputed ContainsBinary capability checks | TestKernelBinaryEventsProjectionAndReadModel appends Go values and raw packaged C# payloads, then compares typed event reads (including nested binary) and emitted root leaf AutoMap model reads under both naming policies with generation validation enabled. The former whole-object Nested.Inner AutoMap control is removed, not generalized into a supported nested read-model profile. TestBinaryCapabilitiesRefuseBeforeIO enumerates the refused capabilities. TestBinaryProjectionMappingsRequireSameRepresentation, TestBinaryExplicitProjectionMappingsAndExpressionsRefuse, TestBinaryProjectionKeysAndJoinsRefuse, TestBinaryAdditionalProjectionBoundariesRefuse, TestBinaryUnicodeAutoMapRefusesBothDirectionsAndJoins, TestBinaryNamingRebindRevalidatesFinalGraph, TestBinaryReadModelIdentitiesRefuseBeforeRegistration, TestBinaryInitialValuesRefuse, TestRuntimeOrdinaryProjectionProfilesRefuseBinary, TestBinaryNamingRebindPreservesRepresentation, TestBinaryUnsupportedPlacementsFailBeforeRegistration, TestBinaryConceptsAndUnusedDerivativesRefuse. TestBinaryArraysFailBeforeRegistration refuses all binary collections, including named/nested binary arrays. The pinned kernel preserves Chunks:["","AQ==","+/8="] in event history but returns Chunks:[[],[{}],[{},{}]] from projected model Get for both Go/C# producers and naming policies. Its event-value converter recurses with the array schema instead of the item schema, enumerates each byte array into a list of bytes, and the BSON converter’s primitive-byte fallback produces empty documents: lost bytes cannot be repaired by the client (Chronicle#4595). The witness now asserts registration refusal for that captured control and compares every admitted leaf case without accepting the corrupt representation. TestBinaryUniqueConstraintsRefuseBeforeRegistration, TestBinarySubjectsRefuseBeforeRegistration and TestBinaryIndexesRefuseBeforeRegistration cover tags and explicit declarations, including indexed/unique objects containing binary. The pinned kernel’s Source/Kernel/Core/Events/Constraints/UniqueConstraintDefinitionExtensions.cs:GetPropertiesAndValues at ae5e00a8 hashes ToString(), collapsing every byte array to System.Byte[]; Source/Clients/DotNET/SubjectResolver.cs:ResolveFrom likewise uses ToString() rather than the base64 wire value. Maps, derived variants, concepts, protection/index declarations (including WithIndexes), unique constraints, subjects (including WithSubjectProperty and ID fallback), explicit projection mappings, initial values, runtime ordinary-scalar profiles, literals and correlation/identity uses remain refused; AutoMap with binary requires qualified ASCII names and binary leaves at both endpoints, never binary-owning objects. Whole-object copies can hit the pinned converter’s unconditional single-value unwrap, recursively converting byte arrays against an object schema into lists. TestBinaryObjectAutoMapRefusesEvenIdenticalRepresentations guards From/Join and naming-recompiled graphs independently of read-model admission. TestBinaryReadModelOnlyAdmitsRootLeaves refuses arbitrary nested model shapes instead of extending the proof; nested event payloads remain supported. No protected binary, explicit binary mapping or arbitrary custom codec claim |
Binary admission boundaries / Source/Kernel/Core/EventSequences/Migrations/{EventMigrationExpressions,EventTypeMigrations}.cs, Source/Kernel/Storage.MongoDB/PropertyExtensions.cs:18–19, Source/Kernel/Storage.MongoDB/ExpandoObjectConverter.cs:22–90,330–339, Source/Kernel/Storage/WellKnownProperties.cs, Source/Infrastructure/Properties/PropertyPath.cs:26–31,365–406, Source/Infrastructure/Properties/DerivedPropertyFunctions.cs:41–48, Source/Infrastructure/Json/ExpandoObjectConverter.cs:51–57,104–106, Source/Infrastructure/Schemas/JsonSchemaExtensions.cs:97,128, Source/Kernel/Core/Projections/Engine/Expressions/EventValues/LiteralExpressionResolver.cs:40–44 at Chronicle ae5e00a8 (19.29.4) | Go-specific fail-closed: all migrations with binary-containing endpoints are unsupported, including identity/pass-through; binary-containing plans require ASCII, case-insensitively unique, non-reserved property names throughout their graphs, including ordinary siblings, promoted/nested fields and registered derived variants; binary-only violations return ErrUnsupported at compilation and every naming recompile; exact duplicates retain ErrInvalidConfiguration; real compile errors take precedence only in binary-containing graphs, while binary-free types retain immediate duplicate refusal and existing naming behavior; binary identities include case-insensitive id and _id | TestBinaryMigrationEndpointsRefuseBeforeCallbacks covers authoring and catalog revalidation for every operation, nullable/nested endpoints and both directions; TestBinaryCapabilitiesRefuseBeforeIO includes migration cases. TestBinaryNamesRefusePlan, TestBinaryNamesValidateDerivedVariantMembers, TestBinaryNamesRefuseNonASCIIRegardlessOfCaseFolding and TestBinaryNamesRefuseNonASCIIUnderEveryNamingPolicy cover both field orders, cross-level path partitions, reserved function/accessor tokens, ASCII/Unicode case collisions, ordinary siblings, nested/collection/promoted fields, used/unused derived variants and naming recompilation. Simple names start with an ASCII letter, followed by ASCII letters, digits or underscores; no dots, expression syntax or underscore-prefixed names. Every nested case variant of id is refused across the binary-containing graph; MongoDB recursively merges id/_id, restores only id/Id and can lose nested ID bytes or overwrite Id/_id siblings. Root identity handling otherwise stays unchanged. The underscore-prefix refusal includes the full pinned sink-owned set: __lastHandledEventSequenceNumber, __initialized, __subject, __subjects. Case-insensitive value is also refused in any object containing binary, preventing the single-value unwrap. TestBinarySinkAndValueNamesRefusePlan, TestBinaryValueNamesRefuseAfterNaming, TestBinaryCompileErrorsPrecedeDeferredDuplicates, TestBinaryFreeSinkAndValueNamesRemainAdmitted and TestBinaryFreeWholeObjectAutoMapRemainsAdmitted cover these boundaries, diagnostic identity and ordinary-type controls. The full pinned derived/accessor set is Week (with or without ()), $this, plus the *NotSet* sentinel; true/false also preempt ordinary event expressions. ASCII sibling comparison agrees with CLR ordinal casing and Go EqualFold; all non-ASCII names are refused. TestBinaryNamesRefuseGoFoldAliases covers Kelvin/long-s pairs in both field orders; TestBinaryASCIIDecodeSelectsOnlyIntendedField checks exact and differently cased ASCII input. TestBinaryJoinAutoMapRefusesBeforeRegistration and TestBinaryVariantAutoMapRefusesBeforeRegistration refuse unwitnessed copies with value-free ErrUnsupported. TestBinaryCapabilityMetadataIncludesRecursiveOwners and TestBinaryGraphRefusesUnrelatedRecursiveBranch refuse recursive binary graphs; TestBinaryFreeDuplicatePrecedesLaterUnsupportedType pins origin/main duplicate precedence. TestBinaryFreeNamesKeepAdmissionAndRecompilation, TestBinaryFreeNamingRetainsExactDuplicateRefusal, TestNonBinaryDottedCollisionKeepsFirstMatchAndSnapshot and TestNonBinaryCollisionKeepsArtifactNamingBehavior retain binary-free naming, lookup, snapshots, exact-duplicate refusal and artifact rebinding. TestBinaryAmbiguousPathsRefuseBeforeIO, TestBinaryConstraintRebindRefusesNewPathAmbiguity, TestBinaryProjectionRebindAmbiguityRefusesBeforeIO, TestBinaryModelNamingAmbiguityRefusesBeforeIO and TestBinaryModelKeysRefuseNewNamingAmbiguity cover artifact admission and client construction. TestBinaryRebindRechecksEveryKeyLikePath independently checks From/Join/removal keys and parents, composite parts, model/variant keys and child identities in lowered graphs; TestBinaryAutoMapAliasCandidatesRefuseBeforeIO and TestBinaryAutoMapChecksGeneratedTargetCandidates refuse generated paths with hidden binary candidates, including excluded owning objects; TestBinaryPathCapabilityIncludesAmbiguousNestedCandidates, TestBinaryDottedInitialValuesRefuse, TestBinaryReadModelIdentitiesRefuseBeforeRegistration, TestBinaryExpressionTextDoesNotImplyEventPath cover detached nested-path ambiguity, emitted root ownership, sink key aliases and expression-kind dispatch. Kernel split/combine operate on JSON strings and then convert each migrated generation with its target schema; no packaged binary migration qualification exists. Use qualified payload/AutoMap paths only; do not migrate binary-bearing generations through generic operations |
Correlation/auditing / DotNET/Auditing, Identities/Identity; connection correlation interceptor | Implemented for immutable explicit correlation, ordered causation and deduplicated actor chains | TestImmutableMetadata, TestIdentityDeduplicatesCycles, TestCorrelationJSON, TestAppendMetadataAndNamedTags, TestDotNETGuidVector, TestDateTimeOffsetPreservesOffsetAndTruncates; no implicit process/program root causation or ambient context |
Ordered additional event information / DotNET/Events/EventSerializer.cs:Serialize,ProvideAdditionalInformationFor at 2e31b0dfba489159b3db323238f16d0f277056b4 | Go-specific bounded events.EventEnricher and expiring EventContent: sequential providers share ordered content, but can only edit declared fields through the frozen codec plan; no arbitrary JSON/schema extension or DI discovery | TestAuditingSelectsSnapshotsAndRunsDuplicateEnrichersInOrder, TestEventContentOwnsOrderedValuesAndExpiresEachHandle, TestEventContentReplacementUsesNormalGraphChecks, TestEventContentUsesRegisteredDerivedPlan, TestEventContentLateCodecCannotPublishIntoNextProvider; reference |
Actor/correlation/causation providers and root / DotNET/{ChronicleClient,EventSequences/EventSequence,Identities/BaseIdentityProvider,Auditing/CausationManager}.cs at 2e31b0dfba489159b3db323238f16d0f277056b4 | Go-specific borrowed context-first providers and opt-in root. Explicit correlation presence wins (zero generates fresh); handled empty causation masks context; root preserves upstream Root. Go defaults remain NotSet/fresh missing correlation/no implicit root, not C# System/process-root defaults | TestExplicitZeroCorrelationBypassesProvider, TestHandledAuditValuesMaskParentAndPreserveRoot, TestExplicitNotSetHasContextPresence, TestUnitOfWorkResolvesBeginThenStagesOnce, TestHandledZeroStageCorrelationDoesNotInheritBoundCorrelation; callbacks never rerun at commit/reconnect |
| Outgoing effect boundary / same C# serializer and event-sequence sources | Go-specific fail-closed, payload-free callback errors; tagged subject immutable, opaque custom subject resolver read-only unless explicit subject bypasses it; SDK reactor built-ins preprepared before custom effects without growing EventAppender | TestEnrichmentFailuresArePayloadFreeAndPreventAllSDKIO, TestProviderCancellationErrorIsNotCallerCancellation, TestEnricherCanCloseClientWithoutHoldingPreparationLease, TestEnrichmentFreezesSubjectAndRefusesOpaqueDependencies, TestReactorPreparesAllBuiltinsBeforeAnyCustomEffect, TestProtectedRevisionRunsEveryProviderOnce, TestUnprotectedRevisionUsesOutgoingContentAndAuditOnce; custom appenders and multiple effects do not gain transaction atomicity |
| Enrichment/audit kernel witnesses | Go-specific integration contract, not executed-.NET equivalence | TestKernelEnrichmentAuditAppendUnitReturnedEffectAndRevision checks stored protobuf content, actor, correlation and causation for immediate append, unit, returned effect, and unprotected revision against the pinned kernel, including the revision system-event audit. Revisions retain the kernel-generated fresh correlation (Kernel/Core/Sequences/Revise.cs:70), not the provider’s selected correlation. C# references above are source evidence only. Identity rename’s separate bounded workflow is described above |
Single append / DotNET/EventSequences/EventSequence, AppendResult | Implemented for single append, routing, subject/time, tags, named tags and complete results | TestAppendScopesOnTheWire, TestAppendMetadataAndNamedTags, TestAppendDomainRejectionsPreserveEveryDiagnostic, TestMalformedAppendResponsesFailClosed, TestKernelRegisterAppendRead |
Concurrency / DotNET/EventSequences/Concurrency/OptimisticConcurrencyStrategy, ConcurrencyScopeConverters | Implemented for default/resolve/exact/no-match/no-check single-event scopes | TestAppendScopesOnTheWire, TestProtectedAppendCannotSilentlyDowngrade, TestAppendValidationDoesNotDispatch; TestKernelExactIsAnUpperBound accepts absent/lower tails and rejects greater tails; the smoke test rejects a second protected-empty append |
Configurable concurrency / DotNET/ChronicleOptions.cs, DotNET/EventSequences/Concurrency/{ConcurrencyOptions,IConcurrencyScopeStrategy,OptimisticConcurrencyStrategy}.cs (baseline revision above) | Implemented: WithCheckFirstAppendIntoAScope, WithDefaultConcurrencyStrategy, ConcurrencyScopeStrategy.GetScope(ctx, sequence, filter) | TestFirstAppendPolicyAndSubjectsAcrossAllPaths, TestDefaultStrategyReplacesAutomaticScopesAndExplicitScopesWin, TestKernelConfiguredFirstAppendRejectsConcurrentWriter; defaults remain optimistic with first-append protection off. Explicit scopes bypass the replaceable strategy; Resolve still honors first-append policy. Strategy callbacks can read the sequence tail themselves or return Resolve for exactly one deferred tail read |
Selected-filter concurrency / DotNET/EventSequences/Concurrency/{ConcurrencyScopeStrategies,IConcurrencyScopeStrategy,OptimisticConcurrencyStrategy}.cs, DotNET/ChronicleOptions.cs (baseline revision above); Arc Source/DotNET/Chronicle/Commands/ConcurrencyScopeBuilder.cs at 7c1e78075b737df64f69fddfaae83374f75e3612 | Implemented: Sequence.ResolveScope(ctx, ScopeFilter) (Scope, error) invokes the same configured strategy/first-append policy as automatic append resolution | TestResolveScopeIndependentTargetsStageWithoutReresolving, TestResolveScopeUsesCustomStrategyAndFirstAppendPolicy, TestResolveScopeExplicitStrategyExpectationsDoNotReadTail, TestResolveScopeValidationCancellationAndFailuresNeverAppendOrRetry, TestClientConfiguredPolicyResolvesExplicitFilters, ExampleSequence_ResolveScope; independent A/B expectations survive staging/batch dispatch. Go accepts an already normalized filter rather than Arc attributes; no append, retry or Arc dependency |
Declared subjects / DotNET/SubjectResolver.cs, DotNET/EventSequences/EventSequence.cs:409,561,738 (baseline revision above) | Go-specific: events.WithSubjectResolver[T](func(T) (events.Subject, bool)) on RegisterEvent[T]; append override → declaration resolver → source ID | TestSubjectResolverDeclarationAndLookup, TestFirstAppendPolicyAndSubjectsAcrossAllPaths; typed callback and compiled subject tags both use the existing resolver slot, accept appended values and pointers, and resolve once at staging for units of work; false/nil represents C# null. Existing nonblank append-subject validation also applies to resolved subjects |
Sequence-number values / DotNET/EventSequences/EventSequenceNumber.cs:15–128 at 2e31b0dfba489159b3db323238f16d0f277056b4 | Implemented constants/predicates and sentinel preservation: events.{First,Unavailable,Max,BeforeFirst}, SequenceNumber.{IsActualValue,IsUnavailable,IsBeforeFirst}; Go-specific checked pure Next, Add(uint64), Subtract(uint64) returning (SequenceNumber, error) | TestSequenceNumberConstantsAndPredicates, TestSequenceNumberSentinelReceiversArePreserved, TestSequenceNumberCheckedArithmetic, TestSequenceNumberNextActualBoundaries, TestSequenceNumberArithmeticRoundTrips, ExampleSequenceNumber_Next; all three high values are non-actual and preserve a sentinel receiver, including BeforeFirst.Next. Actual overflow/underflow/reserved crossing returns Unavailable plus ErrSequenceNumberRange (errors.Is), not C# unchecked wrapping; unsigned amounts replace ulong/int/sequence-number operators, so callers reject negatives before casting. TestSequenceNumberHelpersPreserveServerNextContract keeps Sequence.Next(ctx) distinct; TestSequenceNumberSentinelsPreserveConcurrencyProtocol keeps exact sentinels invalid and no-match encoded as Unavailable plus explicit flag, never raw BeforeFirst. Numeric/JSON tests do not establish full uint64 BSON range parity (#8) |
Empty concurrency dimensions / DotNET/Events/EventSourceType.cs, DotNET/EventSequences/Concurrency/ConcurrencyScopeConverters.cs:26-34 (baseline revision above) | Implemented: empty source classification/stream type/stream ID are non-narrowing, like reads; source IDs and scope labels remain validated | TestEmptyScopeDimensionsAreConsistentWithTailReads; nonempty dimensions and append route defaults retain their existing wire spelling |
Append operation metadata / DotNET/EventSequences/EventSequence.cs:463-469,606-613 (baseline revision above) | Go-specific: AppendWithMetadata, AppendManyWithMetadata, AppendBatchWithMetadata return wrappers with Result() and outcome-independent Operation(); PreparedBatch.Operation() exposes staged metadata | TestOperationMetadataSurvivesRejectionAndUnknownOutcome, TestManyAndPreparedOperationMetadata, TestLegacyResultLayoutsRemainUnchanged; store/namespace/sequence and distinct exact TypeRef generations are owned independently of committed-only Target. Existing AppendResult/BatchResult layouts and methods stay unchanged for Arc.Go and unkeyed literals; adding even a private metadata field would break those callers. Existing transaction results consequently retain their old layout rather than acquiring implicit metadata |
Reverse event catalog / DotNET/Events/IEventTypes.cs:42-65 (baseline revision above) | Implemented: Catalog.LookupID(TypeID) and Catalog.LookupRef(TypeRef) return (Descriptor, bool) including schema and Go type | TestSubjectResolverDeclarationAndLookup, TestHistoricalCatalogExactAndLatestLookup; missing IDs/generations do not match, current-ID lookup returns the highest registered generation |
Canonical identities and names / DotNET/Identities/Identity.cs:28-33, DotNET/EventSequences/EventSequenceId.cs:26-66, DotNET/{EventStoreName,EventStoreNamespaceName}.cs (baseline revision above) | Implemented: identities.Unknown(), identities.System(), events.{UnspecifiedSequence,SystemSequence,Outbox,Inbox,InboxPrefix,UnspecifiedSourceType}, SequenceID.IsEventLog(), metadata.{NotSetStore,SystemStore,NotSetNamespace} | TestCanonicalIdentities, TestCanonicalSequences, TestCanonicalNames; identity functions return independent copies instead of mutable global values; existing EventLog and DefaultNamespace remain unchanged |
Append results / DotNET/EventSequences/{EventSequence,IAppendResult}; Kernel/Core/EventSequences/EventSequence (repository root), baseline revision above | Go-specific: C# exposes IsSuccess/Errors without a disposition. Single/batch errors-only failures are Unknown, with OutcomeUnknownError from the operation and Err(); the kernel cannot distinguish pre- from post-commit failures. Constraint/concurrency violations are Rejected, even with errors | TestAppendFailureDispositionAndErrorIdentity, TestAppendNeverRetriesAnAmbiguousWrite, TestCloseCancelsInFlightAppend; diagnostics and error identities preserved; callers must not treat errors-only results as safe to retry |
Local append observation / DotNET/EventSequences/{IEventSequence.AppendOperations,EventSequence,AppendedEventWithResult} (baseline revision above) | Implemented for nonempty append attempts: Sequence.OnAppend, owned AppendNotification and ordered AppendEvent identities for single/many/batch/prepared/metadata/named-tag paths | TestAppendNotificationsAcrossPathsAndDispositions, TestAppendNotificationsKeepRequestCorrelationAndGeneration, TestAppendNotificationSnapshotsDoNotAliasOtherSubscribersOrResults; one batch notification after the result, not a durable observer feed. Go carries source/type/generation/route/position rather than retaining C#‘s original object and full audit context |
Command attribution and subscription ownership / DotNET/EventSequences/EventSequence.cs:80–83,470–490,805–837,872–922, DotNET/EventStore.cs:61,167,414–417; Arc Source/DotNET/Chronicle/Commands/TransactionalCommandScope.cs:83–102,132 at 7c1e78075b737df64f69fddfaae83374f75e3612 | Go-specific: explicit subscription/disposal shared per client/store/namespace/sequence like C# (low-level eventsequences.New remains handle-local), request correlation independent of response correlation, retained Unknown on transport/protocol/errors-only failures; callback panics become AppendCallbackPanicError without changing disposition | TestStoreSequenceHandlesShareAppendNotifications, TestConcurrentStoreSequenceCallsShareHandles, TestStoreSequenceRejectsInvalidIDs, TestAppendNotificationsRemainHandleLocal, TestConcurrentAppendSubscriptionsAndCommandAttribution, TestAppendCallbackCanReenterSubscribeAndDisposeDuringDelivery, TestAppendDisposalDoesNotWaitForAdmittedCallbacks, TestAppendCallbackPanicsPreserveDispositionAndOtherDeliveries, ExampleSequence_OnAppend; synchronous callbacks outside locks, no global interception. Unsubscribe stops admissions, not already admitted calls; join command work and synchronize shared state |
Append origin attribution / DotNET/EventSequences/{EventSequence,AppendedEventWithResult}.cs, Arc Source/DotNET/Chronicle/Commands/TransactionalCommandScope.cs (revisions above; C# attributes by correlation only) | Go-specific: comparable process-local eventsequences.Origin, NewOrigin, WithOrigin, OriginFrom, AppendNotification.Origin, UnitOfWork.Origin() | TestConcurrentSameCorrelationUnitsAttributeNotificationsByOrigin, TestImmediateNotificationsInheritOriginAcrossPathsAndDispositions, TestPreparedNotificationUsesAppendOriginNotPreparationOrigin, TestOriginsAreUniqueUnderConcurrentAllocation, TestOriginContextInheritanceReplacementAndMasking, TestUnitOriginZeroAndRollbackLifetime, ExampleWithOrigin, ExampleSequence_OnAppend; Arc commands can share a reactor delivery or client-supplied correlation. Unit commits use a fresh Begin-assigned origin, immediate appends inherit append-time context, zero means unattributed. Tokens are never reused within a process, not durable or wire identities; no global attribution registry. Additive callback field; consumers receive SDK-constructed notifications rather than positional literals |
| Metadata-only append origin resolution / same C# append/Arc attribution references and revisions as above (no C# callback equivalent) | Go-specific: eventsequences.AppendOriginResolver, chronicle.WithAppendOriginResolver, optional low-level AppendOriginResolverProvider, payload-free AppendOriginResolutionError | TestAppendOriginResolverEveryVariantOnceWithActualContext, TestAppendOriginResolverHandledZeroAndUnhandledFallback, TestAppendOriginResolverFailuresRejectWithoutRPCNotificationOrPayload, TestAppendOriginResolverFailureBeforeAutomaticScopeRPC, TestPreparedAppendOriginResolverUsesDispatchContextOnly, TestUnitCommitBypassesFailingExternalAppendOriginResolver, TestUnitOriginCapabilityCannotAuthorizeOtherAppendsFromCallbackContext, TestConcurrentSameCorrelationCommandsResolveDistinctOriginsAndReenter, TestClientAppendOriginResolverFrozenAndSharedAcrossStoreCoordinates, TestClientNilAppendOriginResolverDisablesEarlierOption, ExampleWithAppendOriginResolver; handled selects even zero, unhandled falls back to OriginFrom, callback failures reject before RPC/notification without fallback. Actual dispatch context supplies metadata; synchronous borrowed callbacks run outside locks and must support concurrency. Only SDK-owned completion of the exact unit batch bypasses the resolver, not an inherited nonzero origin. No Arc dependency/context decorator, public result/configuration layout change or wire change. #47/#67 are SDK attribution contracts, not an Arc adoption claim; other clients/discarded operation contexts and durable broker identities remain outside scope |
Transaction append observation / DotNET/Transactions/UnitOfWork.cs:406–470, DotNET/EventSequences/EventSequence.cs:805–837 (baseline revision above) | Implemented: staging does not notify; nonempty Owner.Commit notifies through AppendPreparedBatch before unit completion | TestUnitCommitNotifiesSnapshotBeforeCompletionEvenIfCallbackPanics, TestAppendNotificationsExcludePreparationRollbackAndEventlessCompletion, TestAppendNotificationsExcludeBeforeDispatchFailures, disposition matrix above. C#‘s interface remark excludes transactional staging, but actual commit calls the ordinary batch API. Empty/rollback/repeated completion cannot imply accepted events; pre-dispatch preparation failures remain returned errors, not notifications |
Atomic batches / DotNET/EventSequences/EventSequence (AppendManyCore, AppendManyForEventSources, ResolveConcurrencyScopes) | Implemented: AppendMany, AppendBatch, ordered Entry, LabeledScope, full BatchResult | TestAppendManyRoutesAndStaticTagUnion, TestBatchPreservesOrderMetadataAndIndependentScopes, TestBatchScopeExpectationsAndResolution, TestBatchSnapshotsBeforeResolvingTail, TestBatchFailuresAndCompleteDiagnostics, TestKernelOrderedAtomicBatchAndHistory, TestKernelSameSourceBatchRoutesAndTagUnion; A1/B1/A2 is one atomic request, routed same-source batches retain the static-tag union |
Eventless checks / DotNET/EventSequences/EventsWithConcurrencyScopes; Kernel/Core/Sequences/AppendMany*Validator (repository root) | Implemented: protected empty AppendBatch and AppendMany | TestEventlessProtectedBatchAndOlderKernelRefusal, TestResolvedEmptyEventlessScopeIsNotASuccessfulNoOp, TestKernelEventlessChecksAndUpperBoundExpectations; unsupported older kernels fail loudly |
Event reads / DotNET/EventSequences/EventSequence (GetForEventSourceIdAndEventTypes, GetFromSequenceNumber, tail/next/has-events), DotNET/Events/EventContext | Implemented: ReadSource, ReadFrom, Tail, Next, HasEvents; raw events.Appended and complete context | TestReadPreservesEnvelopeMetadataAndGenerations, TestTailPresenceNextAndHasEvents, TestMalformedReadsNeverProduceHistory, TestReadCancellationAndValidation, TestKernelOrderedAtomicBatchAndHistory; no pagination/live-feed claim or automatic generation decoding |
Loaded-history concurrency primitive / DotNET/EventSequences/EventSequence, DotNET/Events/EventSourceId, baseline revision above | Go-specific: ReadHistory, History.Scope; nonblank source IDs retained verbatim as in C#, other read dimensions still normalized | TestHistoryScopePreservesPaddedSourceID, TestHistoryUsesOnlyLoadedEventsAndNormalizedFilter, TestKernelOrderedAtomicBatchAndHistory; expectation comes only from loaded matching events, never a later tail; blank required sources remain invalid |
Unit of work / DotNET/Transactions/{IUnitOfWork,UnitOfWork,UnitOfWorkLifecyclePolicy} | Implemented for strict ordered staging, terminal completion, diagnostics, correlation and completion callback: transactions.UnitOfWork, Owner | TestStageSnapshotsEverythingAndPreservesNestedOrder, TestCommitDispositionsAreTerminalAndObservable, TestEmptyCompletionRollbackAndCancellation, TestConcurrentStageEnrollsEachCallContiguously, TestCommitFreezesParticipantsAndCancellationIsUnknown, TestKernelUnitOfWorkSnapshotsAndNestedOrder, TestKernelUnitOfWorkLoadedHistoryRejectsAllSources |
Unit manager / DotNET/Transactions/{IUnitOfWorkManager,UnitOfWorkManager} | Go-specific: explicit transactions.Begin, WithUnitOfWork, FromContext; no ambient manager or global correlation lookup | TestStageSnapshotsEverythingAndPreservesNestedOrder, ExampleBegin; nested participants share the same unit, never the owner |
Immutable transaction enrollment / DotNET/Transactions/UnitOfWork.AddEvents | Go-specific: serialization at Stage; Sequence.PrepareBatch, immutable PreparedBatch.Merge, AppendPreparedBatch reuse the existing batch transport | TestStageIsAtomicAndRejectsMetadataAndScopeChanges, TestScopeEventTypesJoinAsSets, TestTailFailureRejectsBeforeAppendAndDoesNotResolveDuringStage; no staging I/O, actor/correlation drift fails, conflicts do not partially enroll |
Protected decisions / DotNET/ReadModels/DecisionReads, DotNET/Transactions/{DecisionReadCommitOwner,UnitOfWork.AddDecisionRead} | Implemented for admitted optimistic guards: readmodels.DecisionsFor, DecisionReader.Admit/Get/GetDetached, opaque transactions.DecisionToken, UnitOfWork.Enroll, existing Owner.Commit | TestKernelDecisionCompetingWriteRejectsEntireMultisourceBatch, TestKernelDecisionFirstEventAbsenceAndEventlessCompletion, TestKernelDecisionSimultaneousOwnersHaveOneWinner; no authenticated server-proof claim. Safety departures and protocol gaps are detailed below |
Constraint declarations / DotNET/Events/Constraints/{ConstraintBuilder,UniqueConstraintBuilder,ConstraintConverters} | Implemented: immutable constraints.UniqueValues / UniqueEventTypes builders, ordered composite paths across events, IgnoreCasing, additive RemovedWith, scope dimensions and sequence selection | TestConstraintRegistrationMatchesCSharpGoldenRequest compares a full protobuf request hand-derived from the C# converters; TestConstraintDefaultsAndSnapshotOwnership, TestMalformedConstraintDefinitionsFailBeforeRegistration, TestConstraintRegistryRejectsInvalidOrForeignDeclarations; explicit JSON paths replace CLR expressions; model-bound tags are also supported (see below) |
Constraint registration / DotNET/Events/Constraints/Constraints, DotNET/EventStore | Implemented: Registry.AddConstraint, store-wide registration after event types, stage outcomes, retry, frozen per-store overrides and reconnect replay | TestConstraintRegistrationOrderReplayAndAppendBarrier, TestConstraintRegistrationFailureIsReportedAndRetryable, TestConstraintSnapshotsAndStoreOverrides; no observer attachment claim; runtime constraint additions remain unsupported. See runtime projection additions for the bounded model/projection workflow |
Kernel uniqueness/removal / DotNET/Events/Constraints, Kernel/Core/Events/Constraints/{UniqueConstraintValidator,UniqueEventTypeConstraintValidator} (kernel paths relative to repository root) | Implemented: property ownership, removal, composite/cross-event uniqueness, event-type mutual exclusion and lifecycle cycles, batch rejection, route/sequence/namespace boundaries | TestKernelUniqueValuesAndOwnerRemoval, TestKernelCompositeUniquenessAcrossEventTypes, TestKernelUniqueEventTypeLifecycleAndBatchCycles, TestKernelConstraintBatchRejectionsAreAtomic, TestKernelConstraintScopesSequencesAndNamespaces; rejected events are read back as absent, all enforcement remains in the kernel |
Violation messages / DotNET/Events/Constraints/{Constraints,ConstraintViolation,WellKnownConstraintDetailKeys} | Implemented: WithMessage, WithMessageProvider, complete constraints.Violation details and PropertyName / PropertyValue keys in single/batch results | TestConstraintDetailsAndMessagesInEveryAppendResult, TestConstraintMessageTemplatesPreserveDetailsAndFallback, TestMessageDetailValuesAreLiteral, kernel composite-message test; Go eagerly resolves once per returned violation and keeps values literal |
Observer-tail reads / DotNET/EventSequences/EventSequence.cs:GetTailSequenceNumberForObserver | Implemented: Sequence.TailForObserver(ctx, eventTypes) reads the source tail for explicit declared types, not a checkpoint | TestCompletionUsesActualBatchTailsAndOriginalCoordinates, TestKernelOperationsCompletionReplayAndJobEvidence; explicit plan/snapshot types replace CLR handler discovery. Empty types fail rather than selecting every event |
Read-model catalog / DotNET/ReadModels/{ReadModels,ReadModelTypeExtensions}, Sinks/WellKnownSinkTypes | Implemented for explicit declarations, indexes, sink selection and registration replay: RegisterReadModel[T], readmodels.Model, Descriptor, Catalog, EventStore.ReadModels | TestReadModelRegistrationMatchesCSharpGolden, TestReadModelDefaultsAndCatalogIsolation, TestContainerNamesMatchCSharpDefaultNamingPolicy, TestReadModelRegistrySnapshotsAndConcurrentDuplicates, TestReadModelReplayBarrierAndNamespaceIsolation, TestReadModelRegistrationFailureDoesNotPublishReadiness, TestKernelReadModelRegistrationAndAbsence, TestPassiveReadModelRequiresObserver, TestReadModelPropertyOptionsAccumulate, TestGenericReadModelRequiresExplicitNames; no automatic producer discovery |
One-shot model reads / DotNET/ReadModels/{IReadModels,ReadModels,ReadModelJsonSerialization} | Implemented for kernel-backed reads: typed Reader[T].Get and raw Service.Get, explicit Exists and LastHandled | TestReadModelPresenceAndCollectionNormalization, TestReadModelErrorsAndUnsupportedKernel, TestReadModelValidationNeverDispatches, TestKernelReadModelRegistrationAndAbsence; real-kernel evidence is registration plus absence. Presence is covered through bufconn; materializing state requires a projection/reducer |
Supported typed model decoding / DotNET/ReadModels/ReadModels.cs:257–258, DotNET/ChronicleClient.cs:360–381 (baseline revision above) | Implemented: typed Get, sessions, injected value/pointer models and typed Release use the registered frozen descriptor plan, as do watches/windows | TestTypedReadsUseFrozenNamingPlan, TestTypedNamingPlanPreservesMissingKeyAndDistinctID, TestTypedInjectionAbsenceNeverManufacturesState, TestReadModelPlanDoesNotReuseAnotherFieldsExactName, TestReleaseNeverUsesIndependentDeclaredIDAsSubject, TestReleaseAliasFilteringPreservesSubjectAndLineagePrecedence, TestReleaseExplicitSubjectCanSelectIndependentID; root Go ID → Id remains distinct from ExternalID tagged json:"ID", including missing/null fields and sink aliases. All three naming policies, concept codecs, collection nullability and fail-closed Release errors are covered. A missing/null/empty root ID never infers a protected-value subject from another declared ID field; explicit subjects and stored lineage retain precedence. Decode support does not imply the pinned kernel can register a projection schema containing both Id and ID; it rejects that shape |
Hydration sessions / DotNET/ReadModels/{ReadModels,ReadModelSessionId} | Partial: Reader[T].NewSession, Session[T].Get, Close(ctx) preserve coordinates and explicit cleanup | TestReadModelSessionRetainsCoordinatesAndRetriesFailedCleanup, TestSessionCancellationStillRequiresDehydration, TestUnsupportedSessionsFailBeforeDispatch; projection-backed transport only. Reducer sessions fail explicitly. Classified projection sessions and passive/immediate Get return values the kernel released with each value’s original subject on Chronicle 19.32.2 or later (Chronicle#4561); older or unverified kernels refuse before RPC (TestProtectedProjectionRoutesUseKernelReleaseWithoutSecondDecryption, TestProtectedProjectionRoutesRefuseWithoutProtectedReleaseKernel, TestKernelModelHistoryProtectedReleaseProfile) |
Protected-value release / DotNET/ReadModels/{ReadModelReleaser,ReadModelSubjectResolver}, Source/Kernel/Core/ReadModels/ReadModelsCompliance.cs (repository root, baseline revision above) | Partial, Go-specific fail-closed behavior: Reader[T].Release, raw Service.Release, WithPII, WithSubjectProperty | TestReadModelReleaseFailsClosed, TestReadModelReleaseSubjectAndNoMetadata, TestReadModelReleaseMultipleSubjectGroups, TestReadModelReleaseNumericSubjects, TestReadModelReleaseRejectsMalformedLineage, TestReadModelMetadataUsesSerializationPlan; see the per-behavior compliance rows below for type/container classification, confidentiality, lifecycle APIs and kernel limitations |
Projection basic declarations / DotNET/Projections/{ProjectionBuilderFor,Projections}, ModelBound/ModelBoundProjectionBuilder | Partial: model-bound and fluent front ends, one immutable compiler/encoder, frozen catalog resolution, store registration and reconnect | TestBothFrontEndsMatchCSharpBasicGolden, TestProjectionDiscoveryRegistrationAndFrozenReplay, TestProjectionStartupValidationIsAtomicAndStoreLocal, TestKernelProjectionMaterializesModelBoundAndPassiveReads; attribute-by-attribute boundaries below |
Reducers / DotNET/Reducers/* | Partial: active and passive folds with explicit callbacks and convention discovery | Per-behavior evidence and limits in Reducers below |
Event seeding / DotNET/Seeding/* | Partial: explicit preparation, scoped builders, final registration and kernel idempotence | Per-behavior evidence and kernel limits in Event seeding below |
| Watches and read-model reactors | Partial: unclassified projection changes and local fold notifications; raw/typed materialized windows validate server-released values for the admitted MongoDB root-string protection profile. Classified Watch is unsupported | Per-behavior evidence in Read-model watches and reactions; no blanket protected-route safety or durable cursor/resume promise |
| Observer administration, jobs and completion waits | Implemented for the per-feature surface below | See Observer and job administration; durable event subscriptions remain Not implemented |
The Kotlin and TypeScript client comparisons informed vocabulary and transport translation, not behavioral authority. Unlike TypeScript’s plaintext/certificate URI conveniences, unsupported options are rejected. The canonical descriptor and public package ownership follow the cross-language contract publication convention without a separate Go module.
Read-model collections and history
Section titled “Read-model collections and history”C# baseline: ReadModels.cs:262–328,397–449 and Reducers.cs:259–350 under
Source/Clients/DotNET at 2e31b0dfba489159b3db323238f16d0f277056b4.
Release ownership and capability boundaries use v19.29.4:
Source/Kernel/Grpc/ReadModels/ReadModels.cs,
Source/Kernel/Core/ReadModels/MaterializedReadModelStore.cs,
Source/Kernel/Core/ReadModelExplorer/ReadModelSnapshotReader.cs, and
Source/Kernel/Core/Projections/Projection.cs.
- Partial: raw/typed collections, default versus explicit Unlimited
routing, globally bounded generation-aware reducer folds, and admitted source-ID
root projection snapshots, including classified models on kernel 19.32.2 or later.
TestKernelModelHistoryProjectionCollectionsAndCorrelationGroupsproves materialized absence/deletion, bounded/passive replay, namespace isolation, A/B/A global grouping, first-event occurrence and ambiguous{}deletion history.TestKernelModelHistoryReducerCollectionRoutingAndDeletionseparates materialized default reads from active/passive local folds and checks deletion/present zero. - Go-specific safety differences: zero avoids I/O/activation. Finite
values above MaxInt32 fail rather than clamp; reducer snapshots fail before RPC instead of
adopting the kernel’s unconditional empty result. Nonempty defaults and unproven
custom/relationship-key replay are refused (
TestProjectionHistoryRefusesDefaultsAndCustomKeysWithoutRPC). Mixed ALL plus explicit event mappings is admitted: kernel 19.32.1 and later fold every event handled live (Chronicle#4562).TestMixedAllHistoryRequiresMixedAllReplayKernel(refused on older or unverified kernels) andTestKernelModelHistoryMixedAndPureAllReplaycover it; the kernel witness reads a bounded replay that must include an event only the ALL subscription handles, the complete replay, legacy replay and A/B/A history, beside pure ALL with an empty type filter. No local evaluator/default overlay or decision guard is inferred. - Route-specific release ownership: materialized keyed/collection reads use
persisted model lineage; the SDK validates their final representation without a
second decrypt. Local reducers fold released history. Projection collection
replay, legacy replay, immediate Get, sessions and model history are admitted for
PII and every encryption scope: kernel 19.32.2 and later release them with each
value’s original subject (Chronicle#4561),
and the SDK validates without a second decrypt
(
TestProtectedProjectionRoutesUseKernelReleaseWithoutSecondDecryption).TestKernelModelHistoryProtectedReleaseProfileexercises every route through the SDK for defaultIdand lowercaseid, with a key that differs from the subject, before and after erasure, using legitimate cipher-shaped plaintext. Earlier kernels released replay with the source key, so these routes refuse classified models before RPC unless the verified kernel is 19.32.2 or later (TestProtectedProjectionRoutesRefuseWithoutProtectedReleaseKernel). Contributions on unclassified model history are released separately by the kernel’s event subject/schema before projection, including before/after erasure.TestKernelModelHistoryReleaseOwnershipAndErasurecovers admitted collection routes and keyed passive folds. These witnesses are not general server security assurance and do not complete #33’s protected-history capability. ExplicitService.Releaseremains an unreleased-document API with lineage groups. LegacyPassiveReaderretains that contract;WithReleasedPassiveReaderexplicitly selects plaintext instead. - Decode and lifetime evidence:
TestCollectionAndSnapshotsUseFrozenNamingAndPointerConcepts,TestCollectionRejectsLateErrorsWithoutPartialProgress,TestSnapshotsRejectLateFailuresAndEnvelopeFailures,TestReadDocumentsRejectDuplicateMembersBeforePublication,TestHistoryCodecFailuresArePayloadFreeAndAtomic, andTestModelHistoryCodecsCanCloseClientAndLateCancellationDiscardsEverything. Go rejects duplicate JSON names recursively before schema checks or publication; ordinary unknown fields remain. Decoder panics discard results and never retain panic payloads; ordinary codec causes remain inspectable behind fixed messages. The raw response owns its metadata; no IDs, revision or generation copies absent from the snapshot wire are fabricated. Unknown Go event types stay raw. - Limits: complete matching history is fetched before local truncation, not
paged/bounded on the wire. Counts are not positions or freshness proofs; progress
is reported independently.
Snapshot.Instancehas no invented existence or last-handled marker. Catalog-only producers cannot establish replay fidelity.TestProjectionReplayPolicyOwnsBoundReplacementAndInboxDefinitionsverifies immutable admission from the actual selected store’s bound definitions/models, including replacement registries and inbox sequence binding.
See the collection/history reference.
Audited event history operations
Section titled “Audited event history operations”References: C# 2e31b0dfba489159b3db323238f16d0f277056b4, paths relative to
Source/Clients/DotNET; kernel references use repository-root paths at contract
pin ae5e00a8abaa688138b2c2f689e2b4659cccb4fd.
Kernel integration uses 19.32.3-development. See the
history operations reference for destructive scope,
audit requirements, acceptance versus application, and recovery boundaries.
| Behavior / C# source | Go surface and status | Executable evidence / intentional difference |
|---|---|---|
Point redaction / EventSequences/EventSequence.cs:272–288 | Implemented: Sequence.Redact(ctx, position, events.RedactionReason) | TestHistoryMutationRequestsMatchCSharp, TestKernelRedactOnePreservesMarkerAndAudit; exact position, unchanged routing, original audit in marker and operator in outer context; zero valid, all three highest reserved positions rejected before dispatch by TestPointHistoryMutationsRejectReservedPositionsBeforeDispatch; C# EventSequenceNumber.cs:IsActualValue defines Unavailable, Max, BeforeFirst |
Source/type redaction / EventSequence.cs:291–305 | Implemented: RedactForEventSource(ctx, source, reason, ...events.TypeID) | TestSourceRedactionWithoutTypesIsExplicitlyUnfiltered, TestKernelRedactSourceHonorsTypeFilterAndIsolation; registered IDs replace CLR type arguments, all generations selected; no IDs means all types, across all routes |
Actor/causation / EventSequence.cs:249–305 | Implemented explicit context metadata; Go-specific preflight target/reason checks | Hand-derived full protobuf comparisons in TestHistoryMutationRequestsMatchCSharp, TestHistoryMutationsRejectInvalidInputBeforeDispatch; NotSet/empty defaults; no revision reason or caller correlation field on the wire |
Revision content/generation / EventSequence.cs:249–269, Events/EventSerializer.cs, EventSequenceNumber.cs | Implemented: Sequence.Revise uses the registered shared codec/selected naming policy; Go-specific position/registration preflight | TestRevisionUsesSelectedGenerationSerializationPlan, TestKernelRevisionPreservesOriginalAndRevisionAudit, TestKernelRevisionUsesRegisteredGeneration; pointers, invalid content, generation-specific content, retained original/revision audit; not an append or migration. TestPointHistoryMutationsRejectReservedPositionsBeforeDispatch and TestPointHistoryMutationsAllowActualBoundaryPositions pin actual positions, including zero |
Asynchronous application outcomes / kernel Core/Sequences/{Revise,Redact,RedactForEventSource}.cs | Partial: nil error confirms request acceptance only, like C# | Kernel read-after-mutation tests wait for actual changes; target/type/generation failures can happen later. No public receipt/status contract: Chronicle#4526; no invented completion result |
Ambiguous outcomes and retries / EventSequence.cs:249–329, command envelopes | Go-specific MutationOutcomeUnknownError; explicit authorization/validation refusals remain envelope errors | TestMutationFailuresAreNeverRetriedOrNotified, TestHistoryMutationBeforeDispatchKeepsLocalError; transport/cancellation/exception/malformed responses never imply non-application; pre-dispatch errors retain identity |
Redacted payload / Events/{EventRedacted,EventRedactedConverters,RedactionEventContent}.cs | Implemented, Go-specific ID-first events.EventRedacted, Appended.Redaction, automatic generic/dynamic marker decoding | TestRedactionDecodesOriginalIdentityAndAudit, TestRedactionRejectsNonMarkersAndMalformedContent; OriginalType(catalog) maps known IDs to the current Go type and unknown IDs to any (C# object); UUID fields permit ordinary observer registration; no original type decoded as zero-filled content |
Projection/reactor effects / kernel Core/EventSequences/EventSequence.cs:RewindPartitionForAffectedObservers | Implemented kernel-owned replay, no client-side mutation emulation | TestKernelHistoryMutationsReplayProjectionsAndReactors observes corrected projection state, revised replay delivery, marker replay delivery, and projection recovery of the pre-redacted state; does not reverse external effects |
Local append observation / IEventSequence.AppendOperations, EventSequence.cs:249–329 | Implemented exclusion of all history mutations | TestHistoryMutationRequestsMatchCSharp, TestMutationFailuresAreNeverRetriedOrNotified, kernel point-redaction/revision tests; no OnAppend on acceptance, refusal or unknown outcome; no UoW staging |
Stream completion / EventSequence.cs:309–329, CompleteStreamError.cs | Implemented: CompleteStream(ctx, type, id) (SequenceNumber, error) | TestStreamCompletionTypedRefusalsAndMalformedResults, TestKernelCompleteStreamRefusalsAndFutureAppends; sequence-wide tail, empty sentinel, StreamAlreadyCompleted, DefaultStreamCannotBeCompleted, later append rejection across sources; future/inconsistent responses and reserved returned tails (Max, BeforeFirst) fail unknown wrapping ErrProtocol, since closure may have occurred. Unavailable remains absence; TestStreamCompletionAllowsActualBoundaryTails pins zero/highest actual tails. Unlike C#, Go does not fall back to AlreadyCompleted |
Stream/source status / kernel Core/EventSequences/IEventSequence.IsStreamCompleted | Not implemented publicly: no RPC in 19.29.4 and no C# client member | Generated EventSequencesClient has completion but no status/reopen/source-completion method; no local status cache or destructive status probe is substituted |
Protected revision / C# EventSequence.cs:249–269; kernel Core/Sequences/Revise.cs:Handle at v19.32.3 | Implemented like C# on Chronicle 19.32.2 or later: Sequence.Revise sends plaintext and the kernel applies the replacement generation’s metadata with the original event’s subject (or source) to the revision and its EventRevised system request. Go-specific refusals, before serialization or RPC with payload-free ErrUnsupported: a protected replacement on an older or unverified kernel (which persists it unprotected, Chronicle#4525); and, on every kernel, an unclassified replacement whose type ID another registered generation classifies, because the kernel protects only with the replacement generation’s schema and erasure could not remove that plaintext. C# dispatches both | TestProtectedRevisionDispatchesPlaintextForKernelProtection, TestProtectedRevisionRefusesWithoutProtectedReleaseKernel, TestRevisionRefusesUnclassifiedGenerationOfProtectedIdentity, TestProtectedRevisionStillRejectsInvalidContentBeforeDispatch, TestKernelProtectedRevisionIsProtectedWithOriginalSubject (subject differs from source; PII, namespace and global encryption; raw MongoDB scan finds no plaintext at rest; system request free of plaintext before and after erasure; erasure shreds PII and leaves encrypted values readable). An upgrade does not repair plaintext stored by earlier kernels |
| Idempotence/recovery fencing / kernel revision/redaction storage | Partial kernel semantics only; no safe retry protocol or frozen bulk boundary | Repeated stream refusal is kernel-tested; revision can add records, source redaction can reach later events. Durable operation identity/fencing remains Chronicle#3920; append-tail checks are not revision barriers |
Composed operations / EventSequences/Operations/{IEventSequenceOperations,EventSequenceOperations,EventSourceOperations}.cs, Go #19 | Go-specific substitution: []Entry, AppendBatch, WithScopes, optional PrepareBatch; no second mutable builder | TestBatchPreservesOrderMetadataAndIndependentScopes, TestBatchSnapshotsBeforeResolvingTail; caller owns collection/reset and default causation; entries carry overrides. Go preserves A1/B1/A2 order instead of C# grouping by source. Composition is append-only in C# too; revision/redaction/completion are not atomically mixed with appends |
| Offline workflow / explicit construction rather than container discovery | Go-specific zero-container example | ExampleSequence_Redact executes only an offline protocol fixture, not fake persistence. Ordinary SDK construction and registration remain unchanged |
Compensation/tombstone declarations, generation migrations, subject-key erasure, and stream completion are separate behaviors. None automatically performs the others. Redaction removes targeted original/revision payloads, not backups, external effects, forwarded copies, or prior system revision request content.
Observer and job administration
Section titled “Observer and job administration”The operations reference describes the public workflows. Sources
below are relative to Source/Clients/DotNET at 2e31b0dfb; generated contracts
remain pinned independently to 19.32.3. Request/result golden fixtures are
hand-derived from C#, except the observer wire fixtures,
which were emitted by protobuf-net 3.4.30 using the actual contract at kernel
revision ae5e00a8. Kernel paths below are relative to Source/Kernel at that
revision. Kernel witnesses use the repository’s 19.32.3-development image.
| Behavior / C# source | Go surface and status | Executable evidence / differences |
|---|---|---|
Observer listing/state / Observation/{IObservers,Observers,ObserverInformation} | Implemented: EventStore.Observers().List/Get, immutable state snapshots | TestOperationsMatchHandDerivedCSharpGolden, TestAdministrationRejectsMalformedResponses; explicit sequence for Get, NotFound presence, state/tails/subscription/owner/type/count and detached event references |
Replay policy and subscription availability / kernel Contracts/Observation/ObserverInformation.cs:83–85, Grpc/Observation/{ObserverInformationConverters,Observers} | Implemented, Go-specific availability: IsReplayable, SubscriptionKnown, IsSubscribed | TestObserverSnapshotsDecodeCSharpWirePresence, TestKernelOperationsReplayabilitySubscriptionAndCustomSequenceRemoval: a List/Get-only receive codec restores omitted field 10 to true while preserving explicit false. No generated/schema edits or global codec. List subscription is unavailable; Get queries it authoritatively. The kernel witness also exposed the same default on outgoing Contracts/Observation/Reactions/ReactorDefinition field 4: TestReactorOnceOnlyEmitsExplicitFalseForCSharpDefault ensures reactor-wide OnceOnly registration emits zero rather than silently opting into replay |
Persistent removal / Observation/{Observers,ObserverRemovalResult}; kernel Core/Observation/ObserverRemover.cs:57,60,103 | Implemented, Go-specific safety correction: Service.Remove, RemoveFrom, Information.Remove, four outcomes and blocking namespace | TestObserverRemovalUsesAuthoritativeCustomSequence, TestObserverRemovalFailsBeforeMutationWithoutSafeTarget, TestObserverSnapshotRemovalRejectsChangedSequence, custom-sequence kernel witness. C#‘s event-log default can target the wrong subscription guard before shared-definition deletion. Go resolves the current definition and validates explicit/snapshot sequences; unavailable or ambiguous definitions fail before mutation. No atomic re-registration fence. Existing ID-only callers retain their signature but now incur a lookup and cannot remove state-only orphans |
Failures / Observation/{FailedPartitions,FailedPartition,FailedPartitionAttempt} | Implemented: FailedPartitions(ctx, id) with optional observer filter and immutable attempts | Golden/malformed tests, TestKernelOperationsFailedPartitionDiagnostics; UUID, partition, timestamp offset, kind, messages/stack, resolved and quarantined flags preserved. No automatic recovery |
Recovery/quarantine / Reactors/Reactors.cs:RetryFailedPartitionFor, pinned Contracts/Observation/* | Implemented: retry outcomes, explicit partition replay, failure/observer/partition quarantine clearing | TestAdministrationPreservesRefusalsAbsenceAndTargetIsolation, golden and lost-reply tests; clear/retry distinctions retained. RetryRequested prevents treating proto3’s default Started as an actual retry when none was requested. Empty acknowledgements prove no completion |
Replay / Reactors/Reactors, Reducers/Reducers, Projections/Projections | Implemented: Observers().Replay returns namespace-bound jobs.Handle | Golden, target isolation, malformed/lost reply and kernel replay/job witness. Explicit source sequence replaces C# reactor/reducer empty-sequence inference; malformed/zero job IDs fail rather than silently returning NotSet |
Jobs reads and commands / Jobs/{IJobs,Jobs,Job,JobStep} | Implemented: EventStore.Jobs().List/Get/Steps/Stop/Resume/Delete; immutable job/step snapshots | Golden tests cover full requests, .NET UUID byte order and complete progress/status history. Failure tests preserve envelope authorization/validation/exception and RPC errors; Get searches List as C#. Handles and snapshots retain original store/namespace |
Job waits / Jobs/JobsWaitHelpers | Implemented, Go-specific safety: predicate/progress/list/deletion/terminal waits, strict successful-status wait, 50ms polling and 5s default | TestJobWaitRequiresEvidenceAndPreservesDisappearance, TestJobWaitHelpersHonorBudgetsAndCallerCancellation; absence/disappearance stay unknown, failed/stopped states stay distinct. Strict wait adds AbsentError/TerminalError; terminal-or-absent preserves C# null semantics. WaitForNoJobs corrects C#‘s inverted selected-status predicate; caller cancellation always propagates |
Append completion / Observation/AppendResultWaitForCompletionExtensions | Implemented: metadata result/notification Completion, WaitForCompletion, immutable observation.Completion and diagnostics | TestCompletionUsesActualBatchTailsAndOriginalCoordinates, TestCompletionBudgetsAbsenceCancellationAndDiagnostics; actual per-type batch tails, original first/store/namespace/sequence and maximum-position generation, 5s + 200ms/infinite behavior, missing surface vs unavailable tail. Legacy result layouts remain unchanged; metadata-bearing append methods or existing notifications supply exact input types rather than guessing from current catalogs |
| Completion and lifecycle kernel witnesses | Partial beyond the demonstrated single-kernel workflows | TestKernelOperationsCompletionReplayAndJobEvidence holds a real handler to distinguish timed-out from successful catch-up, reads replay job/steps, observes replay-end and handles later job absence without fabricated completion. TestKernelOperationsFailedPartitionDiagnostics reads failed attempts and completion failure. No durable checkpoint or multi-node guarantee. The 19.29.4 reducer recovery (Chronicle #4540, witnessed by TestKernelReducerFailedPrefixRecovery) and catch-up race (Chronicle #4548) defects are fixed in 19.32.3 |
| Examples and documentation | Implemented for the described APIs | ExampleService_Replay, ExampleService_WaitForCompletion, TestOperationsDocumentationMatchesCompiledExample; offline examples explicitly label their transport. The kernel witnesses separately establish runtime behavior |
All mutation failures after dispatch are conservative OutcomeUnknownError
unless a local pre-dispatch or authorization/validation refusal is known. The SDK
adds no mutation retries. TestAdministrationLostRepliesNeverRetry and
TestUnknownOutcomeFormattingDoesNotExposeCause preserve error identities while
omitting sensitive cause text from OutcomeUnknownError.Error().
Typed DTOs reject malformed required messages, IDs, mutation-outcome enums and
contradictory completion replies. Diagnostic enums preserve future numeric values:
TestAdministrationPreservesForwardDiagnosticValues covers observer states,
failures, jobs, steps and histories, following C# JobsConverters,
JobStepConverters and FailedPartitionConverters numeric casts (observer kinds
retain numbers rather than C#‘s Unknown mapping). TestUnknownJobStatusIsNotCompletionOrTerminalEvidence
proves unknown status waits do not manufacture success or terminal evidence.
Proto3 scalar defaults and empty acknowledgements retain their protocol limits;
accepted work is not completed work. The kernel’s Grpc/Observation/Observers
completion method also succeeds with no matching observers: this is not expected
producer readiness or durable sink/checkpoint evidence.
JSON wire ordering and escaping
Section titled “JSON wire ordering and escaping”At the Chronicle baseline above, ChronicleOptions.cs:81 starts with default JsonSerializerOptions. ChronicleClient.cs:360–382 sets naming, null omission and compact output without changing Encoder; Events/EventSerializer.cs clones those options and calls SerializeToNode. Fundamentals Source/DotNET/Fundamentals/Json/Globals.cs at d2accc4a79b6bcf2708213c97093ab5ba6c06381 also leaves Encoder unset. DotNET/Seeding/EventSeeding.cs:128,174 serializes the resulting node using default options.
Go emits struct properties in declaration order, including nested structs, pointers and embedded/promoted fields in encoding/json index-path order. The compiled naming policy, explicit tags, nil omission, omitempty/omitzero, concept codecs and numeric validation still apply. Direct duplicate property names remain configuration errors; ambiguous promoted fields are omitted like encoding/json, but declarations on ignored fields fail closed. JSON Schema keeps its existing deterministic key ordering; schema property order does not identify seeded facts.
String values, property names, dictionary keys and string-valued concepts use JavaScriptEncoder.Default escaping: non-ASCII characters become uppercase \uXXXX escapes (UTF-16 surrogate pairs when needed), as do HTML-sensitive characters, apostrophe, plus, backtick and double quote. Backslash and the standard short control escapes are preserved. serialization/testdata/csharp-escaped-event.json and the all-ASCII test were captured from .NET 10 with the options/stages above; the ordered-object and multi-property seed fixtures are hand-derived. This does not implement arbitrary C# custom encoders or [JsonPropertyOrder]; equivalent declarations must have the same emitted property order.
Dictionary limitation: C# serializes Dictionary<string, T> in enumeration order (insertion order for ordinary additions); Go maps retain no insertion history, so their keys remain sorted by Go string order. TestDictionaryValuesRetainStructDeclarationOrder and TestEscapingAppliesToNamesConceptCodecsAndMapKeys pin deterministic keys without reordering struct values. For byte-sensitive shared seed content, use structs with matching declarations, or arrange the C# dictionary’s enumeration to match Go’s UTF-8 key order. Arbitrary dictionaries are not byte-parity compatible.
TestKernelCSharpThenGoSeedsAreIdempotent sends a C#-derived request followed by the Go request, asserts exact content bytes, and verifies one persisted event. The kernel compares seed content by ordinal string equality (Source/Kernel/Core/Seeding/EventSeeding.cs:332), not semantic JSON equality. Previously persisted Go seeds may contain alphabetically ordered properties or different escaping; upgrading does not rewrite that tracking data. Review existing Go seeds before re-registering changed bytes, which the kernel treats as new facts.
Compliance and confidentiality
Section titled “Compliance and confidentiality”C102–C106 references below use Source/Clients/DotNET at
2e31b0dfba489159b3db323238f16d0f277056b4; kernel paths are repository-root
paths at the same revision. Compliance reference documents the
public workflow. Runtime fixtures use 19.32.3-development with a disposable
server encryption certificate; ordinary Go tests remain container-free.
| Behavior / C# source | Status and Go surface | Executable evidence / boundary |
|---|---|---|
PII members and rationale / Compliance/GDPR/PIIMetadataProvider, Compliance/ComplianceDetailsExtensions | Implemented: pii, compliance-details(value=...); compliance.For[T] and WithProtection | TestClassificationSchemaMatchesCSharpCategoriesAndPropagation, TestClassificationPrecedenceAndExplicitEmptyDetails; member → declaring type → value type, explicit empty rationale preserved. Go has no constructor attributes; field tags are their equivalent |
Composite/type/nullable/concept collections / Schemas/JsonSchemaGenerator:226–254,491–600 | Implemented for supported concrete shapes: classification follows the serialization plan; composite leaves versus coarse collection metadata remain distinct | Classification test, TestRecursiveClassificationsPreserveShapeAndGuardCycles, TestKernelComplianceNestedConceptsAndCollections; no polymorphic/interface codec claim |
Map-value protection / kernel Core/Schemas/JsonSchemaMetadataManager.cs at v19.32.3 | Implemented: metadata beneath unprotected maps (tags, type, property-path and provider declarations) registers and the kernel applies it to every map value; coarse protection of the entire map property remains supported | TestProtectionBeneathMapsAndCollectionElementsIsAdmitted, TestNestedCollectionProtectionDetectsOnlyKernelSensitivePlacements, TestNestedProtectionRegistrationRequiresProtectedReleaseKernel (registration refused before RPC on older or unverified kernels), TestCoarsePropertyProtectionCoversMapAndArrayDescendants, TestKernelComplianceProtectionBeneathMapsAndCollectionElements (map of objects with a PII member, map of PII concepts and a namespace-encrypted map, in events and a projected read model, before and after erasure, with a raw MongoDB scan for plaintext at rest; erasure fences later appends), TestKernelComplianceCoarseCollections. Requires kernel 19.32.2 or later (Chronicle#4551). Model-bound read models cannot carry chronicle tags inside map value structs (they are projection declarations); classify those values by type |
| Collection-valued array elements / same kernel schema manager | Implemented: classified inner collections (nested lists and maps) are protected, released and erased as whole elements; elements with declared object members keep per-member protection | Same placement and kernel tests ([]ClassifiedNames, []ClassifiedLabels). Requires kernel 19.32.2 or later (Chronicle#4552) |
Recursive property overrides / Schemas/JsonSchemaGenerator, same kernel schema manager | Implemented for concrete recursive shapes: rebuild nullable pointer schemas from classified children and reject unresolved references | TestRecursiveOverrideSchemaReferencesAllResolve, TestReleaseRecursiveOverrideWithPopulatedEdges, TestKernelRecursiveOverrideReleaseWithPopulatedEdges; populated edges beyond the override retain their original classification |
Reserved PII subjects / kernel Core/Compliance/GDPR/PIICompliancePropertyValueHandler.cs at v19.32.3 | Go-specific defense in depth, kept deliberately: compliance.InvalidSubjectError rejects the $chronicle-encrypted-value$ prefix before append, batch, prepared/UoW, reactor-effect and seed admission; lifecycle prohibition remains | TestReservedSubjectsFailBeforeAdmissionOnEveryAppendPath, TestReactorEffectsRejectReservedSubjectsBeforeWrites, TestSeedSourcesCannotUseReservedSubjects, lifecycle validation test, TestKernelReservedPIISubjectsAndSDKAdmission. Since 19.32.2 the kernel refuses PII for a reserved identifier (Chronicle#4553), but it still accepts one as the subject of an unclassified event, which can later reach a protected read model and fail its projection there. The SDK check rejects every such subject at admission instead; C# has no client check. Choose an ordinary opaque subject |
Catalog-local provider admission / ComplianceMetadataResolver, Confidentiality/SecurityMetadataResolver | Go-specific: explicit compliance.Using providers, no assembly/global discovery | TestClassificationCatalogIsolationProviderAndNaming; deterministic metadata callbacks run during compilation, not release. Arbitrary new kernel metadata categories/handlers are not exposed |
Subject confidentiality / ProtectedValues/EncryptedMetadataProvider, EncryptionScope.Subject | Implemented: encrypted defaults to subject; security / EncryptedSubject, never compliance / PII | Classification test and TestKernelConfidentialityIsIndependentOfPIIErasure prove subject encryption survives PII erasure |
Namespace confidentiality / EncryptionScope.Namespace | Implemented: encrypted(scope=namespace) and subject-independent release | Classification test, TestSubjectIndependentReleaseAndCollections, confidentiality kernel fixture; namespace scope is the store/namespace key coordinate, not cross-store tenancy authorization |
Global confidentiality / EncryptionScope.Global, kernel ProtectedValues/EncryptedValueKeyIdentifiers | Implemented on 19.32.3: exact security / EncryptedGlobal wire and subject-independent release | Schema assertions and release tests pass; TestKernelGlobalConfidentiality appends, reads and erases PII beside a global confidential value, which survives. On 19.29.4 MongoDB rejects the reserved global store coordinate (Chronicle#4549, fixed in 19.32.2). No client coordinate workaround |
Non-string protected scalar kinds / kernel ProtectedValues/ProtectedValueCodec, Schemas/JsonSchemaMetadataManager | Implemented on 19.32.3: C#-equivalent scalar classification, strict typed decoding | TestKernelComplianceNumericScalar requires a protected integer to release as a JSON number and decode. On 19.29.4 it releases as a JSON string (Chronicle#4550, fixed in 19.32.2). The SDK does not silently coerce malformed protected payloads |
Source-identity prohibition and category conflicts / PIIMetadataProvider, EncryptedMetadataProvider:42–71 | Go-specific safety correction: always reject protected events.SourceID, including nested/type/provider paths; reject inherited PII + Encrypted | TestProtectionAlwaysRejectsEventSourceIdentity, TestProtectionConflictsFailBeforeRegistration, TestSecurityAndIgnoredEventTagsFailClosed. C103’s direct-member short circuit does not bypass Go’s guard. An unrelated string field named ID is not an EventSourceId type |
Event subject / SubjectResolver | Implemented for declared scalar/concept subjects: override → explicit resolver → tag → source, no event ID fallback | Existing TestFirstAppendPolicyAndSubjectsAcrossAllPaths and declared-subject tests; the kernel lifecycle fixture uses an owner different from its source |
Model subject and lineage / ReadModels/ReadModelSubjectResolver, kernel ReadModels/ReadModelsCompliance | Go-specific raw lineage preservation and grouping; configured/tagged subject → case-insensitive Go ID, not projection Key | TestTaggedSubjectFallsBackToIDButNotProjectionKey, TestReadModelReleaseMultipleSubjectGroups, TestReadModelReleaseNumericSubjects, TestReleasePreservesStoredLineageAgainstUnexpectedReplyFields, TestReleaseSendsDeclaredBookkeepingButKeepsUndeclaredFieldsLocal, TestKernelRawDocumentReleasePreservesBookkeeping; only schema-declared fields enter the Release RPC, while raw lineage and sink bookkeeping survive locally. The kernel fixture reconstructs a sink envelope from a materialized instance and uses real release, not direct MongoDB access. Zero UUID remains its string, numeric token spelling is exact. Nested subject tags are rejected rather than inventing per-element ownership |
Model release boundaries / ReadModelReleaser, ReadModels, MaterializedReadModels | Go-specific fail closed: explicit Release/ReleaseMany accept unreleased documents only and are not idempotent. Materialized keyed/collection reads and store-owned passive folds validate final state without another decrypt. Raw/typed windows do likewise only for MongoDB root string PII/EncryptedNamespace properties, excluding classified exact _id and case-insensitive id aliases; frozen type/provider metadata must compile to that same profile. Other nested/nullable/collection/encryption-scope/sink/custom window profiles refuse before RPC. Projection replay/history/immediate/session/decision reads return kernel-released values on 19.32.2 or later (Chronicle#4561) and are validated without another decrypt. Classified Watch remains unsupported | TestProtectedMaterializedGetNeverReleasesPlaintextAgain, TestCollectionServerReleaseIsNotRepeated, TestSnapshotsValidateKnownProtectedContributionsWithoutSecondRelease, TestMaterializedWindowsNeverDecryptReleasedPlaintextAgain, TestNamespaceEncryptedWindowsValidateServerReleasedValuesWithoutRelease, TestUnwitnessedMaterializedProfilesRefuseBeforeRPC; missing/null protected window roots and invalid final values fail without partial results. TestClassifiedMongoIdentityWindowsRefuseBeforeRPC and TestFrozenProviderAndTypeClassifiedIdentityWindowsRefuse cover admission with no RPC: the identity exclusion derives from Source/Kernel/Storage.MongoDB/Sinks/ChangesetConverter.cs:333 and Sinks/Sink.cs:196,324–330 storage behavior, not a kernel fixture protecting identities. TestProtectedProjectionRoutesUseKernelReleaseWithoutSecondDecryption, TestClassifiedDecisionsAreAdmittedWithoutSecondRelease, TestKernelModelHistoryProtectedReleaseProfile and TestKernelDecisionReadsReleaseClassifiedModelsWithEventSubjects cover the replay and decision routes; TestReadModelReleaseFailsClosed retains atomic release failures; independent confidentiality metadata does not excuse missing PII ownership |
Event history and typed decode / kernel Events/EventCompliance, Sequences/EventSequenceQuerying | Implemented for supported protected shapes: kernel releases history before events.Decode; no second client-side cipher implementation | TestKernelComplianceSubjectKeyLifecycle, nested fixture; decoder performs no I/O and cannot release arbitrary caller-supplied ciphertext. Protected event-generation migrations are only partially usable: since 19.32.2 (Chronicle#4456) the kernel transforms plaintext and protects every generation for the original subject, so the current representation and projections release correctly before and after erasure. Other generations in GenerationalContent are not released on reads or observer delivery and arrive as ciphertext (Chronicle#4623); the SDK has no client-side guard for this yet (#90), so the evolution guide documents the limit. TestKernelProtectedEventGenerationMigrationProfile characterizes 19.32.3 and fails once the kernel releases other generations |
Erasure and namespace-wide reach / Compliance/GDPR/PIIManager, kernel Compliance/GDPR/PIIManager:39–99 | Implemented: store.Compliance().ErasePII / DeleteEncryptionKeyFor, unchanged compliance RPC | TestLifecycleUsesNamespaceScopedKernelContracts, TestKernelComplianceSubjectKeyLifecycle; repeated erasure, second store, other-namespace survival, shredded events and read models. Unprotected outbox-to-inbox forwarding is implemented separately (TestKernelExternalSubscriptionForwardsOutboxToObserverInbox); protected forwarding and erasure across that path remain unverified. No multi-silo cache-eviction claim |
| Write fence and replacement-key authorization / same PII manager and kernel key storage | Implemented: failures propagate without append retries; AllowNewEncryptionKeyFor is explicit and cannot recover old ciphertext | Lifecycle kernel fixture rejects writes in both stores, permits another namespace and reauthorizes new writes while old content remains shredded. Wire has no stable typed EncryptionKeyErased detail; existing append/transport identities remain inspectable |
Partial lifecycle failures, unsupported kernels and confidentiality guard / IPIIManager, kernel EncryptionKeyLifecycleIncomplete | Implemented transport contract with redacted public error text; no success inferred from partial server work | TestLifecycleFailuresPreserveIdentityWithoutSensitiveDiagnostics, TestLifecycleRejectsReservedKeysAndCancellationBeforeDispatch; ErrLifecycle, errors.As, gRPC/context/unsupported identities. Distributed fault injection and backend recovery remain unverified |
Minimum kernel versions
Section titled “Minimum kernel versions”Chronicle 19.32.0 through 19.32.3 share wire contracts, so compatibility alone
does not show which kernel fixes are present. The client records them from the
server version the compatibility check reports, per connection; with
WithSkipCompatibilityCheck or an unrecognized version it assumes none, and
these routes return ErrUnsupported before any RPC
(TestKernelCapabilitiesAreRecordedFromVerifiedVersion). A -development image
counts as its release.
The guarantee holds at dispatch, not only when an operation starts. A gated
operation records what it needs in its call context, and the transport checks
those needs against the connection that is about to send each RPC, after
pinning it. If the client reconnected to an older kernel while the operation was
running (for example while an enricher or audit provider ran), the RPC is not
sent and the operation returns ErrUnsupported, reported as not dispatched. An
early check against the current connection still refuses quickly, before
providers or codecs run; it is not authoritative. Registration checks the
connection it registers on, and every reconnect registers again, so a store that
reconnects to an older kernel refuses its nested protection again
(TestDefinitionTransportRefusesTrackedNeedBeforeDispatch,
TestProtectedReviseRefusesWhenReconnectedToIncapableKernelBeforeDispatch,
TestClassifiedReplayRefusesWhenReconnectedToIncapableKernelBeforeDispatch,
TestMixedAllReplayRefusesWhenReconnectedToIncapableKernelBeforeDispatch,
TestNestedProtectionReRegistrationRefusesAfterReconnectToIncapableKernel). A
gated operation that has to wait for the client to connect or a store to
register does not pass its needs on to that shared work, so it cannot fail the
connection or registration other callers depend on
(TestRegistrationIgnoresCallerTrackedNeeds). An
operation refused by the early check is not retried after a reconnect to a newer
kernel; call it again (TestProtectedReviseOnIncapableKernelRefusesBeforeProviders,
TestNestedProtectionRegistersAfterReconnectToCapableKernel).
| Route | Minimum kernel | Kernel fix |
|---|---|---|
Projection replay of a mixed all-event projection (bounded/passive GetAll, ReplayProjection, GetSnapshots) | 19.32.1 | Chronicle#4562 |
| Revising an event with a protected replacement | 19.32.2 | Chronicle#4525 |
| Classified models on projection replay, history, hydration sessions and immediate reads | 19.32.2 | Chronicle#4561 |
| Registering protection beneath maps or on list- or map-valued array elements | 19.32.2 | Chronicle#4551, Chronicle#4552 |
| Decision reads (any model) | exactly 19.32.3 | characterized release |
Definition factories
Section titled “Definition factories”C02–C08/C10 and issues #21 /
#22 reference
Source/Clients/DotNET/{ClientArtifactsActivator,ActivatedArtifact}.cs,
Projections/Projections.cs:536–570, Events/Constraints/ConstraintsByBuilderProvider.cs,
Events/Migrations/EventTypeMigrators.cs, and Seeding/EventSeeding.cs at
2e31b0dfba489159b3db323238f16d0f277056b4. See the
config-backed definition workflow.
| Behavior | Go surface and status | Executable evidence / exact boundary |
|---|---|---|
| Config-backed preparation | Partial: RegisterProjectionFactory[P], RegisterConstraintFactory[C], RegisterEventMigrationFactory[M]; fixed family/output metadata, plain constructors or borrowed services | TestDefinitionFactoriesPlainAndFundamentalsProduceIdenticalFrozenArtifacts, ExampleRegisterProjectionFactory; original descriptors and names are required, not callback-driven artifact discovery |
| Activation ownership | Go-specific: temporary scopes, owned explicit constructor results, borrowed resolved services/provider, registered-service precedence | TestDefinitionPreparationCleansPartialScopesAndConstructorResultsBeforeDependencies, TestAdapterPreservesPartiallyOpenedScopeForOwnerCleanup, Singleton/scoped equivalence fixture; unlike C# non-disposable definition activation, disposable artifacts are allowed and released before publication |
| Failure boundary | Go-specific: payload-free PreparationError, inspectable ordinary causes, discarded panic values, cleanup failure prevents publication | TestDefinitionPreparationDiscardsPanicsWithoutEverFormattingTheirValues, TestDefaultProviderPreparationDiscardsFactoryAndDisposalPanics, TestPartialScopeIsClosedWhenOpenErrorIsOpaqueOrPanicked, TestSanitizerNeverInvokesHooksThatMutateProviderDiagnostics, TestSanitizerDiscardsEntireSnapshotForComparableValuePanicIdentity; unlike C# log-and-skip or swallowed disposal failures, Go aborts preparation. Fundamentals.Go v0.1 provider diagnostics snapshot original fields before descent and are rebuilt without panic payloads/causes. Only exact provider diagnostics, recognized standard join/fmt wrappers, and comparable ordinary leaves without inspection hooks are admitted; only safe non-nil pointer leaf identities survive, unless quarantined as panic aliases. Opaque hooks are never called and reject the entire snapshot, as do ambiguous panic identities, cycles, or the 64-level/256-rooted-edge limits (including nil/repeated edges). Bounded inspection and cleanup/cancellation fixtures cover failure atomicity; application effects cannot be rolled back |
| Captured schema and definition epochs | Implemented for selected registries: capture all before callbacks, freeze all base schemas before definitions, compile a shared registry once | TestClientCapturesAllRegistriesBeforeApplicationPreparation, TestAllSelectedSchemasFreezeBeforeDefinitionCallbacks, TestSkipKeepAliveRejectsCapturedObserversBeforeAnyFactoryOrClassifier, TestFactoryPreparationRetainsOriginalModelHandle; classification results use Go member identity without retaining application providers |
| Canonical compilation and replay | Implemented for the three factory families alongside direct declarations | TestFactoryAndDirectRegistrationUnionMatchesAndReconnectIsCallbackFree compares complete gRPC requests to direct declarations and across reconnect; TestFactoryVariantsAndDirectGlobalsUseOneCanonicalGroupCompiler covers variant/global composition and frozen defaults/labels; TestKernelDefinitionFactoriesMaterializeConstrainAndEvolve covers materialization, uniqueness rejection, historical upcast and downcast on 19.32.3-development through plain, provider-scoped and provider-singleton activation; it asserts scope disposal, borrowed provider ownership and no double-close of resolved services |
| Identity conflicts and callback lifetimes | Go-specific fail-closed metadata and output checks | TestDefinitionFactoryDuplicateAdmissionIsAtomic, TestDefinitionFactoriesRequireExactOutputIdentitiesAndNoRuntimeMessages, TestDefinitionPreparationResolverIsBorrowedGuardedAndExpires; direct migration duplicates now fail registration atomically rather than waiting for NewClient. Factory constraints allow static messages only; no retained scoped message provider |
Borrowed client composition / DotNET/ChronicleClientServiceCollectionExtensions.cs at 2e31b0dfb | Implemented for the optional Fundamentals v0.1 adapter: CaptureClient, ClientPreparation.Client/Prepare, services.PrepareClient, BindClient, BindEventStore, BindEventLog, BindEventTypes, BindReadModels, BindCompliance | TestFacadeConsumerCaptureBindPrepareAndResolveCoordinates, TestFacadeSeparateProvidersDoNotShareStoreCache, TestBindingDescriptorsUseExactKeysBorrowedFacadesAndResourceFreeOwnedCell: exact borrowed Singleton client and Scoped concrete facades; two stores × two namespaces; interface identity and provider/scope non-disposal. Existing plain constructors stay first-class. No Arc SDK adoption or other-container interoperability claim; provider-internal graphs remain an application contract |
| Scoped selection / same C# service registration source | Go-specific: trusted-context StoreSelector, successful cached private cell freezes coordinates or safe error; retry registration without reselection | TestFacadeFailedRegistrationRetriesFrozenCoordinates, TestSelectorFailureFreezesSafeErrorAndRequiresNewScope, TestSelectorOwnerCancellationIsFrozenDespiteCallbackSuccess, TestFacadeConcurrentWaitersDoNotCancelSelectorOwner, TestFacadeContextGuardBelongsToHostAndChecksPrincipalOnCachedResolution; callbacks run outside locks and must not resolve services. Hidden reentrant/global resolution is prohibited because provider in-flight waits precede adapter checks. Selection is not authorization; hosts own guards |
| Binding admission / same C# service registration source | Go-specific: exact keys and borrowed forwarding, Catalog preflight per helper, no generic Registrar rollback | TestBindEventStorePreflightsWholeBatchAndDependencies, TestBindingRegistrationFailuresAreReturnedWithoutRollback, TestFacadeUnpreparedDenialFreezesSelectionWithoutSDKIOOrPoisoning, TestFacadeClientClosedBeforeFirstScopeAccess; nils/duplicates fail, partial batches must be discarded. Metadata selection may precede the root preparation guard; no adapter Ready probe |
| Preparation lifecycle | Go-specific: caller-owned synchronous attempt, guarded identity, atomic publication after cleanup, retained outcome | TestCapturedAndFailedClientsGuardEveryEntryPoint, TestHandledCallbackDenialAndExternalProbeDoNotPoisonPublication, TestPreparationCloseCancelsButDoesNotJoinCallerOwnedCleanup, TestPreparationCleanupFailurePublishesNoDefaultOrStoreSnapshots; Close cancels but does not join preparation; denied operations do not poison success |
| All-store preflight and frozen capture | Implemented for selected definition/seeder metadata and visible constructor plans | TestPreparationPreflightsLaterStoreConstructorsBeforeAnyScope, TestCaptureDefersCallbacksAndFreezesAllSelectedEpochs, TestCapturedPreparationPreservesCodecProtectionAndOriginalModelHandle; plans retain declaration identity, codec/protection snapshots and store replacement semantics |
Event seeding
Section titled “Event seeding”C112 and the C03/C08 final registration stage reference
Source/Clients/DotNET/Seeding/* and EventStore.cs:RegisterAllArtifacts at
2e31b0dfba489159b3db323238f16d0f277056b4. Kernel paths below are repository-root
paths at that revision. Integration uses 19.32.3-development.
See Seed reference events for the workflow.
| Behavior / C# source | Go surface and status | Executable evidence / exact boundary |
|---|---|---|
Seeder discovery / ICanSeedEvents, EventSeeding.Discover | Go-specific: RegisterSeeder, RegisterSeederFunc, RegisterSeederFactory; explicit admission instead of assembly discovery | TestSeederInstancesAndConstructorsHaveExplicitOwnership, ExampleRegisterSeeder; duplicate concrete types fail, plain callbacks accumulate. Borrowed instances are not disposed; constructor results are |
Definition preparation / EventSeeding.Discover, IClientArtifactsActivator | Go-specific: synchronous preparation at NewClient/NewClientContext, immutable serialization snapshot, no constructor on reconnect | TestSeedersPrepareInOrderAgainstSelectedFrozenNamingCatalog, TestSeedPreparationOnceAndReconnectReplaysFrozenRequest; validates unknown types and naming against the selected catalog, store overrides replace defaults. Callbacks may not perform I/O or retain builders |
Scoped activation / EventSeeding.Discover, artifact activation disposal | Implemented for optional service scopes, with stricter failure propagation | TestSeedersUseFundamentalsPreparationScopeAndJoinCleanupFailures, TestSeederPreparationRejectsInvalidRegistrationsAndPreservesConstructionFailure; open → construct → seed → dispose → close. C# logs and skips failed activation; Go fails construction and preserves cleanup errors. No DI container is imported by the core SDK |
Typed and heterogeneous declarations / IEventSeedingBuilder.For<TEvent>, ForEventSource | Implemented: seeding.For[E], Builder.ForEventSource | TestSeedRequestMatchesHandDerivedCSharpGolden, TestSeedPreparationFailsAtomically; event IDs, non-nil pointers, payload serialization and static tags; fluent errors cannot silently discard invalid entries |
Scoped declarations / IEventSeedingScopeBuilder, ForNamespace | Implemented: independent scoped builder views, explicit default namespace distinct from global | Golden test and TestKernelSeedingRestartReconnectNamespaceIsolationAndObserverVisibility; red-only seeds never appear in default/blue. Namespace selection is not authorization |
Wire grouping / EventSeeding.Register | Implemented: authoritative SeedEvents request, both global and namespaced source/type indexes in first-seen order | TestSeedRequestMatchesHandDerivedCSharpGolden; fixture is hand-derived, not emitted by .NET. TestSeedSnapshotAndRepeatedFacts preserves repeated equal entries and owns input/output snapshots |
Registration order / EventStore.RegisterAllArtifacts | Implemented for supported observers: seeding last, including background reconnect replay | TestSeedingIsLastAfterEveryObserverRegistrationSend; event types/read models precede constraints/projections and reactor/reducer registration sends. Streams have no kernel registration acknowledgement. External subscriptions now precede read-model reactors and seeding; see External integrations |
Failed sends, consumption and replay / EventSeeding.Register | Implemented: store-wide generation single-flight, retained snapshot on failure, successful send cached, same snapshot replayed on reconnect | TestSeedFailureRetainsSnapshotAndManualBatchConsumesOnlySuccess, TestSeedSendIsSingleFlightAndWaiterCancellationDoesNotConsumeWork, TestSeedPreparationOnceAndReconnectReplaysFrozenRequest; bounded retry and inspectable RegistrationError/envelopes, never startup Append |
Manual seeding buffers / IEventStore.Seeding, EventStoreSeedingExtensions.CreateEventSeeding | Go-specific: EventStore.PrepareSeeds → SeedBatch.Register(ctx) | TestSeedFailureRetainsSnapshotAndManualBatchConsumesOnlySuccess; each buffer prepares once, retries retained entries and consumes only success. New buffer for corrected definitions; unscoped entries remain global |
Kernel idempotence / Source/Kernel/Core/Seeding/EventSeeding.cs | Implemented for ordinary seeds in existing namespaces across client restart and real connection interruption | TestKernelSeedingRestartReconnectNamespaceIsolationAndObserverVisibility, TestKernelCSharpThenGoSeedsAreIdempotent; repeated equal facts, C# → Go content-byte equality, static tags, namespace isolation, restart and reconnect readback. No client deduplication or claim about kernel crash atomicity |
Seed observer visibility / kernel Observation/Observer.Catchup.cs, Observation/Jobs/CatchUpObserver.cs | Implemented on 19.32.3: the observer receives the global seeds and the later default-only seed | TestKernelSeedingRestartReconnectNamespaceIsolationAndObserverVisibility/observer_visibility checks persisted content first and fails on any missing delivery. On 19.29.4 a finishing catch-up job could strand the next seeded partition while the observer remained Active and subscribed (Chronicle#4548, fixed in 19.32.3) |
| Late observer subscription / kernel observer catch-up | Implemented on 19.32.3: an observer that subscribes after events exist for a key catches up without dropping later live events for other observers | Integration tests append right after WaitForRegistration, without waiting for observers to become Active. EventStore / WaitForRegistration still means registration was sent, not that observers are subscribed or caught up. On 19.29.4 the late subscription’s catch-up could drop events appended to the same key for other observers, including projections (Chronicle#4558, fixed in 19.32.3); reactors keeps the startup wait for older kernels. TestKernelCacheEvictionRetainsWatchAndExplicitReconnect still waits through awaitObserversObserving: without it the reactor received its previous event again after explicit reconnect |
Global seeds in later namespaces / IEventSeedingBuilder.For, Source/Kernel/Core/Namespaces/NamespacesReactor.cs | Partial: correct global wire and persistence; pinned kernel does not populate later-created namespaces | TestKernelGlobalSeedsReachFutureNamespaces sends a hand-derived C# request directly, confirms initial append plus global tracking, then conditionally skips the observed empty later namespace after a bounded wait. Chronicle#4547; no append workaround |
Generation and rejected-seed outcomes / kernel SeedingEntry, SeedEvents.Handle | Partial protocol capability, matching C#: no generation field and no per-entry append-result response | Golden request pins the available fields; kernel test proves generation one. Kernel rejection logging/response policy is recorded in Chronicle#3662. Registration success means command acknowledgement, not verified append of each seed. Protected-value compatibility limits are listed below; ordinary seeding has no such prerequisite |
External integrations
Section titled “External integrations”C113–C114 references are relative to Source/Clients/DotNET at
2e31b0dfba489159b3db323238f16d0f277056b4; kernel evidence uses the pinned
19.32.3-development image. See External integrations
and Capture declarations.
| Behavior / C# source | Status and Go surface | Executable evidence / boundary |
|---|---|---|
Subscribe / EventStoreSubscriptions/{EventStoreSubscriptions,EventStoreSubscriptionBuilder} | Implemented: EventStore.Subscriptions, immutable eventstoresubscriptions.Define, Subscribe/Register | TestSubscriptionWireDefaultsOwnershipAndIsolation; hand-derived C# request, no filters means all current catalog IDs, first-seen deduplication, generation-one wire filters, opaque unregistered IDs supported. Go rejects blank coordinates/self-subscriptions |
List/remove / IEventStoreSubscriptions.GetAll,Unsubscribe | Implemented: detached definitions, GetAll, Unsubscribe | Same wire test and TestKernelExternalSubscriptionForwardsOutboxToObserverInbox; store-wide coordinates, read-back generations reduced to IDs as in C#. Removal materializes asynchronously; inbox history remains |
Reactor/reducer source inference / ReactorTypeExtensions, ReducerTypeExtensions, ResolveSourceStoreForObserver | Implemented: event WithSourceStore, observer WithSourceStore, immutable Plan.ForStore | TestObserverOriginsBindSequencesModelsAndSubscriptionUnion, TestObserverExplicitSequencesOverrideInferenceAndMixedSourcesFail; local origin uses log, external uses inbox, unspecified origins ignored, incompatible named origins fail, observer source overrides and rejects explicit sequence combinations |
| Explicit sequence escape / observer type extensions | Implemented: explicit log/custom sequence suppresses reactor/reducer inference and automatic provisioning | Explicit-sequence test above. C# projection exception retained: an explicitly selected inbox still provisions, unlike a reactor/reducer explicit sequence |
Automatic subscription union / EventStore.RegisterExternalEventStoreSubscriptionsAsync, Projections.GetExternalEventStoreSubscriptions | Implemented, Go-specific broader projection coverage: source name is subscription ID, union distinct event IDs | Union test and TestExternalSubscriptionsFollowObserversPrecedeSeedingAndReplayPerGeneration; projections include child/nested/removal handlers to match Go inference rather than C# root From/Join-only enumeration |
Registration order, failures and reconnect / EventStore.RegisterAllArtifacts | Implemented: after observer sends, before read-model reactors/seeding, shared across namespaces and replayed per generation | Registration ordering test checks sends, union wire, failure outcome, blocked seeds, namespace sharing and reconnect. Stream registration has no kernel acknowledgement. Explicit user definitions are not silently retried or automatically retained for replay |
| Outbox → inbox → observer | Implemented on the pinned kernel | TestKernelExternalSubscriptionForwardsOutboxToObserverInbox; two stores, auto-provisioned subscription, source outbox append and receiving reactor. No event-log publication, exactly-once or cross-namespace forwarding claim |
Webhook definitions/defaults / Webhooks/{WebhookDefinitionBuilder,WebhookDefinitionConverter} | Implemented: immutable Define, explicit Register/RegisterDefinition, target, exact event generations, headers, sequence, replay/activity options | TestWebhookWireAuthorizationReadbackAndRemoval, TestFalseFlagsAreExplicitOnCSharpWire; default log, active/replayable true and current catalog filters. Fresh outgoing wire snapshots force explicit zero booleans for protobuf-net defaults without modifying generated contracts |
Webhook auth and read-back / Webhooks/{WebhookDefinitionBuilder,WebhookDefinitionConverter,Webhooks} at 2e31b0d | Implemented wire and redacted authoring; Partial runtime | TestRegisterMatchesActualAuthenticationCapture compares actual packaged 19.29.4 C# Register requests for None/Basic/Bearer with Go, including catalog filters, headers and boolean presence. Go WithOAuth is converter/contract-backed convenience: the public C# Register builder exposes Basic/Bearer, not OAuth. Authentication evidence separates public-package observations from live behavior. TestWebhookBearerRegisterDefinitionWire submits bearer alone and compares the actual body, including Value1 and explicit false flags; separate Basic/OAuth wire controls prevent authorization-arm confusion. TestKernelWebhookBearerRegistrationCSharpSourceEquivalent compares SDK and independently assembled C#-source-equivalent requests, not an executed .NET capture. On 19.32.3 it requires each authorization-event schema in the selected store, the authorization kind on named read-back and WebhookAdded plus the authorization event in system history for both paths. On 19.29.4 fresh non-System stores lacked those schemas and persisted only WebhookAdded (Chronicle#4567, fixed in 19.32.3). No credential persistence, token acquisition or HTTP delivery claim |
Webhook activity persistence / kernel Storage.MongoDB/Observation/Webhooks/WebhookDefinitionConverters.ToKernel | Partial: explicit false wire works, replay false round-trips, MongoDB read-back drops inactive state | TestKernelWebhookInactiveCSharpWireReadback compares Go with explicit-zero C#-equivalent wire and skips only both-active/replay-false signature, citing Chronicle#4394. MongoDB converter omits IsActive and uses the concept’s true default; no client falsification of returned state |
Webhook removal / kernel RemoveWebhooks | Go-specific additive Remove beyond C# IWebhooks | TestWebhookWireAuthorizationReadbackAndRemoval, TestKernelWebhookDefinitionRegisterListRemove; store isolation and asynchronous materialization |
External HTTP/database services / ExternalServices/{ExternalServices,ExternalServiceBuilder} | Implemented definitions and registration: HTTP, MSSQL, PostgreSQL, auth, headers, provider options | TestExternalServiceWireAndSecretOwnership, capture kernel fixture confirms HTTP service persistence; ID=name, final endpoint only, port zero preserved. No Go HTTP/database drivers or endpoint-connectivity claim; no high-level administration beyond C# registration |
| Contexts, failure boundaries and secret ownership | Go-specific fail-closed command/query envelopes, no explicit write retries, redacted formatting | TestIntegrationServicesPreserveFailuresAndCancellationWithoutRetry, ownership tests; inspectable gRPC/envelope errors, detached exports, fmt redaction. Explicit KernelDefinition, header/URL accessors and kernel diagnostic collections can contain secrets; never log them |
Capture authoring / Captures/{ICapturer,CaptureBuilder,AppendBuilder,MapBuilder,*SourceBuilder,*CaptureBuilder} | Partial: Capturer, Prepare, Builder, immutable UUID definition, API/webhook/topic sources, conditions, mappings and child/nested scopes | TestCaptureDeclarationGoldenAndSnapshot, TestCaptureScopesConditionsAndMappings, TestCaptureValidationRejectsMissingOrInjectedDeclarations, ExampleBuilder; deterministic CDL. TestCaptureTranslationTargetsUseCDLWordGrammar, TestCaptureIdentifiersMatchConstructGrammar, TestCaptureStringLiteralsEscapeOnlyScreenplaySequences, TestCapturePropertyNamesCannotBecomeLifecycleConditions and the opt-in TestScreenplayCaptureParserPreservesGoDeclarations verify Screenplay 4.16.0 Parsing/CaptureParser and Text/StringLiteral: unquoted word translation targets, uppercase-leading single event IDs, construct-specific names, literal round trips and reserved property condition kinds. Go uses persisted event IDs, not the C# simple CLR name; unrepresentable IDs/translation targets fail explicitly rather than rename. Webhook-source authorization is authoring-only; model-bound capture attributes are not implemented |
Inbound capture authorization / client Captures/{IWebhookSourceBuilder,WebhookSourceBuilder}, kernel Source/Kernel/Concepts/Captures/{SourceAuthorization,SourceAuthorizationJsonConverter} at 2e31b0df; actual NuGet 19.29.4 source ae5e00a8 | Partial authoring: variadic captures.Webhook, WithBasicAuth, WithBearerToken, WithOAuth, immutable SourceAuthorization, source/definition Authorization accessors; Go-specific fail-closed configuration and JSON | TestSourceAuthorizationMatchesActualPackage compares 4 arms × 2 policies byte-exact; TestWebhookSourceAuthorizationMatchesActualSerialize checks 8 cases × 2 policies, retaining absent defaults and single-option bytes. Explicit None is not authorable (zero bytes only). Go rejects blank values, nil options and multiple authorization options at Build rather than C# last-wins, so no credential is silently discarded. TestCaptureAuthorizationRejectsInvalidOptions, TestCaptureAuthorizationSurvivesCopies, TestCaptureDeclarationNeverCarriesCredentials and ExampleWithBearerToken verify authoring/ownership/CDL exclusion. TestSourceAuthorizationHasNoDecoder refuses all 12 deserialize inputs × 2 policies instead of the C# missing/unknown/case-changed discriminator None fallback that drops credentials. TestCaptureAuthorizationRedacted verifies direct fmt/slog redaction and JSON import/export ErrUnsupported on authorization/source/definition/builder; TestCaptureAuthorizationHiddenInUnexportedFields verifies credentials remain hidden in private caller fields and two-level wrappers, including fmt invalid-verb fallbacks and both slog handlers. Credentials are retained behind an immutable opaque pointer, not encrypted memory; other declaration settings can appear when Go cannot invoke formatting methods. No empty-object export or public credential-export API. One-argument calls still compile, but func(string) Source values must adapt to variadic Webhook; JSON users must use explicit authoring. Fixture hashes remain checked by shared loaders. No encryption, enforcement or persistence claim; see authentication evidence |
Capture submission/runtime boundary / kernel Captures/{ValidateCaptureDeclaration,SaveCapture}, Engine/{CaptureValidator,DeclarationLanguage/CaptureDefinitionSyntaxVisitor}, Source/Kernel/Protobuf/captures.proto at 2e31b0df / package ae5e00a8 | Partial: explicit EventStore.Captures().Validate/Save, no activation or discovery; source-authorization submission Not implemented, Go-specific local refusal | TestCaptureAuthorizationSubmissionRefusedBeforeDispatch proves ErrUnsupported and zero Invoke/NewStream for every authorization kind, including canceled/expired contexts; TestCaptureWithoutAuthorizationStillSubmitsCDL preserves unprotected declaration bytes/dispatch. CDL-only Save/Validate requests cannot carry authorization; the syntax visitor builds it absent and the pinned kernel rejects non-API sources. Chronicle#4591 tracks the upstream authorization transport/kernel boundary and converter fallback. TestKernelCaptureAuthorizationSubmissionBoundary verifies an unprotected webhook persists as stopped before ValidationError, while a bearer definition is refused locally and absent from GetCaptures. TestCaptureSubmissionReportsCapabilityAndPartialSave, TestKernelCaptureCapability retain API save/read-back and unsupported translation-map partial-save behavior. Messages fail even when the outer envelope succeeds; unprotected Save may already have persisted a stopped definition. No credential storage, authentication, activation or polling claim; map/nested/children/expression runtime remains unsupported |
| Zero-container workflow and immutable preparation | Go-specific explicit functions instead of assembly discovery | Compiled/executed examples/integrations example, existing no-container dependency gates; no registration constructor, grpc.Server, database driver or dependency-injection container in new production packages |
Read-model watches and reactions
Section titled “Read-model watches and reactions”C81–C85 references: Source/Clients/DotNET/ReadModels/ at
2e31b0dfba489159b3db323238f16d0f277056b4. Protocol/kernel evidence uses
19.29.4 (ae5e00a8abaa688138b2c2f689e2b4659cccb4fd). These rows do not claim
that the wider #33 collection/history inventory is complete.
| Behavior / C# source | Status and Go surface | Executable evidence / deliberate differences |
|---|---|---|
Projection watch and Subscribed / IReadModels, ReadModelWatcher:65–159 | Implemented: typed Reader.Watch, raw Service.Watch, explicit subscription | TestWatchReadinessOrderingAndInterruption, TestKernelProjectionWatchAndReadModelReactor; marker is not a snapshot; early racing changes are retained in order. Projection watches do not require reducers |
Change type, key, model and synthesized context / ReadModelWatcher | Implemented: Change[T], explicit removal/value presence, unknown event type | Ordering test verifies add/modify/remove, ID normalization, namespace, selected sequence and position. TestWatchesAndWindowsRoundTripRegisteredNamingPlan and TestReadModelReactorDispatchUsesRegisteredNamingPlan verify compiled-plan decoding distinguishes root Id from explicitly tagged ID in typed watches, windows and reactor callbacks. Unknown future wire change types fall back to Modified like C# |
Interruption, disposal and bounds / ReadModelWatcher.ResetForReconnect | Go-specific: Recv, Values, Done, Err, joined Close, count/byte limits | TestWatchCancellationBeforeReadyAndCloseJoin, TestWatchOverloadAndNamespaceFailClosed, TestWatchMalformedChangesAndEarlyIteratorExit, TestKernelWatchInterruptionRequiresExplicitRewatch; unlike C# auto-replacement, returned subscriptions terminate and never silently resume. Re-watch/refetch has no atomic handoff or durable cursor |
Protected release ownership / ReadModelWatcher:87–100, ReadModelReleaser, MaterializedReadModels:95–115 | Partial, Go-specific: admitted raw/typed windows validate server-released data without another Release. Classified projection Watch refuses before streaming because the kernel can corrupt cipher-shaped plaintext before SDK decoding (Chronicle#4566; on 19.32.3 the raw watch changeset is empty for default Id as well as lowercase id); classified local reducer Watch refuses before queue attachment because it lacks a delivery-time erasure fence | TestKernelReleaseRouteProfiles covers default Id and lowercase id unclassified keys, source != explicit Owner subject, and legitimate 344-character base64 plaintext (256 decoded zero bytes): before erasure, exact PII name and namespace-encrypted secret; afterward, only PII is empty and confidentiality remains exact, with no extra Release on raw/typed windows. Public raw Watch replies expose the upstream corruption; successful reducer folds before/after erasure establish fold state, not classified local-watch safety. TestClassifiedWatchesRefuseBeforeTransportOrLocalAttachment covers both refusals. C# Watch directly deserializes server output; C# window source calls Release, with no C# window execution claim here. See the window protection profile and compliance boundaries |
Session/sequence boundary / ReadModels and WatchRequest | Go-specific sequence-aware request, watches cannot enroll in sessions | Readiness wire test checks the configured non-log sequence; TestWatchAndSessionHaveIndependentLifetimes proves session dehydration does not stop changes. The protocol has no session ID; no protected-decision claim |
Materialized paging / IMaterializedReadModels, MaterializedReadModels.CalculatePaging | Implemented: Materialized().GetInstances, raw and typed | TestMaterializedWindowPaging, kernel watch test; nil = 0/50, negative skip, nonpositive take, -1 unlimited, aligned/non-aligned windows and int32 covering cap |
Materialized observation / MaterializedReadModels.ObserveInstances | Implemented: Materialized().ObserveInstances returns full replacement windows | TestMaterializedObserveInitialSnapshotOrderingAndTerminal, TestMaterializedWindowsNeverDecryptReleasedPlaintextAgain and kernel watch test; no subscribed marker exists, so opening awaits the first validated snapshot. Kernel coalescing can omit intermediate states |
Name conventions and callback alternative / IReadModelReactor, ReadModelReactorMethods | Implemented: RegisterReadModelReactor[R,M], RegisterReadModelReactorHandlers, exact Added/Modified/Removed discovery at NewClient | TestReadModelReactorConventionsReadyEffectsScopesAndNoRetry, TestReadModelReactorValidationAtNewClientAndFrozenRegistries, TestWatchExampleRegistersWithoutAContainer; optional leading Go context; explicit callbacks replace C# same-name overloads; all matches run |
Per-dispatch scope, collections, effects and failures / ReadModelReactors:133–161, ReadModelReactorInvoker:37–112 | Implemented, Go-specific serial dispatch | Convention runtime test verifies fresh activation/cleanup, zero/one-element collections, returned event and failure reporting; existing scope/activation/effect pipeline is reused. Missing value for nonnullable M fails instead of inventing state. Errors report without retry; all callbacks still run |
Materialized convention / MaterializedAttribute, MaterializedReadModelDiffer | Implemented: reactors.Materialized(window), WindowDiffer | TestReadModelReactorMaterializedUsesWindowsAndJoinsCancellation, TestMaterializedDifferUsesSerializedModelsAndExplicitWindowRemoval, TestMaterializedDifferAddsAndRemovesBooleanKeys; string, numeric and boolean keys use C# scalar stringification; key/content differ, initial Added, nil Removed. Go rejects missing/duplicate keys and prefers registered key metadata; leaving a window does not imply deletion |
Selected-store callback/effect sequence / EventStore, ReducerTypeExtensions, ReadModelReactors (client revision above) | Go-specific sequence-aware callback context and side-effect delivery; read-model reactor plans bind to the same selected-store model as reducers/projections | TestReadModelReactorsBindCallbackAndEffectMetadataToSelectedStore; origin-store event-log versus external-store inbox-orders for materialized callback contexts and both materialized/change side-effect deliveries, default/store registries, immutable compiled plans. C# synthesized contexts do not carry a sequence ID; Go’s additional sequence metadata must match the selected model |
Local reducer feed / ReducerObservers, ReadModelReactors.DetermineChangeType | Implemented for unclassified models: production fold notifications, first-seen Added in reactors | TestReadModelReactorInfersFirstSeenLocalReducerAddition, TestLocalReducerWatchPlaintextRemovalAndGenerationIsolation; local Watch remains Modified/Removed, reactors forget removed keys. Final plaintext is validated without Release; classified Watch refuses before attachment. Local readiness is not a server acknowledgement or proof of sink persistence |
Reactor lifetime and tenant isolation / ReadModelReactors.Start/Stop | Implemented: generation-owned shared observer lifecycle and unregister joins | TestReadModelReactorGenerationRetiresAndNamespacesStayIsolated, TestReadModelReactorStartupReporterCanReenterSameStore, convention runtime test; startup failure completes readiness before reporting on the owned worker; no callback under internal locks, cancellation joins cooperative workers. New generations start fresh watches; no missed-change recovery claim |
C80 all-instance/event-count reads and correlation-grouped history / ReadModels.cs:262–328,397–449, Reducers.cs:259–350 at the baseline revision | Partial: raw/typed GetAll, Collection[T], events.Count/UnlimitedCount, GetSnapshots, Snapshot[T]; bounded active/passive local reducer folds | TestCollectionCountsRoutingAndReportedProgress, TestReducerCollectionsGloballyBoundHistoricalFoldsAndKeepActualPositions, TestSnapshotsOwnCompleteContributionContextWithoutInventingPresence, TestKernelModelHistoryProjectionCollectionsAndCorrelationGroups, TestKernelModelHistoryReducerCollectionRoutingAndDeletion. Zero means no I/O; nonzero explicit reducer counts fold locally; finite overflow is rejected rather than clamped. Reducer snapshots, nonempty projection defaults and unproven key/relationship replay fail explicitly; see collection/history limits |
Event-level model-bound declarations
Section titled “Event-level model-bound declarations”All C# paths in this section are relative to Source/Clients/DotNET at
2e31b0dfba489159b3db323238f16d0f277056b4. Registration uses the pinned 19.32.3
contract above. The shared declarations parser is used by projections, events
and read-model indexes; there is no second tag grammar or uniqueness engine.
Syntax and role errors can fail during registration; catalog conflicts and
semantic event declarations fail atomically at NewClient, before I/O.
| C# declaration / source | Go surface and status | Evidence and exact boundary |
|---|---|---|
Property [Unique] / Events/Constraints/{UniqueAttribute,UniqueConstraintProvider} | Implemented for top-level fields: chronicle:"unique(...)" compiles through UniqueValues().On() | TestCompositeDeclarationImprovementMatchesExplicitBuilderGolden, TestDeclarationUnrestrictedSequencesDefaultsAndFrozenStores, TestKernelModelBoundUniqueRemovalAndSubject; shared names, first nonempty message, unrestricted-sequence-wins, exact serialized paths |
Same-event same-name Unique fields / UniqueConstraintProvider.cs:77–80, UniqueConstraintBuilder.cs:131–135 | Go-specific improvement: one ordered composite On per event | TestCompositeDeclarationImprovementMatchesExplicitBuilderGolden; C# calls On per property and throws for the repeated event. Decision: follow #24 §5’s composite lowering, not that upstream failure; duplicate directives on one field still fail |
Type [Unique] / UniqueEventTypeConstraintsProvider | Implemented: events.WithUnique(events.Unique{...}), shared lifecycle names | Golden test above checks both constraint kinds and per-violating-type messages with fallback. Name defaults to Go type name, independent of WithID; one declaration per type |
Unique<TEvent> / IConstraintBuilder, ConstraintBuilder | Implemented: constraints.UniqueEventTypes, plus WithUnique on that registered type | Golden test above. The inspected C# snapshot has a generic builder method, not a Unique<TEvent> attribute; no nonexistent generic attribute is claimed |
[RemoveConstraint] / RemoveConstraintAttribute, both Unique providers | Implemented: events.WithRemoveConstraints for model-bound constraints; RemovedWith for explicit builders | Golden and kernel model-bound tests attach every remover, deduplicate repeated names and release the owner’s claim. Go rejects unknown names rather than silently ignoring them |
Explicit/declaration composition / Constraints duplicate-name protection | Go-specific: Registry.ConfigureDeclaredConstraint composes the same builder; AddConstraint never replaces a declaration | TestDeclaredConstraintsRequireExplicitComposition; duplicate explicit definitions still fail, mixed same-name definitions produce typed errors, callbacks run once per snapshot |
[Subject] / SubjectAttribute, SubjectResolver | Implemented for top-level scalar/concept fields: subject, existing WithSubjectResolver | TestTaggedSubjectPrecedenceAndStaging, TestTaggedSubjectScalarConversions, TestEmptyTaggedSubjectsFallBackToSourceAcrossAppendPaths, TestExplicitEmptySubjectResolverStillFails, kernel model-bound test. Append override → explicit resolver → tag → source; false explicit resolver skips the tag. Nil or empty means absent for tags, no ID-field fallback; explicit resolvers retain strict empty-subject validation. Multiple fields fail instead of C# first-property selection; non-string scalar conversion uses Go’s invariant formatting, not culture-sensitive CLR ToString |
[Index] / ReadModels/{IndexAttribute,ReadModels.cs:475–527} | Implemented: index tags add to existing WithIndexes metadata | TestIndexTagsShareExplicitMetadataAndKeepRepeatedNestedTypes, TestIndexDeclarationsRejectDuplicatesAndWrongRoles, TestIndexTagsSkipMapValuesLikeCSharpDictionaries, TestIndexesDoNotBecomeProjectionMappings; paths include arrays/slices, but map-value descendants are skipped like C# dictionaries. Index tags on the map property itself remain supported. Go correction: path-local cycle guards preserve repeated nested types that C#‘s global visited set can omit |
[EventType] / Events/{EventTypeAttribute,EventTypes} | Implemented for current descriptors: RegisterEvent, WithID, WithGeneration | Existing TestRegistration and registration/kernel generation tests; explicit root admission, no assembly scan. Historical generations/migrations have separate behavior rows below (#32) |
[Tombstone] / Events/{TombstoneAttribute,EventTypeExtensions.cs:109–123} | Go-specific: WithTombstone records descriptor-only metadata (IsTombstone()) | TestDescriptorMetadataRegistrationContract; C# attribute is inert; Go records it on the descriptor; wire matches C# (Tombstone=false for registration and appends). Missing C# consumer tracked in Chronicle#4541; no deletion or historical tombstoning workflow claimed |
[CompensationFor<T>] / Events/CompensationForAttribute*, Schemas/JsonSchemaGenerator.cs:444–451 | Implemented: WithCompensationFor(handle) adds top-level schema compensationFor with the target’s persisted ID | TestDescriptorMetadataRegistrationContract, TestEventDeclarationsFailAtomicallyAtNewClient, TestCompensationSchemaPreservesUnsignedBounds; same-registry reference, preserved in current schema and generation schema without changing numeric bounds. This is metadata, not compensation execution |
[EventStore] / Events/EventStoreAttribute, Events/EventTypes.cs:130 | Implemented descriptor provenance: WithSourceStore, registration EventStore, existing observer inference | TestDescriptorMetadataRegistrationContract and existing source-inference tests. No assembly-level default or append rerouting; observer inference and automatic provisioning are covered under External integrations |
[Tag] / TagAttribute | Implemented static append labels: existing WithTags | TestAppendMetadataAndNamedTags; repeated options remain last-wins, unlike additive CLR attributes. Not implemented as registration labels: 19.32.3 has no such field (TestDescriptorMetadataRegistrationContract). No event-property extraction or observer filtering |
[Tags] / TagsAttribute | Implemented static append label lists: existing WithTags | TestAppendManyRoutesAndStaticTagUnion; static/dynamic labels merge distinctly. Same last-wins option and absent registration-label boundary as [Tag] |
[PII] | Implemented for field/type schema classification; scalar runtime limits below | TestClassificationSchemaMatchesCSharpCategoriesAndPropagation, TestProtectionAlwaysRejectsEventSourceIdentity; subject identity is not protection |
[Encrypted] | Implemented for distinct security schema classification in all three scopes; global runtime remains Partial | Classification golden assertions and TestKernelConfidentialityIsIndependentOfPIIErasure; see the scoped rows below |
Blank constraint sequence IDs are errors in Go instead of C#‘s filtering. Nested event Unique/Subject tags fail explicitly because the referenced providers scan root properties only; read-model indexes do recurse. See event metadata for the public options and constraints for composition and migration guidance.
Event evolution
Section titled “Event evolution”C13–C17/C48 references: Chronicle 2e31b0dfba489159b3db323238f16d0f277056b4,
Source/Clients/DotNET/Events/{EventTypeExtensions,EventTypes}.cs and
Events/Migrations/*; kernel Core/EventTypes/EventTypeRegistrar.cs and
Core/EventSequences/Migrations/{EventTypeMigrations,EventMigrationExpressions}.cs.
Wire definitions use the pinned 19.32.3 protobuf contracts. See the
evolution guide and offline example.
| Behavior / C# source | Status and Go API | Executable evidence and differences |
|---|---|---|
Historical ID derivation / EventTypeGenerationForAttribute*, EventTypeExtensions:85–130 | Implemented: RegisterEventGeneration[Previous](registry, current, generation) | TestHistoricalCatalogExactAndLatestLookup: inherited ID, lower positive generation, registered current, no mixed/overridden identity. Explicit registration replaces markers and scanning |
Exact and latest lookup / EventTypes:226–244 | Go-specific: LookupRef is exact; LookupID selects current | TestHistoricalCatalogExactAndLatestLookup: C# falls back to latest for missing CLR generations; Go retains explicit failure rather than guessing a codec. Duplicate current IDs remain forbidden; historical (ID, generation) pairs are distinct |
Real generation schemas before migrations / EventTypes:114–198 | Implemented: grouped event registration with ordered concrete schemas | TestMigrationRegistrationMatchesCSharpWire: real old/current schemas, one registration per ID, latest top-level schema, adjacent migration wire fields. Go requires concrete registered endpoints rather than C#‘s compatibility placeholder schemas |
Adjacent shared identity and duplicate migrators / EventTypeMigration:29–49, EventTypeMigrators | Implemented: RegisterEventMigration, NewClient validation | TestMigrationInvalidGraphsFailBeforeConnection: wrong IDs, gaps, duplicate pairs, missing endpoints and missing chain fail atomically without I/O |
Complete chain and validation defaults / EventTypeRegistrar.ValidateMigrationChain | Implemented: WithEventTypeGenerationValidation(true) checks every link at construction | TestMigrationChainIsCompleteOrderedAndStoreIsolated, TestKernelGenerationWithoutMigrationsMatchesCSharp: disabled default retained; enabled errors move earlier from kernel registration to NewClient |
Rename / EventMigrationPropertyBuilder.RenamedFrom | Implemented: MigrationBuilder.RenamedFrom emits $rename | TestMigrationRegistrationMatchesCSharpWire, TestHistoricalConceptSchemasAndMigrationNames: both directions, nested paths, explicit JSON names |
Split and combine / EventMigrationPropertyBuilder.Split,Combine | Implemented: zero-based Split, ordered Combine | TestMigrationRegistrationMatchesCSharpWire, TestKernelHistoricalEvolutionAndSubscribedGenerations: forward split and reverse join round-trip persisted events |
Defaults and explicit empty direction / EventMigrationPropertyBuilder.DefaultValue, EventMigrationBuilder | Implemented: $defaultValue; mandatory callbacks may be empty | TestMigrationRegistrationMatchesCSharpWire, TestMigrationRequiresBothDirectionsAndValidLiterals, TestMigrationRecursivePropertiesAndCollectionRejection, kernel evolution test: missing target gets default, empty direction emits {}. Default semantics are executed by the kernel, not reimplemented locally |
Shared numeric/property value maps / EventValueMapBuilder, ValueMapBuilder | Implemented: ValueMapBuilder.For, ordered ValueMapping pairs | TestMigrationRegistrationMatchesCSharpWire, kernel evolution test: ordinary unregistered named integers are numeric JSON, one declaration emits both directions; declared enum codec endpoints are explicitly unsupported; historical appends after upgrading preserve unlisted values through the kernel |
Migration expression order / EventMigrationBuilder:45–49, EventMigrationPropertyBuilder; kernel EventTypeMigrations:203–205 | Implemented: compiled JSON preserves declaration order; repeated targets keep their first position and last expression | TestMigrationPreservesExpressionInsertionOrder, order-sensitive TestMigrationRegistrationMatchesCSharpWire: overlapping parent/child rename and default writes in both directions, repeated targets, shared maps before directional operations |
Lossy inverse and directional override / EventValueMapBuilder:43–66, EventTypeMigration:80–105 | Implemented: first pair for each inverse target; directional operations win | TestMigrationRegistrationMatchesCSharpWire, TestDirectionalMigrationOverrideAndFrozenInputs; defaults/maps/builders and returned definitions cannot mutate frozen clients |
Paths and schema concepts / MigrationPropertyNames:34–62 | Implemented for object paths: Property[T] uses Go fields resolved through the shared serialization plan | TestHistoricalConceptSchemasAndMigrationNames, TestMigrationRegistrationMatchesCSharpWire, TestMigrationRecursivePropertiesAndCollectionRejection: JSON tags, policy, recursive paths, UUID concept nullable schemas. C# expression trees become typed Go field strings validated at construction; collection traversal/literal-dot names fail explicitly |
Typed history / EventSerializer, AppendedEventConverters | Implemented: events.Decode[T], Appended.Decode, Descriptor.Unmarshal | TestHistoricalTypedDecoding, TestPlanDecodeUsesGenerationFieldNamesAndConcepts, kernel evolution test: historical/exact and kernel-migrated representations, concept codecs, atomic decoder failure, raw fallback without fabricated migrations |
Historical reactor/reducer handler expansion / EventTypeExtensions:61–76 | Implemented, correcting C# C15/C48 omission | TestHistoricalConcreteHandlersKeepTheirOwnExpansion: generation-only concrete reactor and reducer handlers retain themselves; subscriptions contain their selected generation, not every catalog alias; raw fallback preserves actual context |
Multiple generations in one observer / Reactors:717–726 | Go-specific: require one generation per event ID across live/replay methods and interface families | TestObserverRejectsMixedGenerationBindings: C# selects its first subscription for an ID; Go rejects ambiguous cross-generation bindings before activation. Use separate observers, as in the kernel evolution test |
Kernel historical backfill and observer delivery / Reactors:717–726, Reducers, kernel migration engine | Implemented against 19.32.3-development | TestKernelHistoricalEvolutionAndSubscribedGenerations: register/append generation one, introduce generation two plus migration, append generation two, read both representations, current and historical observers receive subscribed shapes and generation context after historical backfill. Registration readiness is not a backfill barrier; starting earlier can legitimately receive raw fallback content |
Protected generations / kernel Core/EventSequences/Migrations, Core/Events/EventCompliance.cs at v19.32.3 | Partial: migrations of PII/encrypted events register and transform plaintext (Chronicle#4456, fixed in 19.32.2); only the current representation is released | TestKernelProtectedEventGenerationMigrationProfile: historical upcast, current append, projection replay and erasure are correct; GenerationalContent for other generations stays ciphertext on reads and historical-generation reactor delivery (Chronicle#4623). No SDK guard yet (#90); documented in the evolution guide |
Migrations are immutable declaration output, not local payload callbacks. No assembly scanning or separate client-side migration engine is introduced. The broader enum schema/name-read conventions remain tracked by #19; this slice maps numeric enum values and does not claim that remaining serialization surface.
Deliberate differences and migration guidance
Section titled “Deliberate differences and migration guidance”- TLS validates by default. C#‘s local profile skips validation. Add
WithDevelopmentDefaultsonly for development; configure real roots/client certificates withWithTLSfor production. Explicit validating TLS wins over security opt-outs. - Root registration is explicit and snapshotted. Use
RegisterEvent[T],RegisterReadModel[T]andRegistry.AddConstraintbeforeNewClient. Model-bound projection tags and typed type-level options preserve attribute-driven declarations alongside the fluent builder; only root admission replaces assembly scanning. No globalinitregistry.WithRegistryForStorereplaces, rather than merges with, the default catalog. - Context replaces ambient state. Pass
metadata.WithCorrelation,WithIdentityandWithCausation; the client does not invent application/program audit facts. Namespace selection remains explicit and does not grant tenant access. - Errors describe write ambiguity. Domain rejection is a result; operation failures remain errors. Unknown outcomes are never represented as known rejection.
Errexposes both constraint and concurrency categories; no transparent append retry is added. - Unsupported options fail. API keys, plaintext and URI certificate/password formats return
ErrUnsupported, rather than silently skipping security or routing behavior. UseWithTLSfor PEM material. - Serialization has a deliberately bounded schema contract. Fundamentals
Concept[T]and standard codecs replaceConceptAs<T>; plain named primitives remain supported. Explicit JSON tags are recommended. Unsupported custom/protected shapes fail registration; no PII/encryption tag can silently send plaintext. Empty exact type IDs and IDs containing commas fail validation. Scalar/array integers use kernel formats; unsigned values aboveMaxInt64fail before dispatch because the kernel MongoDB append path parses bare JSON integers as signed BSON integers (Chronicle#4588). Use strings for that range. Dictionary integers outside -2^53 through 2^53 fail before dispatch, including nested values, because the kernel converts dictionary numbers throughdouble. Use typed struct properties or strings for wider values. Nullable pointer schemas preserve absence;omitzerofollows GoIsZeromethods, but omitted non-nullable fields may materialize as kernel defaults. - Protected concurrency cannot silently downgrade. No-match uses both wire fields. A confirmed commit without the requested check returns its committed result plus
ErrUnsupported: do not retry. A single append rejects scopes naming another source;AppendBatchsupports independently labeled checks. Duplicate labels and source/label mismatches fail locally. Batch check flags mean every scope was checked, so mixed checked/unchecked batches may legitimately report false; the wire carries no per-scope success flags. - Resource ownership is explicit. Constructors are lazy;
Dialperforms preflight. Close cancels/joins owned work but never closes borrowed channels/token sources.EventSequencereturns a construction error for invalid IDs rather than publishing an invalid handle. Scalar options are last-wins; conflicting auth modes are errors. - Generation validation matches C# defaults. Registrations include every concrete schema in
Generationsand default to disabling schema/migration validation, allowing generations above one without migrations. UseWithEventTypeGenerationValidation(true)to protect existing generation schemas;NewClientthen requires the complete adjacent migration chain. Disabled validation permits overwriting history’s schema. Shared C#/Go IDs require compatible generated schemas when validation is enabled; identical payload names alone are insufficient. - The default namespace is
Default. This corrects the earlier lowercasedefaultvalue to match C# and the kernel. MongoDB rejects namespaces differing only by case; callers must use the kernel spelling or explicitly select another namespace. - Recovery is supervised, not application replay. Slice 3 replaces slice 1’s explicit-connect-after-loss behavior with automatic generation replacement, following
Connections/ChronicleConnectionandConnectionWatchdogat the reference revision above. Old workers join before replacement. Unlike C#‘s broad gRPC retry policy and permissive compatibility fallback, Go fails compatibility/authentication closed and never retries an ambiguous append. Terminal failures require an explicitConnect; transient reconnect runs with time-bounded attempts and capped jittered backoff until shutdown. - Registration outcomes describe only implemented stages. Store-wide definitions share a barrier; namespace readiness stays separate. A batch acknowledgement is not a per-artifact verdict. Transient registration failures receive bounded retries and delayed background retry; deterministic validation/authorization failures require an explicit retry rather than endlessly repeating. Go adds a per-attempt budget and a 64-endpoint discovery/probe bound. For runtime projection additions,
Publishedrecords retained local admission separately from stage acknowledgements; other artifact families and replacement remain unsupported. - Shutdown is context-bounded without breaking
Close().Shutdown(ctx)drains admitted RPCs, then cancels and joins;CloseContext(ctx)immediately cancels and bounds the join. A timeout reports incomplete cleanup, which continues until cooperative callbacks return. The unchangedClose() errorremains an unbounded cancellation/join convenience. Borrowed channels retain caller ownership. - Read filters describe kernel history, not exact routes. Empty/default sentinel dimensions do not narrow. Event types match IDs across generations; normalized history filters sort/deduplicate IDs and use generation one. Raw JSON plus generation metadata replaces automatic CLR deserialization. Read results are ordered and malformed/duplicate positions fail; these finite queries do not imply a multi-query snapshot or revision/redaction protection.
- Batch scope enrollment is explicit. A unique label matching a source replaces that source’s default first-entry-route scope. Independent labels need no corresponding event. Like C#
EventsWithConcurrencyScopes, duplicate labels fail, including identical duplicates. Unlike that C# wrapper’s restriction on unresolved independent scopes, Go can explicitlyResolvean independent filter before the atomic append. An unchecked empty batch is rejected locally. Eventless protected checks work with 19.32.3; an older kernel’s event-required validation becomesErrUnsupported, not a no-op success. - Persisted timestamp precision is kernel-limited. The Go wire keeps 100 ns precision, but the 19.32.3-development MongoDB kernel returns occurrence/causation dates truncated to milliseconds.
TestKernelOrderedAtomicBatchAndHistoryrecords this server behavior;TestReadPreservesEnvelopeMetadataAndGenerationsproves conversion retains all precision actually returned. No client-side reconstruction or extra persistence path is invented.
Batch/read C# references above use revision 2e31b0dfba489159b3db323238f16d0f277056b4; kernel behavior is exercised against the pinned 19.32.3-development image. The runnable batch example also uses the public production APIs.
Naming, shared scalars and correlation
Section titled “Naming, shared scalars and correlation”Naming references: Chronicle 2e31b0dfba489159b3db323238f16d0f277056b4,
Source/Clients/DotNET/ChronicleClient.cs:119–128,360–381 and
Schemas/JsonSchemaGenerator.cs:62–93; Fundamentals
d2accc4a79b6bcf2708213c97093ab5ba6c06381,
Source/DotNET/Fundamentals/Serialization/{DefaultNamingPolicy,CamelCaseNamingPolicy,AcronymFriendlyJsonCamelCaseNamingPolicy}.cs,
Strings/StringExtensions.cs and Fundamentals.Specs/Strings/for_ToCamelCase/when_converting.cs.
Shared Go dependency: Fundamentals.Go v0.1.0,
Go 1.26; no dependency-injection integration is introduced.
| Surface | Status and Go behavior | Evidence / boundary |
|---|---|---|
| Default and explicit property naming | Implemented: WithNamingPolicy, serialization.PreservePropertyNames (default), CamelCase; tags win, all schemas/payloads and paths use immutable plan metadata | TestSchemaAndSerializationShareNames, TestFundamentalsCamelCaseGolden, TestNamingPoliciesCompileOneFieldPlan, TestClientNamingFreezesEveryArtifactAndPreservesRegistry, TestFluentNamingRebindsOnlyPlanPaths, TestClientNamingRejectsCollisionsBeforeIO, TestKernelFundamentalsScalarsAndDefaultNaming. C# fixture port excludes null/unpaired UTF-16 strings with no valid Go UTF-8 equivalent; supplementary letters remain unchanged |
Go read-model ID ↔ C# Id | Go-specific spelling translation with kernel-compatible behavior: an untagged root Go field named exactly ID uses C#‘s equivalent Id, serialized as Id under PreservePropertyNames and id under CamelCase or LegacyGoCamelCase. The read-model plan supplies schemas, JSON and all property paths; collisions fail registration. Events, nested fields, other initialisms and fields with any explicit json tag keep their policy names | TestReadModelPlanTranslatesOnlyUntaggedRootID, TestReadModelPlanHonorsEveryExplicitJSONTag, TestReadModelPlanRejectsTranslatedRootIDCollisions, TestReadModelDescriptorRejectsRootIDCollision, TestReadModelIDPathsRebindFromPlan, TestClientReadModelIDTranslationRebindsProjectionPaths, TestKernelProjectionReadsUntaggedIDWithDefaultNaming. At the Chronicle revision above, Storage.MongoDB/ExpandoObjectConverter.cs:62–77 only maps _id back to schema-declared id/Id; Infrastructure/Json/ExpandoObjectConverter.cs:26–76 otherwise drops it from the response. Use id/Id for explicitly tagged root identities that must round-trip through this kernel |
| Read-model root identity | Implemented: root chronicle:"key" or case-insensitive Go ID field metadata supplies the serialized name; reads normalize _id/case variants only when that name is absent, and protected releases resolve the same subject fallback | TestReadModelNormalizesRootIDAlias, TestReadModelDeclaredIDIsNotOverwritten, TestReadModelNormalizesDeclaredKeyProperty, TestReadModelReleaseUntaggedIDSubject, TestRootIDNamingUsesBoundPlan. Nested keys and other kernel metadata remain unchanged; a read never invents an ID from the requested key |
| Earlier Go names | Go-specific: serialization.LegacyGoCamelCase preserves the original Go algorithm (URLValue → urlValue, ID → id) | TestNamingPoliciesCompileOneFieldPlan, ExampleNamingPolicy; compatibility only, not C# acronym-friendly camelCase |
| Shared scalars and concepts | Implemented for concepts.UUID, DateOnly, TimeOnly, TimeSpan and valid Concept[T]; Underlying supplies schema shape, declared codecs plus CheckJSON supply validated JSON | TestFundamentalsEventRegistrationSchemaAndJSON, TestInvalidConceptRegistrationPreservesTypedError, TestConceptCodecsAreCheckedBeforeDispatch, TestConceptIntegerRangesUseWireRepresentation, TestKernelFundamentalsScalarsAndDefaultNaming; Chronicle numeric limits still apply, google UUID shapes retained, concept map keys unsupported |
| Shared correlation | Implemented: Chronicle metadata delegates to Fundamentals correlation.WithID / FromContext with lossless RFC bytes | TestFundamentalsCorrelationContextRoundTrip, TestFundamentalsContextReachesAppendCorrelationID, TestKernelFundamentalsScalarsAndDefaultNaming; broader Chronicle parser/codecs and BCL Guid adapter remain unchanged |
| Projection conversion | Implemented for explicit mapping representation compatibility; AutoMap performs no client-side validation, matching C#, and defers conversion to the kernel | TestProjectionAcceptsKernelScalarAndObjectConversions, TestProjectionRejectsIncompatibleExplicitConversions, TestProjectionAcceptsCompatibleFormattedConversions, TestObjectAndCollectionCompatibilityUsesSerializedProperties, TestLiteralValidatesWrappedConceptIntegerRange, TestContextOnlySubscriptionDoesNotPrevalidateAutoMap, TestTypedConceptPathsPreserveDeclaredDomainIdentity; widths and source nullability do not independently reject mappings. String formats must match (uuid/guid are aliases), unless either format is empty. Integer → number is admitted. Objects compare shared target properties recursively by serialized name; missing source properties are allowed. Collections compare element representations recursively. Typed field/key metadata still uses declared types, not unwrapped ID equality |
Behavior change: untagged Go properties now retain their spelling, matching
C# production defaults, except that an untagged root read-model ID translates
to C#‘s Id before the policy is applied. Fundamentals CamelCase preserves
other leading acronyms: URLValue stays URLValue, while Person becomes person. Container naming
remains case-preserving pluralization, independently of property policy.
Existing stores must select LegacyGoCamelCase explicitly to keep previous Go
schemas, or retain exact persisted names with json tags. Enable
WithEventTypeGenerationValidation(true) during upgrades; never overwrite a
same-generation schema or rewrite history merely to change naming. Validation
remains disabled by default for C# parity. Use adjacent generation migrations
when changing already-persisted shapes. Author constraint,
index and projection paths against declaration names; client compilation rebinds
those paths by field identity. Full instructions: serialization.
Go projection paths consistently honor exact JSON tags. C# reflected paths in
ModelBoundProjectionBuilder.cs:600–609 and FromDefinitionExtensions.cs:26–31
do not universally honor JsonPropertyName; migrations do. This is an intentional
Go consistency improvement, not a claim of equivalence for that C# limitation.
No C# executable was run here: golden names are source-derived; round-trip
execution uses the pinned Chronicle kernel and the Go client.
Append configuration and declaration migration
Section titled “Append configuration and declaration migration”Register the subject policy once alongside the event schema:
registered, err := chronicle.RegisterEvent[CustomerChanged](registry, events.WithSubjectResolver(func(event CustomerChanged) (events.Subject, bool) { return events.Subject(event.CustomerID), event.CustomerID != "" }))CustomerChanged is the application’s named event struct; the callback must match
that exact declaration type. An append-level WithSubject (or Entry.Subject)
wins without invoking the callback. Missing subjects fall back to the source ID.
Add chronicle.WithCheckFirstAppendIntoAScope(true) to client construction to
protect first writes across all sequence handles, batches and unit-of-work commits.
It stays off by default, matching C# ConcurrencyOptions. Explicit NoCheck
remains unchecked. A competing writer after an empty-tail read becomes a known
concurrency rejection rather than an unchecked success.
WithDefaultConcurrencyStrategy preserves C#‘s extension point without dependency
injection: the callback receives the sequence and default source/route filter.
Return an explicit expectation after a sequence.Tail(ctx, eventsequences.TailFilter(filter)) read, or return
Resolve to have the SDK perform its existing matching-tail read. Explicit
expectations do not trigger an extra read. Batch defaults invoke the strategy once
per source using its first entry’s route; units of work defer it to commit, never
staging. No retry or extra kernel round-trip is required by this translation.
Call sequence.ResolveScope(ctx, filter) to apply this same policy to selected
concurrency dimensions before staging. Resolve each target independently, then
label the returned scopes for Stage/AppendBatch. This adds no route defaults.
Resolved scopes never reread their tails at dispatch, even when an empty tail is
unchecked under the default first-append policy; that unchecked result retains
its filter but is distinct from an unresolved Resolve expectation.
Operation metadata is available through the new *WithMetadata append methods,
not by changing the original public result structs. Operation().EventTypes()
retains generations on success, rejection and unknown outcomes; Result().Target
still describes only confirmed committed work. Local failures include registered
input types only, because an unregistered value has no persisted identity. Existing
callers can keep their original methods and unkeyed literals unchanged.
Connection API and default comparison
Section titled “Connection API and default comparison”This slice compares DotNET/ChronicleClient.cs, ChronicleOptions.cs, EventStore.cs, Registrations/{RegistrationRetryOptions,RegistrationBackoff,SingleFlightRegistration}.cs, and the connection sources in the table above at revision 2e31b0dfba489159b3db323238f16d0f277056b4. Go spellings avoid package stuttering and Get prefixes; the underlying concepts remain client, event store, namespace, connection and registration.
| Concern | C# counterpart/default | Go behavior and deliberate difference |
|---|---|---|
| Client construction and stores | ChronicleClient, GetEventStore, GetEventStores | NewClient/Dial, EventStore, EventStores; explicit contexts and construction errors, frozen options rather than mutable Options |
| Endpoint/authentication | localhost:35000, development client credentials | Same endpoint and credentials; validated TLS is the safety difference described above |
| Connection budget | ConnectTimeout = 5 seconds; initial-startup and post-failure wait behavior differ | WithConnectTimeout, five seconds for each complete discovery/auth/handshake attempt; first attempt returns its actual failure rather than reporting an unready connection as connected. Subsequent readiness waits use the caller’s deadline |
| Heartbeats and transport pings | Fixed five-second staleness, evaluated on a one-second watchdog tick; HTTP/2 60s/30s; SkipKeepAlive = false | Same default thresholds/pings; timer-driven staleness is configurable with WithKeepAliveTimeout, without the extra polling tick. WithSkipKeepAlive() omits the entire application session, not HTTP/2 pings |
| Reconnect | Watchdog retries with exponential 1–30s delays, awaited lifecycle disconnect/connect notifications | Same automatic replacement and re-registration with application sessions; Go adds half-to-full downward jitter and stops on terminal authentication/compatibility errors (custom selection failures are always transient). Async application hooks never delay recovery: per-event concurrent hooks are joined in transition order with bounded coalescing. Delivered Connected pairs on Close/Shutdown too; panics cannot reject a connection. Skipped sessions have empty ConnectionID and no heartbeat-driven replacement |
| Calls during recovery | Service accessor attempts connection, subject to failure backoff | Existing handles fail before dispatch while disconnected, rather than making every RPC wait for reconnection. Use Ready(ctx) when waiting is appropriate; replay barriers block required operations |
| Balancing | Factory defaults to least-connections; explicit ChronicleOptions.LoadBalancerStrategy overrides the URI and is disposed when disposable | Same algorithms and URI names; at most 64 endpoints. WithLoadBalancer borrows a last-wins strategy, called once per attempt including single endpoints. Go rejects URI-strategy/borrowed-channel conflicts, never closes custom strategies, and retries selection failures with normal backoff. Candidate slices are copied; invalid selections fail before OAuth or dialing |
| OAuth | 60s refresh margin, 5s failure throttle, 3600s fallback lifetime, forced refresh on rejection | Same cache/refresh constants; five-second HTTP request cap and a fresh endpoint-bound OAuth cache per generation. This intentionally avoids carrying a token across server restarts; no automatic unary replay |
| Registration within a pass | Five attempts; initial 2s/max 30s half-to-full jitter, retries the full artifact pass | Same attempt count and delay defaults; 30s per-attempt cap. Only transient failures retry automatically; acknowledged store-level stages are shared rather than resent per namespace |
| Registration between passes | Per-store background exponential 2–30s delay plus single-flight failed-pass backoff (1–60s) | One generation-owned worker scans pending registrations at MaximumDelay (30s default), reducing background fan-out. An explicit subsequent wait may start a fresh pass immediately; it does not add C#‘s second backoff layer. Applications must not hot-loop explicit waits; use the owned background retry or a caller deadline |
| Readiness and outcomes | Registration, WaitForRegistration, RegisterAll, Lifecycle.IsConnected | WaitForRegistration(ctx) returns a generation/pass outcome; Ready(ctx) separates connection health from artifact readiness. The pass ensures store/namespace, registers event types/read models, constraints and projections, starts reactors/reducers, registers external subscriptions, starts read-model reactors, then seeds. TestSeedingIsLastAfterEveryObserverRegistrationSend and TestExternalSubscriptionsFollowObserversPrecedeSeedingAndReplayPerGeneration cover ordering/failure barriers. Outcomes report registration work and failures, not an attachment receipt: duplex observer readiness means sent; a watch Subscribed marker and seed command acknowledgement have their own narrower meanings. Runtime RegisterAll reload remains unsupported; bounded runtime projection additions use the existing registration barrier |
| Failed store creation | Evicts a failed lazy task from the name/namespace cache | Retains the immutable handle but never caches failure as successful readiness; later passes and reconnect can recover it. No failed task permanently poisons admission |
| Disposal | Dispose() cancels owned connection lifetime and disposes the supplied connection | Close() cancels/joins only owned resources; CloseContext bounds joining and Shutdown adds deadline-bounded RPC draining. Borrowed channels are never disposed |
| Cache eviction | IChronicleClient.cs:42–53, ChronicleClient.cs:289–300 at 2e31b0dfba489159b3db323238f16d0f277056b4: void EvictEventStores() removes completed stores’ connection handlers and clears all name/namespace entries | Partial lifecycle parity: Client.EvictEventStores() error clears cached membership only. Go returns existing prepared/closed state errors and shares one retained coordinate owner across distinct facades; no selector, context, cleanup or revocation. TestEviction*, ExampleClient_EvictEventStores cover admission races/cancellation, captured versus later Ready/replay, shared observers/feeds/tombstones, immutable readers/stale guards, uncertainty, identity readiness and borrowed scopes. TestKernelCacheEvictionRetainsWatchAndExplicitReconnect covers old-watch delivery, distinct reacquisition and explicit registration/rewatch after connection loss; not uninterrupted delivery, memory reclamation or authorization proof |
| Connection hooks and custom selection | ConnectionLifecycle, ILoadBalancerStrategy at the pinned revision above | #62 hooks and balancing are implemented with the deliberate Go ownership/dispatch differences above. See connection hooks and custom load balancing. Sink defaults are recorded below and captured process/software audit metadata in the enrichment section |
Client transport options
Section titled “Client transport options”Authority: DotNET/ChronicleOptions.cs and
Connections/ChronicleConnection.cs:{ConnectInternal,CreateGrpcChannel} at C#
2e31b0dfba489159b3db323238f16d0f277056b4. The actual application protocol is
server-streaming Connect plus unary ConnectionKeepAlive acknowledgments, not
bidirectional streaming. All options below are client-wide, validated and frozen
at NewClient; they cannot be changed on a store or namespace handle.
| C# option/default | Go option and status | Executable evidence / intentional difference |
|---|---|---|
MaxSendMessageSize = 104857600 bytes | Implemented, Go-specific bounded substitution: WithMaxSendMessageSize(bytes int), last wins | TestMessageSizeOptionsValidateFinalValues, TestMessageBoundsExactProtobufUnaryAndStreamingOwnedAndBorrowed, TestKernelClientTransportOptions, ExampleWithMaxSendMessageSize; accepts 1..2147483647, rejects zero/negative/overflow. No nullable/unlimited mode |
MaxReceiveMessageSize = 104857600 bytes | Implemented, Go-specific bounded substitution: WithMaxReceiveMessageSize(bytes int), last wins | Same exact protobuf and kernel tests; TestOmittedMessageBoundsPreserveBorrowedOwnerDefaults, ExampleWithMaxReceiveMessageSize; accepts 1..2147483647. Owned replacement generations retain both frozen bounds; borrowed channels retain owner defaults unless explicitly overridden per RPC, for unary and streaming calls alike. Neither direction implicitly overrides the other |
SkipKeepAlive = false | Implemented, Go-specific protected readiness: WithSkipKeepAlive() | TestSkipKeepAliveProbesRegistersAndSurvivesSilence, TestSkipKeepAliveProbeFailsClosedEvenWithoutCompatibility, TestSkipKeepAliveCompatibilityStillTerminatesStartup, TestSkipKeepAliveProbeCancellationAndCloseJoin, TestSkipKeepAliveTokenFailureDoesNotDispatchProbe, TestKernelClientTransportOptions, ExampleWithSkipKeepAlive; skips Connect/first acknowledgment/receiver/watchdog entirely; HTTP/2 60s/30s settings remain unchanged |
| Fixed application staleness threshold; optional skipped session | Go-specific: positive WithKeepAliveTimeout, default five seconds | TestSkipKeepAliveRejectsExplicitTimeoutInEitherOrder, TestSkipKeepAliveRejectsConfiguredObserverRuntimesBeforeIO; an explicit timeout conflicts with SkipKeepAlive in either order. Configured reactors, reducers (even passive) and read-model reactors in default or per-store registries also fail before network I/O: kernel subscriptions require a registered logical session |
Message bounds cover serialized protobuf messages, not just event JSON. Small
limits can therefore reject compatibility, the mandatory authenticated readiness
probe, registration or observer requests as well as appends and reads. Both Invoke and NewStream apply explicit settings
on the shared generation transport; no borrowed channel is mutated or closed.
An explicit value equal to the SDK default still overrides a borrowed owner’s
limit. Omitted settings preserve that owner’s limits.
Compatibility preflight remains fail-closed unless explicitly skipped. It is
anonymous on the kernel and cannot prove authenticated readiness. With a skipped
application session, startup always calls protected, read-only
GetConnectedClients, even when compatibility is disabled. Authentication,
authorization and unsupported-method failures are surfaced without fabricating
connection registration or retrying the rejected operation. The
19.32.3-development witness verifies anonymous rejection and authenticated
registration/append/read. It checks for new Go session IDs belonging to its own
process, not an empty or unchanged global client list: other fixture processes
can have active sessions and changing heartbeat timestamps. The public SDK does
not expose a skipped client’s internal connection ID; the serial process-local
check is narrower than proof by individual client ID. The transport unit tests
also verify that skipping keepalive dispatches no Connect RPC.
The kernel test pins the compatibility request’s exact protobuf send boundary
(126854 bytes), with a separate 64 KiB bounded startup receive envelope. A
128 KiB event in a unique immutable source then supplies the target event-read
receive boundary, measured from grpc-go’s uncompressed incoming payload length.
Both equality and one-byte-short are asserted through owned public SDK clients,
including successful startup and registration before the target read; oversize
reads return no partial history. The fixture cap is 512 KiB. Re-serializing a
Go-decoded response with proto.Size is not an exact wire-size witness because
the kernel’s protobuf-net serializer emits explicit defaults that Go omits.
The small compatibility response is not used as a client-wide receive cap:
GetConnectedClients can legitimately return a larger shared-session envelope.
Exact unary and streaming transport bounds remain covered by bufconn tests.
A skipped session has no application heartbeat-driven logical reconnect or
registration replay guarantee after a kernel restart. grpc-go transport
reconnection is not a replacement for that lifecycle. Startup/replacement
registration workers still belong to their generation; cancellation, Close,
CloseContext and Shutdown join client-owned work as usual. Prefer skipped
sessions for short-lived one-shot clients. TLS verification, credential ownership
and the no-append-retry contract are unchanged; no idle-ping guarantee is added.
Client default sink selection
Section titled “Client default sink selection”Authority: C# 2e31b0dfba489159b3db323238f16d0f277056b4,
DotNET/ChronicleOptions.cs:DefaultSinkTypeId,
DotNET/ReadModels/ReadModels.cs:GetSinkTypeIdFor and
Kernel/Concepts/Sinks/WellKnownSinkTypes.cs. The provider identities are strings
MongoDB, SQL, InMemory and None; UUIDs identify provider configurations.
C# defaults to MongoDB, with passive read models/projections selecting None.
| Surface | Status / Go contract | Executable evidence and limits |
|---|---|---|
| Materialized client default | Implemented: WithDefaultSinkType(kind readmodels.SinkType), MongoDB omitted; MongoDB/SQL/InMemory accepted, last wins | TestDefaultSinkSelectionIsDetachedAndStoreReplacementIsNotMerged, ExampleWithDefaultSinkType; selected default and every per-store replacement resolve independently, nil remains empty. Catalog access performs no I/O |
| Model override/configuration | Go-specific existing readmodels.WithSink takes precedence, even explicit MongoDB equal to the declaration default; canonical configuration UUIDs are retained | TestExplicitSinkSurvivesNamingAndDefaultCopies, TestDefaultSinkCSharpHandDerivedWireGoldens; goldens are hand-derived from the pinned C# contract, not captured C# runtime output |
| Passive producer precedence | Implemented, with Go-specific strict conflict validation: inherited materialized defaults become None; explicit materialized selections conflict with passive producers; explicit None remains valid with passive semantics | TestDefaultSinkPassiveProducerPrecedenceAndExplicitConflicts; C# has no corresponding per-model sink option in GetSinkTypeIdFor. Existing Go explicit-sink intent is retained rather than silently ignored |
| Global None/unknown default | Partial, Go-specific restriction: rejected with ErrInvalidConfiguration at capture before preparation | TestDefaultSinkRejectsUnknownAndNoneBeforePreparation, TestCaptureInvalidSinkRunsNoSchemaFactoryOrServiceCallbacks; C# accepts global SinkTypeId.None and registers that selection for non-passive models, allowing immediate projection instead of materialized reads. This is working C# behavior, not an inert declaration. Go restricts None to passive producers; callers using the C# global option must explicitly declare passive producers in Go |
| Frozen compilation and reconnect | Implemented: detached sink resolution preserves original declaration identity, schema/generation, codec/classification and initial-state snapshots; producer plans bind the final catalog | TestDefaultSinkFactoryProducerPlansUseFinalCatalogWithoutRepreparation, TestFactoryCodecsSurviveNamingInitialStateStoreBindingAndReconnect, TestDefaultSinkCopyPreservesFrozenClassificationAndSchema, TestDefaultSinkRegistrationFrozenReconnectAndOriginalTypedHandle; shared registry preparation runs once per client epoch and reconnect bytes are stable |
| Kernel-backed materialization | Partial backend qualification on 19.32.3-development | TestKernelClientDefaultSinkMongoAndExplicitMongoOverrideSQL covers unchanged MongoDB default and a MongoDB model overriding a SQL client default. It materializes MongoDB only. SQL/InMemory registration and wire selection are not proof of live backend reads; neither backend is provisioned by this fixture |
| Decision agreement | Go-specific fail-closed check, not expanded decision capability | TestDecisionSinkDefaultRequiresActualServerProfileBeforeFolding, TestDecisionAgreementRechecksServerShapeAndKey; actual server sink type/configuration and projection active/rewindable flags must match the admitted definition before and after folding. A sink mismatch returns no token; this is not full mapping equality or live SQL qualification |
TestCaptureSinkDefaultsFreezeOptionsRegistriesCodecsAndFactoryModels covers
Capture → Prepare with SQL/InMemory defaults, explicit MongoDB and passive None,
shared and replacement registry snapshots, retained options/services, frozen
codec initial state and final catalog/producer identity. Capture validates the
sink without preparation callbacks; Prepare never rereads options or registries.
No namespace override, database dependency ownership or client driver is added. Sink selection is store-wide and does not configure kernel backend providers. This section records the default-sink slice of #62; hooks and custom balancing are covered in the connection API comparison above.
Unit-of-work migration
Section titled “Unit-of-work migration”Transaction references use C# revision 2e31b0dfba489159b3db323238f16d0f277056b4, Source/Clients/DotNET/Transactions/{IUnitOfWork,IUnitOfWorkManager,UnitOfWork,UnitOfWorkManager,UnitOfWorkLifecyclePolicy}.cs. The Arc consumer reference is Source/DotNET/Chronicle/Commands/TransactionalCommandScope.cs: completion is distinct from confirmed commitment, and early commitment completes shared work.
- Separate participant and owner. Replace ambient
IUnitOfWorkManager.Begin/Current/SetCurrent/TryGetForwithtransactions.Beginand explicit participant context accessors. Go has noAsyncLocal; there is no global correlation registry.UnitOfWork.StagetranslatesAddEvents. Only the separately retainedOwnerexposesCommit/Rollback; this capability split also keeps ordinary Arc handlers from completing outer work. The store, namespace, sequence, correlation and actor are fixed at begin. Missing correlation is generated once; actor changes fail because one atomic request cannot represent several actors. - Strict lifecycle and global order. Go uses C#‘s strict-policy intent, not its legacy compatibility mode. All enrollment retains A1/B1/A2 order instead of legacy single-event source grouping. Events serialize at
Stage, not commit, so later mutations cannot change the transaction. Concurrent stage calls enroll atomically; no serialization callback or RPC runs under the state lock. Repeated equal explicit scopes join, while conflicting scopes fail without mutation. An explicitResolveis not C#‘s absentNotSet: it includes a filter and cannot overwrite an enrolled expectation. Use omission for the default strategy. - Inspect final disposition, not completion alone.
Commitreturns the existing GoBatchResultand operation error;Result, C#-named diagnostic getters,IsSuccessandTryGetLastCommittedEventSequenceNumberpreserve the query workflow.GetEventsreturns defensive JSON snapshots rather than mutable CLR event objects. Transport loss and errors-only kernel results remain outcome-unknown, not a successful default result or known rejection. All attempts are terminal, including local cancellation; retry requires a deliberate new unit and fresh decision, and is never safe merely because an ambiguous owner completed. The retained result survives a repeated commit error. Empty completion is successful without persistence; scope-only completion still validates through the kernel. - Explicit rollback instead of disposal. Defer
Owner.Rollbackfor pending cleanup. Unlike C#‘s repeated rollback exceptions, terminal rollback is a no-op so ordinary Go defer does not mask a successful result; it never changes a committed or ambiguous disposition. During completion it fails withErrCompleting. Rollback reportsRolledBackand is notIsSuccess, avoiding C#‘s success-shaped empty append result being mistaken for commitment. - Completion notification retains the C# name and replacement semantics.
OnCompletedreplaces the callback and runs synchronously after final state, including commit failure and rollback. Go rejects late registration withErrCompletedrather than silently retaining an uncallable callback. No manager-cleanup callback is replaceable, since lifetime belongs to the explicit owner. Callback panic propagates after final disposition is recorded.
The unit-of-work guide and executable example show the participant/owner workflow. Prepared batches are a lower-level serialization seam, not another owner: they do not provide once-only completion. Admitted decision reads supply opaque optimistic tokens; ordinary concurrency scopes must not be presented as decision tokens.
Decision-read migration
Section titled “Decision-read migration”Decision references are Source/Clients/DotNET/ReadModels/{DecisionReads,DecisionReadAdmission,DecisionReadScopes}.cs and Transactions/{DecisionReadCommitOwner,UnitOfWork}.cs at 2e31b0dfba489159b3db323238f16d0f277056b4. Kernel semantics come from ae5e00a8abaa688138b2c2f689e2b4659cccb4fd (19.29.4), especially Kernel/Core/EventSequences/Concurrency/ConcurrencyValidator.cs and Kernel/Core/Sequences/AppendManyForEventSourcesValidator.cs.
| Contract | Go surface and status | Executable evidence and differences |
|---|---|---|
| Local admission and canonical keys | Implemented: DecisionsFor[T](service, model).Admit() | TestDecisionAdmissionMatrix, TestDecisionKeySchemaAndCanonicalKeys; actual schema key is id then Id, not an arbitrary Go key tag. Frozen contract AST and event catalog avoid a second projection compiler |
| Selected server agreement | Implemented with safety additions | TestDecisionAgreementRechecksServerShapeAndKey, TestDecisionServerGenerationAndUnreadableSchemaRefuseBeforeAndAfterFold, TestDecisionServerProtectionIsNotAnAgreementCriterion, TestDecisionAgreementAllowsPlainNonKeySchemaDifferencesAtSelectedGeneration; compare producer, model generation, sequence and dependency ID set, not full mapping equality. Re-admit server parent/All keys, key schema before and after folding. Server-side protection metadata is not compared, as in C#: the kernel releases session folds with original subjects (Chronicle#4561). No persistent agreement cache |
| Classified-model decisions | Implemented like C#: classified models are admitted; the kernel (19.32.2 or later) releases the session fold with each value’s original subject and the SDK validates it without a second release. DecisionProtectedModel and DecisionProtectionMetadata are deprecated and no longer produced | TestClassifiedDecisionsAreAdmittedWithoutSecondRelease (PII and every encryption scope; property, type and provider declarations), TestDecisionCodecsRunAfterCleanupWithoutCountingShutdownWork, TestDecisionErrorCallbacksRemainInsideRecovery, TestKernelDecisionReadsReleaseClassifiedModelsWithEventSubjects (default Id and lowercase id, key differs from subject, before and after erasure, guarded commit). Earlier kernels released with the requested key (Chronicle#4561); decision reads already require the verified 19.32.3 profile |
| Fresh-session reads and presence | Implemented: Get enrolls into the existing participant; GetDetached does not | TestDecisionRetriesFreshSessionsAndUsesUnfilteredBoundary, TestDecisionAttemptExhaustionAndPresence, TestDecisionGetRequiresAndEnrollsExistingParticipant; retain the pre-fold unfiltered log boundary separately from the filtered probe and reported progress |
| Cleanup and retry exhaustion | Go-specific fail closed | TestDecisionFailuresAwaitCleanupAndReturnNoToken, TestKernelDecisionCleanupFailureAfterRealFoldIssuesNoToken; await bounded metadata-preserving detached cleanup after every session. Three incomplete/ahead attempts return a refusal; the C# third-ahead boundary-5/fold-6 result is explicitly refused instead of returning a doomed guard |
| Decision error diagnostics | Go-specific payload-free public messages with inspectable causes | TestDecisionReadErrorsDoNotDiscloseTransportDiagnostics, TestCodecErrorsPreserveCausesWithoutDisclosingMessages; fold, cleanup, codec and joined messages omit sensitive text while retaining cancellation, unsupported, protocol and returned codec causes through errors.Is/As |
| Application codec lifetime | Go-specific no counted decision lease across codecs | TestDecisionCodecsRunAfterCleanupWithoutCountingShutdownWork; finish fold, protocol checks, cleanup and agreement before typed decoding; classified decision models refuse before lease acquisition. Synchronous codec-triggered close completes; caller cancellation, generation loss or epoch change returns no token. Typed-decoding panics return payload-free DecisionCodecPanicError, discard the panic value and issue no model/token |
| Opaque tokens and ownership | Go-specific permanent first-enrollment binding; existing participant/owner split retained | TestDecisionEnrollmentValidatesTargetEpochOwnerAndState, TestDecisionTokenCopiesHaveOneConcurrentFirstOwner, TestFrozenCatalogAndIssuedTokenOwnTheirInputs; copies cannot transfer to a second owner after rollback, and failed enrollment does not redeem a token. No public proof constructor or owner-from-context API |
| Merge and atomic completion | Implemented: earliest boundary, union IDs, one frozen ordered batch | TestDecisionScopesMergeEarliestBoundaryAndUnionIDs, TestDecisionCompletionPreservesSnapshotGlobalOrderAndAudit, TestKernelDecisionCompetingWriteRejectsEntireMultisourceBatch; colliding explicit labels fail in either order. Protected completion sends no automatic effect-source defaults and rejects unchecked/unresolved explicit mixtures (TestDecisionScopeCollisionsAndUncheckedMixturesAreAtomicBothOrders) |
| Owner append attribution | Implemented with SDK-only exact-snapshot authority | TestGuardedOwnerCommitBypassesExternalOriginResolver, TestGuardedPreparedAppendResolvesOriginBeforeDecisionLease, TestUnitOriginCapabilityCannotAuthorizeOtherAppendsFromCallbackContext, TestKernelDecisionOwnerOriginBypassesResolver; guarded plain/named/eventless completion bypasses external resolution and retains unit attribution. Ordinary guarded appends resolve before acquiring a decision lease; callbacks cannot authorize an unrelated append |
| Eventless validation | Implemented for the verified pinned profile | TestKernelDecisionFirstEventAbsenceAndEventlessCompletion; plain batch RPC, no positions, dummy events or separate validation/write. Named-tag eventless overload remains excluded |
| Capability, epoch and generation boundaries | Go-specific conservative refusal | TestDecisionCapabilitiesRequireVerifiedVersionAndPreflight, TestDecisionCommitRechecksCapabilitiesAndEpochBeforeDispatch, TestDecisionEpochIsCheckedAfterAuthorizationBeforeDispatch, TestDecisionLeaseCannotSpliceRPCsAcrossGenerationLoss; compatibility success alone and skipped preflight cannot enable guards |
| Unsupported and ambiguous completion | Implemented using existing terminal owner outcomes | TestDecisionCompletionPreservesUnsupportedUnknownAndRejection; false check flags never mean success. Confirmed writes with unsupported-check errors remain committed; lost/malformed acknowledgements remain unknown; no owner retry |
The pinned protocol accepts client-supplied concurrency scopes; it supplies no signed read proof, definition revision binding or history revision binding. Opaque tokens prevent unsupported SDK enrollment, not malicious direct RPCs. Definition/history races, including ABA replacement and in-place revision/redaction, cannot be eliminated by client rechecks or hashes. Detached tokens do not detect every later server definition change. Applications requiring that stronger guarantee need upstream protocol support. chronicletest deliberately cannot issue tokens. Runtime projection additions invalidate fixed expected epochs for all older issued and enrolled guards across the store before publication. Old decision readers cannot issue fresh evidence; acquire a new reader. This does not fence external server definition/history changes.
The decision guide and typed example cover admitted reads, explicit detached enrollment, checked scopes and the optimistic-versus-authenticated boundary.
Constraint API translation
Section titled “Constraint API translation”Constraint references use DotNET/Events/Constraints and its matching DotNET.Specs/Events/Constraints at the baseline revision above. Request fixtures derive from ConstraintBuilder, UniqueConstraintBuilder, ConstraintConverters, and the converter/sequence/remover specs, rather than from a Go-generated expectation. Kernel behavior is exercised against 19.32.3-development. The constraint example uses the same production registry and append APIs.
- Choose model-bound declarations or explicit definitions. Field
uniquetags,events.WithUniqueand remover options compile through the same builders asUniqueValues(name).On(descriptor, paths...). Explicit paths are exact serialized JSON names validated against the descriptor; tags use the shared field plan.Registry.ConfigureDeclaredConstraintexplicitly composes a declared constraint, while duplicateAddConstraintcalls fail rather than silently merge.TestCompositeDeclarationImprovementMatchesExplicitBuilderGoldenandTestDeclaredConstraintsRequireExplicitCompositioncover this boundary. Same-event, same-name fields form one ordered composite in Go; the pinned C# provider instead throws on its repeatedOncall. Use stable explicit names for shared definitions. Empty property sets, blank names/sequence IDs and foreign descriptors fail early (TestMalformedConstraintDefinitionsFailBeforeRegistration,TestConstraintRegistryRejectsInvalidOrForeignDeclarations). - Existing kind constants stay compatible.
constraints.Uniqueandconstraints.UniqueEventTyperemain result-kind constants; builders are namedUniqueValuesandUniqueEventTypesto avoid breaking those exported names. The event-type builder defaults to the first persisted event ID, matching C#Unique<T>(), and accepts multiple types for mutual exclusion. Use one grouped definition instead of C#‘s same-name declaration merging; duplicate names acrossAddConstraintcalls are rejected atomically. Repeated covered/removal IDs and selected sequences coalesce in first-declaration order. Scope calls apply to the one definition regardless of call order, avoiding C#‘s multi-definition builder’s order-sensitive scope capture. - Messages remain client-side. Missing/empty templates preserve the kernel text, and callbacks run once per matching returned violation as in C# append conversion. Go supplies copied details to callbacks and inserts values literally in one pass; unlike C#‘s sequential dictionary replacement, values containing
{OtherDetail}cannot become a second template expansion. This removes map-iteration-order dependence and avoids interpreting event values as formatting instructions.TestMessageDetailValuesAreLiteralfixes that deliberate edge-case difference. Use a provider for intentional complex formatting; synchronize captured mutable state. Providers run outside the RPC lease so they can use client lifecycle APIs without self-deadlocking; shutdown does not wait for caller-owned message formatting. - Registration acknowledges a batch, not each name. Constraints use the same generation-scoped store barrier as event types, after event-type and read-model registration and before projections and reactor/reducer registration sends, as in C#
EventStore.RegisterAllArtifacts. Outcomes include theconstraintsstage; the RPC returnsgoogle.protobuf.Empty, not a command envelope. Empty local constraint sets perform no RPC, matching the Go event-registration fast path; no API for deleting persistent definitions is implied. Store-specific registries replace both events and constraints. Adding constraints after client construction still requires a new client; runtime projection additions do not add constraints. - Results remain additive and kernel-owned. Append/AppendMany/AppendBatch retain their existing violation/result/error types, now resolving registered messages and exposing standard detail-key constants. No UUID dependency, new scalar type, local uniqueness algorithm or preflight read is added. Low-level
eventsequences.Newhas no registry/message provider and preserves kernel text; obtain sequences fromEventStorefor registered message resolution. Claim removal and batch ordering are server behavior, not emulated by a Go test fake.
Read-model migration
Section titled “Read-model migration”Read-model references use the C# baseline revision above, Source/Clients/DotNET/ReadModels/{IReadModels,ReadModels,ReadModelTypeExtensions,ReadModelJsonSerialization,ReadModelReleaser,ReadModelSubjectResolver}.cs and Source/Kernel/Grpc/ReadModels/ReadModels.cs. Container naming follows the Cratis.Serialization.DefaultNamingPolicy used by ChronicleClient: a case-preserving Humanizer plural of the simple type name. The vocabulary adaptation and MIT attribution are pinned in readmodels/HUMANIZER-LICENSE.
- Explicit declarations replace discovery.
RegisterReadModel[T]supplies the schema and producer association separately from projection/reducer registration. A registered model can be read without a locally known handler, unlike C#‘s handler-existence guard; this supports catalog consumers such as Arc.Go without fabricating a producer. Registrations use generation one, MongoDB, an empty configuration UUID,Clientowner,Codesource and projection observer type by default. Model definitions join the registration pass immediately after event types and replay once per store/generation, with per-namespace read barriers. Passive (NoSink) models require a nonblank producer identifier;readmodels.Passive()defers that binding to a registered reducer (TestReducerPassiveAndInactiveBinding). Materialized models may omit the producer. RepeatedWithIndexesandWithPIIcalls accumulate copied property paths in declaration order and reject duplicates within or across calls withErrInvalidConfiguration. Runtime projection additions admit a new ordinary model together with its new projection; no standalone runtimeRegisterReadModel[T]or replacement API is provided. - Identity and container names stay distinct. C# uses
Type.FullName; Go uses its import-qualified type name, not the simple event-type naming convention. For shared persisted models, useWithIdentifier("CSharp.Namespace.Model").WithContainerNamecorresponds to C#‘sReadModelNameAttribute, not a model identifier override. Generic Go instantiations require bothWithIdentifierandWithContainerNameexplicitly: reflection’s argument-qualified type names are not stable persisted identities or valid conventional container names.TestGenericReadModelRequiresExplicitNamescovers rejection and explicit registration. The shared serialization plan preserves property names by default, matching C#DefaultNamingPolicy; camelCase is opt-in, and explicit JSON tags take priority. An untagged root GoIDbecomes C#Id; nested fields and other initialisms retain their policy names.TestNamingPoliciesCompileOneFieldPlanandTestReadModelPlanTranslatesOnlyUntaggedRootIDcover these rules. Keep persisted names stable with explicit tags or a deliberate naming policy; changing policy is a schema migration, not a cosmetic rename. The golden registration request derives envelope/defaults from C# while using the Go plan’s equivalent JSON Schema, not a claim of byte-identical NJsonSchema output. - Presence and progress are separate.
Instance[T].Existsreplaces a nullable model;LastHandledreplaces C#‘s injected__lastHandledEventSequenceNumberproperty. A missing model never becomes a present zero value. Raw documents retain their metadata. Slice fields in present models normalize missing/null collections to empty; pointer-to-slice fields preserve nullable absence and maps are not normalized. The one-shot API rejects the kernel’s*replay wildcard rather than treating it as an ordinary key. Raw/typedGetAll, materialized paging, watches and admitted projection snapshots are available. Their count, presence and replay rules differ: use the collection/history reference and watch reference. Reducer snapshots remain unsupported. - Sessions have explicit cleanup contexts.
NewSessioncreates an opaque random ID, binds all coordinates and performs no I/O until Get.Close(ctx)dehydrates, reports errors and permits cleanup retry after failure; it is intentionally notio.Closer, so callers retain cancellation/deadline ownership. Unlike C#‘s unconditional event-log dehydration, Go sends the session’s actual sequence. Close sessions before the client. No cross-reconnect hydration continuity or decision-token guarantee is claimed. Reducer sessions remain unsupported, but passive reducer Get and explicitly bounded collection folds run locally through the registered fold plan. Classified projection sessions and immediate reads return kernel-released values on Chronicle 19.32.2 or later and refuse before RPC on older or unverified kernels (TestProtectedProjectionRoutesUseKernelReleaseWithoutSecondDecryption,TestProtectedProjectionRoutesRefuseWithoutProtectedReleaseKernel,TestKernelModelHistoryProtectedReleaseProfile); ordinary session coordinate/cleanup behavior is covered byTestReadModelSessionRetainsCoordinatesAndRetriesFailedCleanup. - Release fails closed. C# returns unchanged values for missing subjects or reported release failure. Go returns
ErrReleasewith no value, preserves underlying error identities and redacts public error text. Release ownership is route-specific: materialized keyed/collection results, store-owned passive folds and projection replay/history/immediate/session/decision reads (kernel 19.32.2 or later, Chronicle#4561) validate already-released data without another decrypt. Admitted MongoDB root-string PII/EncryptedNamespace windows validate server-released data without Release; classified sink identity aliases and other protection profiles refuse before RPC (see the window profile). Classified projection Watch refuses before streaming (Chronicle#4566); classified local reducer Watch refuses before queue attachment without a delivery-time erasure fence. Unclassified local notifications validate final plaintext without Release. Repeated Release is not a safety check. Explicit release accepts unreleased documents only, never already-released plaintext: raw documents preserve subject lineage, with stored__subjecttaking precedence over a configured property and the GoIDfallback. Strings and JSON numbers resolve subjects; numeric token text is preserved without floating-point conversion. Null, empty strings, objects and arrays do not resolve a subject. As in kernelReadModelsCompliance.HandleBySubject, top-level properties are grouped by their__subjectsentry, falling back to the resolved default subject. Each group is released under its own subject and the replies are merged; any group failure returns no document. Typed release uses the same path after serialization and requires the subject on the value. This intentionally adds raw-document lineage handling absent from the typed C# helper. PII and distinct confidentiality classifications share this pipeline. Namespace/global-only values need no subject; present subject-dependent values still fail without ownership. Cryptographic/erasure semantics remain kernel-owned, not a client-side cipher heuristic.
The read-model reference, ExampleRegisterReadModel and runnable example use these public APIs. Kernel registration, absence, producer-backed materialization and passive reads are tested on 19.32.3-development. Session hydration/dehydration and explicit release failures retain bufconn evidence, not a claim of complete projection/compliance integration.
Projection attributes: basic mappings
Section titled “Projection attributes: basic mappings”References in this section are C# revision 2e31b0dfba489159b3db323238f16d0f277056b4, Source/Clients/DotNET/Projections, Keys/KeyAttribute.cs and ReadModels/PassiveAttribute.cs. Golden projections/testdata/basic.json is hand-derived, not generated by running .NET: it combines ModelBound/FromDefinitionExtensions, ModelBoundProjectionBuilder and ProjectionBuilderFor output with the DotNET.Specs/Projections/ModelBound/for_ModelBoundProjectionBuilder cases for custom keys, explicit context, string/integer values, null values and field exclusions. The equivalent fluent fixture uses FromBuilder and SetBuilder. JSON property names are explicit Go-plan names, not a claim about C#‘s default naming policy.
| C# attribute / source | Go surface and status | Evidence and intentional difference |
|---|---|---|
FromEvent<E> / ModelBound/FromEventAttribute | Implemented for root subscriptions: FromEvent(handle, options...); fluent From(builder, handle, callback, options...) | TestFromEventKeysFlagsAndDefaultIdentity; event-source default, property key, parent key, constant key and override order. Typed options replace class attributes; duplicate typed subscriptions fail instead of relying on dictionary overwrite. Composite/context keys are covered in the arithmetic/node section below |
SetFrom<E> / ModelBound/SetFromAttribute | Implemented for basic field mappings: set(E,from=path), Map and MapAs | TestMapAsPreservesModelBoundCompatibility, TestMapAsRejectsIncompatibleAndIncorrectFieldTypes, TestBothFrontEndsMatchCSharpBasicGolden, TestExactSerializedPathsAndNilOptions, kernel materialization test. Omitted from uses the target’s serialized name. Paths use exact plan spelling, not C# case-insensitive CLR property lookup |
SetFromContext<E> / ModelBound/SetFromContextAttribute, FromDefinitionExtensions.AddContextPropertyMapping, PropertyValidator, SetBuilder, AllSetBuilder, Events/EventContext.cs at 2e31b0dfba489159b3db323238f16d0f277056b4 | Partial: existing scalar context/Context, plus whole Tags/tags into string slices (optionally pointers), including Every/All | TestContextCollectionFrontEndsGolden compares both Go front ends to hand-derived testdata/context-clear.json, not an actual .NET capture. TestContextCollectionEveryAndAll, TestComplexContextRefusalAndScalarOnlyKeys, TestKernelProjectionContextCollectionClear cover shared validation, scalar-only keys, conflicting payload labels, order, empty arrays, startup typed-null collection clears/repeated clears and nonempty rewrite. Explicit NoAuto/NotProjected writes remain effective. C# model-bound/All apply naming policy to context paths; per-event fluent preserves the extracted path. Go equality uses the same explicit spelling, not a claim of universal C# byte equality. Other known complex objects fail with located ErrUnsupported; unknown paths/type mismatches remain configuration errors. Context names are the kernel contract, not Go events.Context names. Remaining depth belongs to #26 |
SetValue<E> / ModelBound/SetValueAttribute | Implemented for supported scalar representations: value(E,value=JSON-scalar) and Value | Golden and kernel materialization tests cover string, integer, boolean and null. Nullable scalar and direct compiled slice/string-keyed map pointers clear with $null; non-nullable/array/object/element clears fail. TestPointerLiteralsRespectKernelIntegerRange guards the kernel signed-integer limit |
Key / Keys/KeyAttribute | Partial: key retained as immutable identity metadata | TestBothFrontEndsMatchCSharpBasicGolden; root metadata does not itself map or redirect an event key, matching C#. One non-nullable scalar key is enforced. Child identity conventions are implemented below; variant discovery remains #27 |
NoAutoMap / NoAutoMapAttribute | Implemented at the root: no-auto, NoAutoMap() and AutoMap() | Golden, key/flag and kernel materialization tests. Enabled by default; field exclusions coexist with explicit mappings. Immediate-parent model-bound policy, fluent Inherit defaults and local exclusions are covered by TestModelBoundAutoMapChecksOnlyImmediateParent, TestFluentChildAutoMapDefaultAndOverrides and TestNodesKeepExclusionsAndSubscriptionsLocalAndShareEveryNames |
NotProjected / ModelBound/NotProjectedAttribute | Implemented at the root: not-projected | Golden and kernel materialization tests prove AutoMap exclusion; provenance retains the intentional-unmapped marker. It does not silently erase an explicit mapping |
NotRewindable / ModelBound/NotRewindableAttribute | Implemented: NotRewindable() | TestFromEventKeysFlagsAndDefaultIdentity proves wire rewind policy for both front ends |
Passive / ReadModels/PassiveAttribute | Implemented for source-keyed basic projections: Passive() | TestKernelProjectionMaterializesModelBoundAndPassiveReads and registry tests prove inactive registration, None sink and immediate reads. Unlike a C# analyzer warning, passive key redirection fails construction because immediate reads cannot honor it |
EventSequence, EventLog / EventSequences/*Attribute | Implemented for explicit selection: WithEventSequence, WithEventLog | TestFromEventKeysFlagsAndDefaultIdentity, TestProjectionExplicitDeclarationsAndStoreOverrides, TestProjectionDiscoveryInheritsModelDefaults, TestProjectionExplicitModelConflictsFailAtNewClient; declarations inherit the model sequence unless explicitly selected, and bound reads share it. Different explicit settings conflict, including explicit event-log. Automatic source-store/inbox inference is #27 |
ContextKey / Keys/ContextKeyAttribute | Not implemented, upstream-inert | No consumer exists in the referenced C# client. No inert Go tag is accepted; working UsingKeyFromContext and context(path) keys have separate executable evidence below |
Compilation and migration boundaries
Section titled “Compilation and migration boundaries”declarations.Parse(declarations.V1, tag)is the shared, bounded parser.FuzzParse, grammar/error tests andTestDeclarationsOnIgnoredFieldsFailClosedcover delimiters, escaped strings, offsets, ignored fields and fail-closed role checks. Unsupported compliance and event-field directives remain errors, not no-ops. Arithmetic, global mappings and nodes use this same parser.serialization.Plan.Fields()supplies detached field/index, serialized name/path, declared type, nullability and scalar/format metadata. Projection code never invents another casing policy.TestFieldMetadataSharesPlanAndOwnsSnapshotscovers snapshot isolation. Fundamentals wrapper compatibility is centralized inscalarCompatibleandconcepts.Underlying, without calling fabricated concept values. Child parent-key inference retains declared domain-ID types.- All event references resolve only against the frozen store catalog: bare Go name,
go("qualified.Type"),id("persisted-id"[,generation]), or a typedBindEventalias. Tests cover aliases, exact generations, ambiguity with qualified candidates, foreign handles and store isolation. Runtime validation is mandatory; ordinary gopls rename and Go vet do not validate custom tags. Generation/analysis tooling remains deferred. - Model-bound precedence is C#‘s family order (
set,add,subtract,increment,decrement,count,context,value,clear), with last-in-family wins and inspectable shadowing diagnostics. Fluent duplicate writers fail.TestModelBoundPrecedenceAndFluentDuplicateRejectionfixes this distinction. Mixing mapping tags and fluent mappings fails rather than applying accidental precedence; identity/exclusion tags are shared. - The one encoder emits the canonical immutable definition.
TestFluentBuildSnapshotsValuesAndCallbacksand golden mutation tests prove literal/callback/protobuf ownership. Reconnect uses those definitions without reflection or author callbacks. Defaults are{}, active, rewindable and AutoMap enabled. The model-bound/explicit projection ID defaults to the model observer ID when set, otherwise the full Go model type name, independently of an overridden read-model identifier;TestProjectionDiscoveryInheritsModelDefaultsandTestFluentBuilderInheritsModelDefaultscover both front ends. ZeroDefinitionaccessors return zero metadata (TestZeroDefinitionAccessors);WithIdentifieror a builder ID selects shared C# identities. Container naming remains a read-model setting. NewClientfails the whole declaration graph before I/O rather than C#‘s log-and-skip discovery. Each registry snapshot includes events, models, constraints and projections under one lock.TestProjectionStartupValidationIsAtomicAndStoreLocalproves forward references and failure atomicity. Models with mapping tags discover projections; type-level options requireAddProjection. Explicit foreign model handles and conflicting producer settings fail.- Registration follows event types and read models, in the observers/constraints portion of the pass.
TestProjectionDiscoveryRegistrationAndFrozenReplayproves store-wide sharing, producer binding, read barriers, unchanged reconnect definitions and namespace isolation.TestProjectionFailureCannotPublishReadinessproves old-kernel rejection. Nonempty projection sets register withClientownership andFullSet=true; empty Go sets skip the RPC, so removing all local declarations does not provide a persistent-projection deletion API. - Kernel
Source/Kernel/Core/Projections/Engine/Expressions/EventValues/{ValueExpressionResolver,LiteralExpressionResolver}.csat the revision above restricts literals to BMP letters, combining marks, decimal digits, connector punctuation, space and._/:*+-.TestKernelUnrepresentableModelMappingsFailAtNewClient,TestKernelUnrepresentableKeysFailAtNewClientandTestFluentLiteralUTF16Boundarycover UTF-16 limits. Exact event pathstrue,True,falseandFalsefail because the kernel resolves them as boolean literals first;TestBooleanPropertyPathsRejectLiteralResolverAmbiguitycovers fluent mappings. Quotes, commas, semicolons, parentheses and newlines cannot be escaped into support. The Go compiler rejects them instead of allowing the kernel’s partial regex match. Literal error strings are redacted. Schema/range/format validation remains authoritative; no arbitrary$...passthrough exists. - These mappings are not full projection parity. Variants, global handlers, concrete recursive prerequisites, build-local fingerprints, source inference and preview/query are covered below under projections part 3. Initial-state authoring and artifact labels are covered below. Bounded runtime projection additions are implemented; replacement and runtime registration of other artifact families remain unsupported. Explicit unprotected derived families and their single-derivative children are covered in registered derived codecs; protected families and other custom representations remain unsupported.
The projection guide presents model-bound declarations first and fluent declarations second. TestProjectionSnippetsMatchCompiledExample prevents drift from the executable example.
Projection initial values and artifact labels
Section titled “Projection initial values and artifact labels”Authority: C# 2e31b0dfba489159b3db323238f16d0f277056b4,
Source/Clients/DotNET/Projections/ProjectionBuilder.cs:55–59,
ProjectionBuilderFor.cs:166–183, and Source/Clients/DotNET/TagExtensions.cs.
Kernel contracts are Engine/Pipelines/Steps/{SetInitialState,HandleEvent},
ChangesetExtensions.AddPropertiesFrom, and the root initial-state specifications
under Core.Specs/Projections/Engine/Pipelines/Steps/for_HandleEvent.
projections/testdata/defaults.json is hand-derived, not captured from .NET;
object keys are canonicalized to Go’s sorted snapshot order.
| Behavior | Go surface and status | Evidence and difference |
|---|---|---|
| Whole-model initial values | Implemented for supported model codecs: WithInitialValues[M](value) on both front ends | TestInitialValuesAndLabelsShareCanonicalDefinition, golden fixture, TestInitialValuesPresenceAndFrozenNaming, TestProjectionInitialStateUsesEveryFrozenStoreNamingPlan, TestInitialValuesHonorKernelIntegerGuards, TestInitialValuesStringsAreJSONNotProjectionExpressions; omitted {}, zero/false, empty collections, concepts, nested objects, dictionary values and explicit names. Go snapshots a typed value instead of invoking a C# callback; model-bound authoring is a Go addition |
| Scalar presence | Go-specific: WithInitialValue(Path[M,V](path), value) | Presence/invalid-shape tests and ExampleWithInitialValues; explicit scalar zero/null survive naming rebind without a typed round-trip. Whole-model nil properties remain omitted. Collection nulls, collection traversal, duplicate writes, overlapping scalar paths and foreign types fail |
| Artifact labels | Implemented: WithLabels(labels...) encodes only ProjectionDefinition.Tags | Shared-definition golden and reconnect tests; exact duplicates keep first occurrence, like C# GetTags().Distinct(). Go additionally rejects blank, control-containing and invalid UTF-8 labels. Kernel materialization test appends untagged events to a labeled projection, proving labels do not filter |
| Snapshot and definition identity | Implemented through the existing canonical compiler/encoder | Golden mutation/hash tests, TestInitialValuesSnapshotPopulatedPointersAndNestedConcepts, selected-store naming test and TestProjectionDiscoveryRegistrationAndFrozenReplay; caller map/slice/pointer/nested-concept/protobuf/registry mutation cannot change bytes, hash or reconnect output. TestSnapshotRebindRejectsFieldsLostToPromotion, TestSnapshotRebindPreservesEmbeddedPointerAndRecursiveFieldIdentity and TestProjectionInitialStateRejectsLostFieldBeforeClientIO require unique Go field-index/type identity and reject lost fields before I/O, rather than silently rebinding to a shadowing field. Sorted object keys differ from C# declaration order; hashes remain build-local |
| Protection boundary | Go-specific fail-closed protected-root initialization | TestInitialValuesRejectPlaintextProtectionAndNonfiniteValues, TestInitialValuesRejectEntireProtectedRootThroughOptionsAndReferences; any supplied root with PII/confidentiality metadata, including null, is rejected because definition initialization bypasses append encryption. Omitted roots remain allowed. Encrypted defaults are not supported |
| Materialization and removal | Implemented for the witnessed root shape on 19.32.3-development | TestKernelProjectionDefaultsMaterializationRemovalAndReplay: registration absence, defaults plus explicit mappings/increments, subsequent updates, removal and fresh initialization. TestKernelProjectionDefaultsAutoMapAndAggregateOnly separately proves ordinary AutoMap overwrites the initialized name, while an aggregate-only increment leaves that name unchanged. Ordinary collection defaults are covered; projection-owned child roots are excluded by ChangesetExtensions.AddPropertiesFrom, so an empty initializer does not prove materialized child presence |
| Explicit observer replay with defaults | Partial: acceptance witnessed, completion unverified on the pinned kernel | The replay_completion subtest subscribes to ObserveJobs before requesting replay and requires CompletedSuccessfully for that exact job before comparing state. It skips when the job disappears without that evidence; unchanged read-back can be pre-replay state and is not substituted as proof. No default-aware replay-completion, automatic replay-on-default-change or revision/redaction rewind claim |
| Scenario replay defaults | Not implemented, fail-closed | The pinned Grpc/ReadModels/ReadModels.GetAllInstances calls projection.Process, which omits initial state in the observed kernel result. TestReadModelScenarioRefusesProjectionDefaultsBeforeConnection and TestKernelReadModelScenarioRefusesUnfaithfulProjectionDefaults refuse nonempty state with ErrFidelityUnavailable; #38 owns scenario fidelity. No test-only initial-state compiler |
The initial-values guide and compiling stock example
are tied together by TestProjectionDefaultsSnippetMatchesCompiledExample.
Projection attributes: arithmetic, globals and nodes
Section titled “Projection attributes: arithmetic, globals and nodes”References use the same C# revision above, DotNET/Projections/ModelBound and the
corresponding fluent builders. projections/testdata/advanced.json is
hand-derived, not captured from a .NET run. TestFrontEndsMatchTheirCSharpAdvancedShapes
compares model-bound output to that definition and fluent output to its distinct
C# shape: children and nested objects use AutoMap Inherit, and fluent child
subscriptions do not synthesize identity mappings. Both cover arithmetic, nested
*NotSet*, All, joins and both removal contracts. testdata/fluent-child.json is
also hand-derived from ChildrenBuilder, ProjectionBuilder.Children and
KeyAndParentKeyBuilder; TestFluentChildWithoutParentKeyMatchesCSharpGolden
checks that absent UsingParentKey emits the empty string, not an inferred
property. C# NoExpression.Build returns string.Empty and FromBuilder.Build
assigns it to contract ParentKey; protobuf scalar-default encoding can omit
that empty value on the wire. Kernel ProjectionFactory:1165–1170 resolves empty
to $eventSourceId.
| C# attribute / source | Go surface and status | Executable evidence / difference |
|---|---|---|
AddFrom<E> / FromDefinitionExtensions.AddAddMapping, AddBuilder | Implemented: add, fluent Add; preserves $add(path) | Advanced golden, TestArithmeticUsesConceptScalarRepresentation, kernel arithmetic test. Numeric representations only; arithmetic paths must fit the kernel’s stricter ASCII regex |
SubtractFrom<E> / AddSubtractMapping, SubtractBuilder | Implemented: subtract, fluent Subtract; preserves $subtract(path) | Advanced golden and kernel arithmetic test; incompatible scalar sources/targets fail before I/O |
Increment<E> / AddIncrementMapping | Implemented: increment, fluent Increment; optional constant/context/composite key | Advanced golden, TestCounterKeyTags, kernel arithmetic test |
Decrement<E> / AddDecrementMapping | Implemented: decrement, fluent Decrement; $decrement, not subtraction rewriting | Advanced golden, counter-key test and kernel arithmetic test |
Count<E> / AddCountMapping | Implemented: count, fluent Count; emits $count | Advanced golden, TestCompositeAndContextKeysMatchBothFrontEnds, kernel arithmetic test |
ClearWith<E> / ScalarClear, FromDefinitionExtensions.AddClearMapping, SetBuilder.ToValue, NestedDefinitionExtensions at 2e31b0dfba489159b3db323238f16d0f277056b4 | Partial: scalar-pointer and direct compiled nullable slice/string-keyed map clear/Clear, value(null)/Value(nil) emit $null; nested member clear and Node[T](ClearWith(event)) remain unchanged | Advanced golden, TestNestedNodeAndMemberClearMerge, TestCollectionNullFormsShareTypedNullableContract, TestContextCollectionTargetAndNullBoundaries, unchanged TestNullableCollectionsExistingPresenceContract, TestKernelProjectionContextCollectionClear, TestKernelProjectionContextClearRuntimeAdmission. The chosen contract is ordinary typed nullable collections, not mandatory present raw null: C# ReadModels.GetInstanceByKey deserializes the materialized response directly, without another Release; ReadModelJsonSerialization:58–78 excludes nullable collections from empty normalization, and DotNET.Specs/ReadModels/for_ReadModelJsonSerialization/when_the_declaration_keeps_the_distinction:27–32 leaves both missing and explicit-null nullable collections null. No actual .NET execution/capture is claimed. C# constructors/default initializers can distinguish omission from null; these and configured serializer profiles are not covered by this ordinary Go pointer profile. The pinned 19.32.3-development witness covers both startup front ends on one event stream: ordered conflicting-payload Tags, nonempty payload map, non-nil pointer empty arrays/maps, marker-synchronized clear/repeated clear with typed nil and raw omitted-or-null properties, retained document/key/correlation/marker and real nonempty rewrite. Non-null stale/empty values after clear fail. It separately observes initial array seeding; {} registration creates no instance and is not raw-null-state evidence. No read overlay or serializer change: missing/null both decode to nil; nil outer pointers/pointers to nil collections serialize absent, empty collections serialize []/{}. Present raw null preservation is a stronger separate capability, not a blocker for this typed profile. Fixed arrays/interfaces/structural objects/element paths/bare collections remain unadmitted. Runtime collection refusal before publication/RPC and scalar-only frozen classification controls remain; other complex context sources keep #26 partial |
FromEvery / ModelBoundProjectionBuilder.BuildFromEveryDefinition, FromEveryBuilder | Implemented for the snapshot’s stored mappings/flag: every, Every, EveryMap, EveryContext, IncludeChildren | TestGlobalPayloadAndIncludeChildrenFrontEndEquality, TestNodesKeepExclusionsAndSubscriptionsLocalAndShareEveryNames, kernel global test. Ordinary mappings use contract All, never derivative FromEvery. Model-bound cross-node bare-name collisions retain last writer with diagnostics; fluent node Every remains node-local. Kernel does not run parent Every mappings on child-only events despite the flag |
FromAll / ModelBoundProjectionBuilder.ProcessMember | Implemented: all, fluent All; merged All mappings plus root SubscribesToAllEvents | TestFromAllAndFromEveryWithoutExplicitSubscriptions, TestChildFromAllDoesNotBroadenRootSubscription, TestKernelProjectionFromAllAndFromEvery. Child-level All contributes mappings without broadening the root subscription, as in C# |
ChildrenFrom<E> / ChildrenDefinitionExtensions:153–168,378–403,798–895, ChildrenBuilder | Partial: children, fluent Children/IdentifiedBy, WithNodes(Node[T](...)) | Advanced golden, TestChildIdentityPrecedenceAndAmbiguousParentKey, TestParentInferenceExcludesChildKey, TestParentInferencePreservesDeclaredConceptTypes, TestNodeFromEventDoesNotEraseCreatorKeys, kernel add/remove. Model-bound identity: explicit → property Key → case-insensitive Go Id → matching serialized key name → source. Go has no constructor attributes. Only model-bound creators infer parents and write scalar identities; fluent subscriptions retain empty parent keys and require explicit identity mappings. Parent inference selects first exact declared ID-type match excluding child key; ambiguity is diagnostic. Model-bound AutoMap checks only the node and immediate parent’s own setting; fluent defaults to Inherit. TestModelBoundAutoMapChecksOnlyImmediateParent, TestFluentChildAutoMapDefaultAndOverrides, fluent-child golden. Concrete recursive children are covered under part 3; children of a single-derivative family are covered under registered derived codecs |
Join<E> / JoinDefinitionExtensions, JoinBuilder | Implemented for event joins on root/collection nodes: join, fluent Join | Advanced golden, TestJoinFirstOnAndLocalWriteDiagnostics, kernel initial/update join tests. First model-bound On wins; joins override local writes and produce overlap diagnostics. Go normalizes child joins by accepting an explicit On through either front end instead of C# fluent’s prohibition |
Nested / NestedDefinitionExtensions, NestedBuilder | Partial: nested on object pointers, fluent Nested; node From/AutoMap/clears | Advanced golden, nested-node fixture and kernel test. Emits C# PropertyPath.NotSet as *NotSet*, not empty. Kernel 19.29.4 does not execute collections inside nested objects, join removals on nested objects, or nested-object joins under collection children; definitions preserve the contract but do not claim those effects |
RemovedWith<E> / RemovedWithExtensions | Implemented: remove on collection/other fields, root/node RemovedWith, fluent Configure | Advanced golden, TestPropertyLevelGrandchildRemovalAndChildScopes, kernel child and root removal tests. A collection tag targets that child, not its parent; other field placement removes the containing instance. Parent defaults to source, without child-creation inference |
RemovedWithJoin<E> / RemovedWithExtensions, ProjectionBuilder | Partial: remove-join, root/node RemovedWithJoin | Advanced golden and property-level grandchild fixture prove wire definitions. TestKernelProjectionChildRemovedWithJoin executes the C# integration scenario with GroupId and Id child identifiers, and TestKernelProjectionArithmeticChildrenJoinNestedAndClear/remove_join_of_readded_id_child covers a remove/re-add content key. On 19.29.4 the MongoDB sink fails to translate child id/Id to _id in join-removal filters (Chronicle#4538, fixed in 19.32.1). TestRootRemovedWithJoinReportsKernelLimitation proves compilation diagnoses root join removal without erasing the wire definition. Root join removal is unsupported (Chronicle#4263); nested join removal is also unwired (Chronicle#4125). C# ProjectionBuilderFor.Build omits the accumulated root RemovedWithJoin dictionary; Go intentionally repairs the producer omission |
Working composite/context keys / CompositeKeyBuilder, per-event key builders | Implemented for ordered flat composite parts and scalar context: tag composite(...), context(path), fluent UsingCompositeKey, UsingCompositeParentKey, KeyPart*, UsingKeyFromContext, UsingParentKeyFromContext | TestCompositeAndContextKeysMatchBothFrontEnds, TestCompositeChildKeysDoNotWriteScalarIdentity, TestAdvancedFluentValidationAndSnapshots, TestCompositeKeysRejectEventAndIndexDeclarations. Event and index directives on typed composite-key structs fail explicitly rather than being ignored. Composite child keys preserve correlation but skip convention scalar identity writes; see the C# limitation below. Duplicate parts and nested composites fail; the kernel splits commas without balancing nested composites. Typed part names use K’s serialization plan; explicit JSON tags keep them stable across policies. These APIs do not implement the upstream-inert ContextKey attribute |
Composite child identity safety difference: C# ChildrenDefinitionExtensions:378–403
at the revision above has no composite-key guard; it copies a non-source key
string directly into the identity property mapping. Go’s structured composite
keys cannot be assigned to a scalar identity field, so the model-bound convention
skips that write rather than rejecting an otherwise valid correlation key.
Fluent C# and Go never synthesize it. Supply an explicit scalar identity mapping
when needed; composite correlation support is not a claim that C# model-bound
identity synthesis supports composites.
Join-removal investigation: C# source at
2e31b0dfba489159b3db323238f16d0f277056b4,
Source/Clients/DotNET/Projections/{ChildrenBuilder,RemovedWithJoinBuilder}.cs and
ModelBound/{RemovedWithJoinAttribute,RemovedWithExtensions}.cs, emits the same
child removal dictionary and key as Go. The C#
Integration/Client/Projections/Scenarios/when_removing/child_removed_with_join.cs
uses GroupId with $eventSourceId creation/removal keys and UserId as parent
key. Its Go translation passes against the pinned image; changing only the child
identifier to Id reproduces the failure without any re-add. The original
content-key probe also passes when only its child identifier changes from id
to itemId; that diagnostic change was reverted. The .NET suite itself was not
run. Kernel Source/Kernel/Storage.MongoDB/Sinks/Sink.cs:RemoveChildFromAll omits
ToMongoDBPropertyName, which ordinary BuildChildRemoved applies. Root and
nested join-removal limitations are source-confirmed in
Source/Kernel/Core/Projections/Engine/ProjectionFactory.cs, not additional live
Go probes. The single Go encoder and golden fixtures remain unchanged.
Additional acceptance evidence: TestNodeRebindingUsesSerializationMetadataEverywhere
checks joins, child/removal keys, nested mappings and arithmetic against a second
naming plan without mutating the original. TestAdvancedInvalidDeclarationsFailBeforeIO
checks malformed structural/path/literal declarations at registration/NewClient;
TestArithmeticRejectsKernelUnrepresentablePath covers an otherwise valid JSON
path rejected by the arithmetic resolver. TestAdvancedFluentValidationAndSnapshots
checks callback ownership. Recursive guards and derivative-group production are
not claimed by these nonrecursive fixtures.
The C# acceptance cases reproduced include child identity and parent-key ambiguity,
with_from_all/and_it_is_combined_with_from_every, nested clear attributes and
with_class_child_grandchildren_having_property_level_removed_with. Go adds
atomic startup errors and inspectable diagnostics instead of C# discovery’s
log-and-skip behavior. The order guide and its example
have a snippet-equality test. The example explicitly maps fluent identity and
checks that its only wire difference is the child’s Inherit versus model-bound
Enabled AutoMap default.
Projections part 3
Section titled “Projections part 3”References: Chronicle 2e31b0dfba489159b3db323238f16d0f277056b4,
Source/Clients/DotNET/Projections/ModelBound/{ModelBoundProjectionBuilder,VariantOfExtensions,ChildrenDefinitionExtensions}.cs,
VariantReclassifier.cs, Projections.cs:286–302, and the corresponding
DotNET.Specs/Projections/ModelBound/for_ModelBoundProjectionBuilder/when_building_variant
and with_children_having/self_referencing_children* specifications.
The variant golden and recursive expectations are hand-derived from C# source;
the .NET suite was not executed. Runtime evidence uses 19.32.3-development.
| Attribute / C# surface | Go status and API | Evidence and boundary |
|---|---|---|
VariantOf<I> / VariantOfAttribute, VariantOfExtensions | Implemented: VariantOf[I](), group-wide compilation after explicit registration | TestVariantFrontEndsMatchCSharpGolden, TestVariantValidationAndGlobalTargets, TestKernelVariantsEnterLeaveAndDoNotResurrect. Own key required; non-entering From handlers replace matching self-joins; sibling entry events add removals. Siblings and globals must share a store registry |
EntersOn<E> / EntersOnAttribute, VariantReclassifier | Implemented: EntersOn(handle, ...FromOption) with optional key; VariantKey for tag-free fluent identity | Variant golden covers redirected entry keys and unchanged source-key sibling removals. Kernel variant fixture covers entry, global update, exit, non-resurrection and intentional re-entry. Existing explicit sibling removals win. Joins still override local entry writes |
GlobalFor<I> / GlobalForAttribute, MergeGlobalHandlers, GlobalHandlerPropertyNotOnVariant | Implemented: Global[T](GlobalFor[I]()), or a local fluent builder with GlobalFor[I] | TestGlobalCopiesFromPropertiesOnly, TestGlobalDoesNotMaterializeAutoMapMatches, variant golden and target validation, kernel shared title update. Only explicit root From property mappings merge, with overwrite diagnostics; class-level FromEvent adds no properties and AutoMap matches are not materialized. Go rejects a global From without explicit writes with typed GlobalFromHasNoProperties at NewClient (TestGlobalFromWithoutPropertiesFailsAtNewClient) instead of C#‘s silent no-op. No standalone model registration. Fluent reusable globals are a Go authoring extension; C# fluent repeats From mappings |
Recursive ChildrenFrom<E> / ChildrenDefinitionExtensions:641–715 | Implemented for concrete recursive shapes and finite C# traversal: existing tags/Nodes and explicit fluent children | TestRecursiveKeyedChildPropagationAndAncestorExclusion, TestRootSelfReferenceUsesFirstLevelFiltersAndStopsExpansion, TestRecursiveChildSetFromFiltersNonRecursiveLevelOnly, TestKernelRecursiveKeyedChildren. The first child uses normal event/member filters even for a root collection of its own type, while the traversal guard stops grandchildren. Set mappings for the child type’s own ChildrenFrom event are excluded at non-recursive levels and retained at repeated levels, reproducing C# self_referencing_children_with_set_from. Repeated nodes keep local exclusions and own creators, exclude ancestor creators, and require parent keys for keyed update propagation. Traversal guards are path-local, not global visited sets; this is not unlimited-depth recursive routing |
Derived children / ResolveConcreteChildType, AddDerivedTypeDiscriminatorMapping | Partial: children of a family with exactly one registered derivative; see registered derived codecs | TestDerivedProjectionCopiesWholeFamiliesButDoesNotInferChildren permits whole-family copies and refuses children on a two-derivative family, which C# would keep as the undecodable interface. Direct family-valued derivative properties remain refused for Fundamentals#1149; protected families remain unsupported |
| Recursive schema/codec prerequisite | Implemented for concrete structs, pointers and collections using draft-07 references | TestRecursiveSchemaCodecAndSiblingTraversal, TestGenericRecursiveSchemaReferencesUseSafeNames, TestRecursiveValuesRejectCyclesAndExcessiveDepth, TestRecursiveReadModelKeepsRootIDTranslationLocal, kernel recursive fixture. Recursive definition names use only ASCII letters, digits and underscores plus a type hash, including for instantiated generics. Cyclic runtime values fail; encoding is bounded to 256 levels. Untagged root ID normalization does not leak into nested instances |
EventStore on events / InferEventSequenceId, fluent ProjectionBuilderFor | Partial: events.WithSourceStore, finalized projection inference, Definition.ForStore | TestSourceInferenceExplicitSequenceAndCompatibility, TestSiblingRemovalsDoNotChangeVariantSource. Local origin selects log, external selects inbox-<store>; incompatible origins fail. Generated sibling removals never affect source inference, so a local variant remains on event-log when its sibling enters from an external store. Go intentionally normalizes both front ends and includes authored joins/children/removals, excluding generated sibling removals; C# model-bound checks root From before lowering and fluent lacks local-store comparison. Subscription provisioning is covered under External integrations |
EventSequence / EventLog precedence | Implemented: explicit projection or read-model sequence overrides source inference | Source inference test covers explicit log/custom, including conflicting source stores bypassed by explicit selection. Inferred templates are specialized for each store without rediscovery |
FromAll + FromEvery with variants | Implemented for C# emitted definitions; broad All fallback is not exclusive | Variant golden retains All/IncludeChildren and SubscribesToAllEvents; kernel variant fixture combines both while entering/leaving and handling global joins. Unrelated events selected by All can create a model through the kernel fallback: prefer Every for exclusive lifecycle variants |
| Final definition fingerprint / registration snapshots | Go-specific: Definition.Hash() returns SHA-256 of deterministic finalized protobuf encoding | Variant golden compares both front ends and reordered admission, checks detached ownership; recursive fixture verifies nested naming affects the hash. TestProjectionDiscoveryRegistrationAndFrozenReplay proves frozen reconnect registration. Excludes timestamps/provenance, includes nested nodes, exclusions, source selection and sibling removals. Stable only within one build: protobuf deterministic marshaling is not guaranteed across binaries or dependency versions, so do not persist hashes across upgrades. Neither the kernel nor C# uses a client hash; reconnect reuses frozen definitions |
IProjections.Query, historical ProjectionPreview | Partial: EventStore.QueryProjection / PreviewProjection, projections.QueryResult, typed QueryError; explicit-model queries work | TestProjectionQueryPreviewTransportAndFailureBoundaries, TestKernelProjectionQueryAndPreview. Uses existing Preview RPC with C#‘s exact request fields; PDL executes on the kernel without permanent registration. Owned JSON entries and optional schema; malformed/ambiguous envelopes fail. TestKernelProjectionQueryInfersSchema covers inferred-schema queries on 19.32.3; on 19.29.4 the inferred schema is empty and drops projected properties (Chronicle#4539, fixed in 19.32.1). Caller context bounds the call. Kernel caps matching events at 1,000; no claim of complete large-history reads |
The variant guide has model-bound and fluent examples
with snippet-equality and definition-equivalence tests. Preview diagnosis sent
unchanged PDL, inspected the persisted event schema, and observed a returned schema
without properties despite explicit title = title mapping. An explicit target
read model passes the same query assertions. The C# Query implementation sends
the same four fields to Preview; no alternate Go compiler or callback is involved.
The exact server root cause is not claimed. Existing remove-join limitations
remain unchanged. Use registered derived codecs
for admitted unprotected families; those codecs do not implement derived-child
lowering. Subscription provisioning is covered under
external integrations.
Reactors part 1
Section titled “Reactors part 1”References in this section use C# 2e31b0dfba489159b3db323238f16d0f277056b4, relative to Source/Clients/DotNET unless marked kernel. Scope agreements follow Fundamentals.Go#9; the optional adapter pins Fundamentals.Go 0fd6b51d12e47b1ecd1d7c9b5524d0350d01c216. The reactor guide leads with plain closures, then convention discovery and optional services.
| Behavior / C# source | Go surface and status | Evidence and deliberate difference |
|---|---|---|
Explicit admission and convention dispatch / Reactors/{ReactorInvoker,EventHandlerMethods} | Implemented for current registered event types: RegisterReactor[R], RegisterReactorHandler, reactors.On/Returning; one compiled Plan | TestDiscoveryPrecedenceAndExportedMethods, TestTypedCallbacksShareValidationAndEffects. Go explicitly admits types, adds optional leading context.Context, and can discover only exported methods. No required Handle/On name. Unregistered method parameter types are not inferred as events; explicit unknown callbacks and reactors with no known handlers fail |
Event-interface families / Events/EventTypeExtensions, Reactors/ReactorInvoker | Implemented for interfaces implemented by current catalog events, excluding unconstrained any | TestRegisteredEventInterfaceFamilies; value or pointer implementations participate. Current and historical codecs coexist in the catalog; each observer must select one generation per event ID (TestObserverRejectsMixedGenerationBindings). See event evolution |
Method precedence / Observation/HandlerMethodPrecedence | Implemented: descending arity, then ordinal name; shadows are logged and available from Plan.Shadows | TestDiscoveryPrecedenceAndExportedMethods. All reflected methods are exported, so C#‘s public/nonpublic comparison is inapplicable. The optional Go context counts toward arity. Duplicate explicit bindings are errors |
Signature validation and catalog isolation / ReactorInvoker, ReactorMethodArgumentsResolver | Go-specific: typed DeclarationError, validation at NewClient, no process-global dispatch cache | TestInvalidSignaturesAreTypedAndDoNotActivate, TestPlansAreIsolatedByCatalog, TestReactorCatalogValidationAtNewClient, TestReactorDuplicateAndClientCatalogIsolation. Unsupported results/variadics fail before I/O; constructors are not executed. Catalog-capable factories validate services; factories without Catalog defer actual availability to invocation |
Stable observer ID / Reactors/{ReactorAttribute,ReactorTypeExtensions} | Go-specific: full Go import path plus normalized type name; WithID selects a persisted C# identity | TestDiscoveryPrecedenceAndExportedMethods, duplicate/catalog tests. Renames change the default identity; choose explicit IDs for migration. Duplicate IDs/types and projection-ID collisions are rejected per registry |
Parameter precedence / Reactors/ReactorMethodArgumentsResolver | Implemented: event context → delivery → registered read model by source/custom key → scope | TestReactorReadModelInjectionUsesCustomKey, TestReactorFundamentalsScopeCatalogAndBorrowedSingletonOwnership. Go checks explicit model registration rather than requiring a local producer. Missing models require pointer parameters; value parameters fail instead of fabricating a zero model. Service.GetValue shares typed Reader.Get collection normalization for discovered parameters |
Delivery identity / Reactors/ReactorDelivery, Source/Infrastructure/KeyHelper | Implemented: reactors.Delivery, six components and C# #-joined ID() | TestDeliveryIdentityMatchesCSharp, TestReactorOrderedBatchPartialFailureAndCleanupBeforeAck. Identity uses the event source ID, not an inferred custom partition. C# delimiter ambiguity is preserved; no deduplication/exactly-once claim |
Default activation / DefaultServiceProvider, ClientArtifactsActivator | Go-specific: plain constructors/closures; zero-container scopes construct zero-valued structs/pointers and empty slices | TestPlainGoExampleHasNoDependencyInjectionDependency executes go list -deps as an ordinary test. No automatic NewX discovery or C# constructor enumeration. The core does not transitively import DI; Fundamentals factories enter through the separate services.WithServices adapter, not a root-package DI dependency |
Service activation and ownership / ClientArtifactsActivator, ActivatedArtifact | Implemented for batch artifacts: Catalog-advertised services win, constructor results owned, service results borrowed, context closer preferred | TestRegisteredArtifactsWinWithoutDoubleDisposal, TestActivationFailureReleasesPartialResourcesAndPreservesCause, TestLeaseCleanupIsIdempotentAndGuardsInvocation, TestFactoryWithoutCatalogDefersResolutionAndRejectsWrongTypes. Go never double-disposes scope/provider-owned artifacts. Without Catalog, explicit constructors run; nil constructors request resolution. Unlike C# GetServiceOrCreateInstance, Chronicle does not guess whether a resolver failure permits construction fallback. ActivationError preserves factory/cleanup causes; lease invocation after Close fails |
Batch/event scope boundaries / Reactors/Reactors.cs:683–708 | Implemented: one scope/artifact/middleware chain per received batch; PerEvent() opt-in | TestReactorOrderedBatchPartialFailureAndCleanupBeforeAck, TestReactorPerEventCreatesAndClosesEachScope. BatchFromContext installs batch coordinates before scope activation without capturing a first event. Client-lifetime collaborators use provider-owned Singleton bindings; no client-lifetime scope is retained. Config-backed definitions, two-phase preparation and borrowed facade helpers are implemented separately under definition factories; they do not change the per-batch default |
Per-event metadata / Reactors/ReactorHandler | Implemented for system/on-behalf-of identity, correlation and reactor causation | Ordered-batch transport test. Go uses explicit derived contexts, not ambient state. Error messages reach the kernel; Go errors do not synthesize .NET exception stack traces |
Middleware hooks / Reactors/{IReactorMiddleware,ReactorInvoker,ReactorMiddlewares} | Implemented for before failure and after logging policies; Go-specific deterministic registration order instead of C# Task.WhenAll | TestReactorMiddlewareOrderAndFailurePolicies. All before and after hooks run; before failures suppress handling, after errors never change the result after effects. Middleware receives delivery and scope directly, allowing Arc adapters to unwrap/check the borrowed Fundamentals scope |
Middleware admission / Reactors/ReactorMiddlewaresActivator | Implemented: RegisterReactorMiddleware applies registry-wide middleware to every reactor, ahead of per-reactor WithMiddleware | TestRegistryMiddlewareRunsForEveryReactorBeforeLocalMiddleware, TestRegistryMiddlewareValidationAndStoreIsolation. Registration order is deterministic; NewClient freezes the list per store catalog. Later registrations do not affect existing clients |
Middleware activation / ReactorMiddlewaresActivator | Go-specific fail-closed activation, rather than C# log-and-skip | TestActivationFailureReleasesPartialResourcesAndPreservesCause; every successfully acquired resource is released even if a later constructor fails |
Basic returned effects / Reactors/SideEffects/EventResultHandler | Implemented for no result, error, event and event+error; nil event means no effect; append to EventLog using triggering source or EventSourceIDProvider | TestReturnedEventAndErrorShapes, TestTypedCallbacksShareValidationAndEffects, TestReturnedEventUsesReactorSourceProvider, TestReactorPerEventScopeAndAppendRejection, TestKernelReactorOrderingReturnedEventsAndFailedPartition. Errors suppress results; append domain rejection fails handling. Stream/source-type/subject metadata providers and custom side-effect handlers are covered in Reactors part 2 |
Ordered receive, generation selection and partial failure / Reactors/Reactors.cs:708–810; kernel Grpc/Observation/Reactors | Implemented: serial receive/handle/send, no application queue, acknowledgement only through the last successful event | Ordered-batch and kernel tests. Uses target generational content when available, otherwise C#‘s current-content fallback while retaining the original context generation. Invalid contexts, unknown subscribed types and non-increasing batch positions fail closed. Wire message-size limits bound transport buffering |
Cleanup and acknowledgement / ActivatedArtifact, Reactors/Reactors | Go-specific cleanup before acknowledgement; cleanup errors fail handling instead of C# logging/swallowing | TestReactorCleanupFailureDoesNotAcknowledgeCompletedEffects, lease cleanup tests. Batch cleanup failure resets progress to unavailable; per-event cleanup failure retains earlier successful events. Already completed effects may repeat on kernel recovery. Cleanup has a bounded context, not forcible goroutine termination |
Registration order / EventStore.RegisterAllArtifacts, Reactors/Reactors | Implemented after event types, read models, constraints and projections | TestReactorOrderedBatchPartialFailureAndCleanupBeforeAck rejects an early observer at the fake kernel. Duplex registration has no acceptance reply: readiness means registration sent, not observer catch-up (see the late-subscription row; Chronicle#4558 is fixed in 19.32.3). TestReactorOpenDoesNotBlockBackgroundArtifactReplay verifies background artifact replay does not await observer readiness. Frozen dispatch plans are reused on reconnect |
Cancellation, unregister and reconnect / Reactors/Reactors.cs:106–114,542–611 | Implemented: generation-owned workers, independent two-second resubscription after stream error/completion or failed open, retained local unregister | TestReactorReconnectUnregisterAndWorkerJoining, TestReactorOpenFailureRetriesOnSameGeneration, TestReactorUnregisterCancelsResubscriptionDelay, TestReactorUnregisterIsRetainedAcrossReconnect. Resubscription retains the ConnectionId and does not cancel unrelated appends. Only the keep-alive watchdog replaces lost connections. Whole-kernel reactor restart remains unverified; a malformed-definition kernel regression is blocked by a separate keep-alive failure (Chronicle#4537) |
Readiness cancellation / Reactors/Reactors | Go-specific: caller context bounds readiness, not the generation-owned subscription | TestReactorOpenOutlivesCallerAndAppendDeadline, TestReactorUnregisterDuringOpenDoesNotPoisonRegistration, TestReactorOpenFailureRetriesOnSameGeneration. A failed Open releases explicit waiters with a registration outcome naming the reactor and pending subscription retry; later calls observe recovery. Removal during Open releases readiness without dropping the shared connection |
Retired observer cleanup / Reactors/Reactors.cs:106–114 | Implemented: reconnect does not await running handlers; Go-specific explicit shutdown join | TestReactorIgnoringCancellationDoesNotBlockReconnect, TestReactorCancellationJoinsActiveHandlerAndClosesScope, TestReactorUnregisterJoinsRetiredGeneration. Retired observers are tracked until completion, and owned channels close after they finish. UnregisterReactor joins current and retired runs for its ID; both it and CloseContext report incomplete cleanup on deadline. Uncooperative handlers may overlap a newer generation; cancellation and idempotent effects remain application responsibilities |
Registration policy metadata / Reactors/Reactors, kernel Grpc/Observation/Reactors | Implemented: wire registration reads replayability, artifact/filter tags and source/stream types from the compiled plan | TestPlanRegistrationMetadataAndDetachedTags, TestReactorMetadataProvidersAndFiltersVersusLabels. Defaults remain replayable, no tags, all source types and All streams. Public authoring and replay behavior are detailed below |
Failure ownership / kernel Core/Observation/Observer.Failing | Implemented: failures reported to kernel, no autonomous effect retry | TestReactorActivationFailureIsReportedWithoutAdvancement, partial/rejection tests, TestKernelReactorOrderingReturnedEventsAndFailedPartition verifies persisted failed partition. Recovery/quarantine commands are available through EventStore.Observers(); administration evidence distinguishes a retry request from completed recovery |
Reactors part 2
Section titled “Reactors part 2”Reference: C# 2e31b0dfba489159b3db323238f16d0f277056b4, paths relative to
Source/Clients/DotNET unless prefixed with Kernel. These additions extend the
existing Plan/Lease invocation boundary and shared observer stream, not a
second execution engine. Kernel fixtures target 19.32.3-development.
| Behavior / C# source | Go surface and status | Evidence and deliberate difference |
|---|---|---|
Replay replacement / Reactors/ReactorInvoker.cs:252–342, ReplayAttribute | Implemented: Replay("Method"), callback .DuringReplay(), separate live/replay maps and union subscriptions | TestReplayMapsAndOnceOnlyPolicies, TestReactorReplayDeliveryAndSeparateNotificationScopes, TestKernelReactorReplayReplacementOnceOnlyAndNotifications; replacement wins, fallback to live without a replacement, replay-only types subscribed, combined replay flags honored |
Method selectors / ReplayAttribute, OnceOnlyAttribute | Go-specific validated exported method names, no name-prefix semantics | TestReplaySelectorsMustIdentifyDiscoveredMethods, TestReplayNamePrefixHasNoSemantics, TestReplayTypedCallbacksFallbackSkipAndDuplicates; duplicate explicit bindings are rejected within each map; typed generated selectors await #23 |
Reactor-wide OnceOnly / ReactorTypeExtensions, Reactors.Register, Source/Kernel/Contracts/Observation/Reactions/ReactorDefinition.cs:33–34 (repository-root kernel path, baseline revision above) | Implemented: OnceOnly() sends explicit field-4 false despite proto3 omission and C#‘s absent=true default | TestProto3FalseReplayabilityIsAbsentAndCSharpDefaultsTrue, TestReactorRegistrationWirePreservesReplayability, TestKernelReactorWideOnceOnlyRefusesReplayJob; byte-level full registration evidence covers false, unchanged true defaults and separate method/mixed policies. The pinned kernel refuses reactor-wide replay with JobId.NotSet, while default reactors start a job and receive replay. The refused observer continues handling live events; this is not a method-handler skip or a client deduplication ledger |
Method OnceOnly / ReactorInvoker.HandlerMethods.Resolve | Implemented: OnceOnly("Method"), callback .OnceOnly() | TestReactorOnceOnlySkipsReplayButNotRecovery, TestReplayMapsAndOnceOnlyPolicies, kernel replay fixture; a OnceOnly replacement does not fall back to live |
Replay notifications / Reactors.HandleReplayNotification:814–842 | Implemented: ReplayNotifier, PartitionReplayNotifier, one separate artifact/scope per notification, no middleware | TestReactorReplayDeliveryAndSeparateNotificationScopes, TestReactorNotificationFailureReleasesScopeAndReconnects, kernel replay fixture. No notification ack exists. Activation failure logs like C#; Go cleanup failures propagate, as for ordinary batches |
Event collections / SideEffects/EventsResultHandler | Implemented: typed slices/arrays and bare []any via existing AppendMany | TestReactorRichEffectsWireShapesAndOrdering, TestReactorEffectsValidateBeforeWritesAndPropagateRejection, TestKernelReactorRichEffects; empty collection is no effect; nil items fail; finite Go collections replace IEnumerable |
Targeted wrappers / SideEffects/{EventForEventSourceIdResultHandler,EventsForEventSourceIdResultHandler} | Implemented: existing eventsequences.Entry, pointer and collections | Rich-effect wire and kernel fixtures preserve source, route, subject, tags, occurrence and causation. No reactor default leaks into self-describing entries |
Mixed bare/targeted events / SideEffects/MixedSideEffectsResultHandler | Implemented: []any lowered to one ordered atomic AppendBatch | Wire and kernel rich-effect fixtures; complete classification before writes, input order preserved, no grouping by source |
Concurrency effects / SideEffects/EventsWithConcurrencyScopesResultHandler | Implemented: eventsequences.EventsWithConcurrencyScopes{Events, Scopes} via existing batch/scopes | Rich-effect wire and kernel fixtures; ordered entries, independent protected scopes and append rejection retained. Empty batches require a protected scope |
Bare-event metadata / SideEffects/*ValuesProvider | Implemented: EventSourceIDProvider, EventStreamIDProvider, SubjectProvider, source/stream/stream-ID options | TestReactorMetadataProvidersAndFiltersVersusLabels, TestReturnedEventUsesReactorSourceProvider; outputs always target EventLog, provider stream ID overrides option, ordinary event subject fallback remains in effect |
All-matching custom effects / SideEffects/{IReactorSideEffectHandler,ReactorSideEffectHandlers} | Implemented: RegisterReactorSideEffectHandler, WithSideEffectHandlers, SideEffectContext | TestReactorCustomEffectsInvokeAllMatchesAndFailPartition, TestReactorClassifiesExtensionsBeforeBuiltinAppend; all matches selected before effects, errors joined before ack. Instances borrowed/concurrency-safe, no Arc dependency; Arc command seam tracked by #44 |
Store-local extension admission / SideEffects/EventStoreReactorSideEffectHandlerInstances, ReactorInvoker | Implemented: registry lists frozen per store at NewClient, return-type claims participate in startup validation | TestCustomEffectsFrozenPerStoreAndStartupAdmission, TestCustomEffectsReceiveReplayPolicyAndBorrowedBatchScope; delivery, scope, catalog and replay/OnceOnly policy are invocation-local |
Custom/mixed collections / ReactorSideEffectHandlers extension contract | Go-specific element-wise fallback when no handler claims the whole collection | TestCustomEffectCollectionPreflightAndDeterministicExecution; all elements classified before any effect; deterministic order, stop after failed item, all matching handlers for that item still run. Pure events remain atomic. External/custom mixtures have no cross-effect transaction; finite collections only (#44) |
Tag filters / FilterEventsByTagAttribute, Reactors.Register | Implemented: WithEventTagFilter sent in ObserverFilters.FilterTags | TestReactorMetadataProvidersAndFiltersVersusLabels; copied input, OR within tags. Kernel owns matching; wire verified, independent live-kernel tag-matching fixture not added here |
Artifact labels / TagAttribute, TagsAttribute | Implemented: WithTags sent only as definition tags | Same registration fixture distinguishes labels from filters; no event-property extraction |
Source/stream filters / EventSourceTypeAttribute, EventStreamTypeAttribute | Implemented: source/stream options sent as filters and used for bare-effect defaults | Same wire fixture; categories combine with AND in kernel. WithEventStreamID never filters |
Explicit sequence and external inbox / ReactorTypeExtensions.GetEventSequenceId | Implemented: explicit sequence/log, event-origin inference, observer source overrides and automatic provisioning | TestObserverOriginsBindSequencesModelsAndSubscriptionUnion, TestObserverExplicitSequencesOverrideInferenceAndMixedSourcesFail, TestKernelExternalSubscriptionForwardsOutboxToObserverInbox; per-behavior boundaries under External integrations |
Historical-generation-only concrete handlers / Events/EventTypeExtensions.cs:61–76 | Implemented: historical/current catalog entries coexist; a concrete historical handler retains its exact codec and subscription generation | TestConcreteHistoricalGenerationHandlerIsNotLost, TestHistoricalConcreteHandlersKeepTheirOwnExpansion, TestKernelHistoricalEvolutionAndSubscribedGenerations. Go corrects the C# historical-only discovery omission, but rejects multiple generations of one ID in a single observer (TestObserverRejectsMixedGenerationBindings). Use separate observers; event evolution describes exact/current lookup and fallback, without claiming an executed two-generation cross-language consumer witness |
Read-model reactors / ReadModels/{ReadModelReactors,ReadModelReactorMethods} | Implemented: explicit admission with Added/Modified/Removed discovery, callbacks and materialized differs | See Read-model watches and reactions; distinct from durable event observers |
Observer admin, tails, completion waits / Reactors.Replay, ReactorWaitExtensions, Observation/AppendResultWaitForCompletionExtensions | Implemented: EventStore.Observers(), Jobs(), Sequence.TailForObserver, completion and job waits | TestOperationsMatchHandDerivedCSharpGolden, TestCompletionUsesActualBatchTailsAndOriginalCoordinates, TestJobWaitRequiresEvidenceAndPreservesDisappearance, TestKernelOperationsCompletionReplayAndJobEvidence; see operations. Accepted requests, registration sends, observed catch-up and durable checkpoints are distinct. Missing/disappearing jobs stay absent/unknown; no actual-attachment, multi-node or durable-checkpoint guarantee is inferred |
Reducers
Section titled “Reducers”References use C# 2e31b0dfba489159b3db323238f16d0f277056b4, relative to
Source/Clients/DotNET. The reducer guide leads with plain Go,
then conventions and optional DI. Contracts and kernel evidence target 19.32.3.
The active reducer integration test requires the deletion event to be delivered.
On 19.29.4 the kernel’s finishing catch-up job race
could persist event 2 without delivering it; 19.32.3 fixes that race. The Go
transport test independently verifies the deletion result and its checkpoint.
| Behavior / C# source | Go surface and status | Evidence and deliberate difference |
|---|---|---|
Explicit admission and one-model association / Reducers/{IReducerFor,ReducerAttribute,ReducerTypeExtensions} | Implemented: RegisterReducer[R,M], automatic Reducer producer binding; ID defaults to import path plus normalized type name | TestReducerDiscoveryValidatesAtNewClientWithoutConstruction, TestReducerProducerAndIdentityConflictsFailAtomically. Explicit root registration replaces assembly scanning. Duplicate model/type/ID and projection/other-observer conflicts fail; original model handles remain valid |
Exported signature discovery / Reducers/ReducerTypeExtensions.cs:50–105 | Implemented: event, current *M, optional event context; M/*M result, optional error and leading Go context | TestInvalidFoldSignaturesAreTypedStartupErrors, TestNilDeleteRecreateAndPresentZero. Constructor-only dependencies; arbitrary fold service arguments fail. Go cannot reflect unexported methods; bare C# Task deletion is expressed as a nil *M result |
Nullable current and value adaptation / Reducers/Validators/ReducerMethodCurrentReadModelMustBeNullable | Go-specific: nullable *M is recommended; value M is also accepted with zero on absence | TestValueCurrentUsesZeroForAbsence. Unlike C#‘s nullable-current validator, Go permits the requested value alternative; use *M to distinguish absence from present zero |
Event families and precedence / Events/EventTypeExtensions, Observation/HandlerMethodPrecedence | Implemented for current-catalog event interfaces, richest signature then ordinal name, shadow diagnostics | TestDiscoveryPrecedenceFamiliesAndCatalogIsolation. Shared discovery code with reactors; no global type-only cache. Explicit duplicate handlers fail rather than override; Go context contributes to arity |
Typed callbacks and frozen fold plan / Reducers/ReducerInvoker | Implemented: RegisterReducerHandlers, reducers.On[E,M]; callbacks and discovery share validation/invocation | TestExplicitAndDiscoveredFoldsSharePlanAndResults, TestReducerDiscoveryValidatesAtNewClientWithoutConstruction. Equivalent semantic signatures have equal fingerprints; callbacks retain caller-owned captures |
Startup validation / Reducers/ReducerInvoker, ReducerTypeExtensions | Go-specific atomic typed DeclarationError at NewClient before I/O or constructors | TestInvalidFoldSignaturesAreTypedStartupErrors, TestReducerProducerAndIdentityConflictsFailAtomically. Clearly fold-shaped unknown event methods fail instead of disappearing; a no-sink reducer model must have a matching registered plan |
Sequential fold, initial state, deletion and recreate / ReducerInvoker.cs:62–142 | Implemented: one scope, serial folds, nil deletion distinct from present zero | TestNilDeleteRecreateAndPresentZero, TestReducerRuntimeFoldOrderDeletionGenerationAndCleanupBeforeAck, TestActiveReducerInitialStateUsesFrozenNamingPlan, TestKernelReducerMaterializationDeletionAndFailedPartition; active initial-state decoding uses the frozen model descriptor for distinct ID names, all naming policies and collection nullability |
Failure and last-success position / ReducerInvoker.cs:116–142, Reducers.cs:534–578 | Implemented: stop at first error/panic, failed result never contains partial state | TestFirstFailureStopsAndDiscardsMutatedPartialState, TestReducerRuntimeFailureNeverReportsPartialState, kernel failed-partition test. Go does not synthesize .NET exception stack traces. Kernel owns recovery; no effect retry is added |
Per-operation activation and identity / Reducers.cs:261–286,534–539, ClientArtifactsActivator | Implemented: shared artifact leases/scopes with reactors; kernel-driven operations use system identity before construction and every fold, passive reads retain caller identity | TestIdentityIsSystemForConstructorsAndFolds, TestPassiveReducerReadPreservesCallerIdentityForConstructionAndFold, TestReducerRuntimeFoldOrderDeletionGenerationAndCleanupBeforeAck, TestReducerRegisteredServiceWinsAndIsDisposedOnlyByScope. No arbitrary fold parameter injection; shared optional services.WithServices adapter remains borrowed |
Cleanup ownership / ActivatedArtifact, ReducerInvoker | Go-specific: constructor values close before scope; cleanup errors fail operation, unavailable progress | TestReducerActivationFailureReleasesPartialArtifactAndScope, TestReducerLeaseGuardsUseAfterCloseAndRetainsCleanupFailure, TestReducerRuntimeFailureNeverReportsPartialState. C# logs/swallows disposal failure. Partial resources are released; scope-owned artifacts are not disposed twice |
Active/materialized and inactive modes / Reducers.ShouldReducerBeActive, ReadModels.ReadModels | Implemented: active by default; WithActive(false) preserves sink and does not select local reads | TestReducerPassiveAndInactiveBinding, TestKernelReducerMaterializationDeletionAndFailedPartition. Model binding preserves configured sink/sequence and rejects incompatible producers |
Passive source reads / ReadModels.cs:187–220, Reducers.GetInstanceById | Implemented: reducers.Passive or readmodels.Passive selects local Reader.Get, no sink, absence/deletion and fold errors preserved | TestKernelPassiveReducerReadsFoldLocallyWithAbsenceDeletionAndFailure. Uses 19.32.3 server-released source/type history and the same fold plan, then validates final state without another decrypt (TestKernelModelHistoryReleaseOwnershipAndErasure). Like C#, passive history reads do not apply active observer tag/source/stream filters. Unlike C# root-provider passive activation, Go uses one scope per local read |
Tag/source/stream filters and artifact labels / Reducers.RegisterReducer | Implemented for definition encoding: WithEventTagFilter, WithEventSourceType, WithEventStreamType, WithTags | TestReducerRuntimeFoldOrderDeletionGenerationAndCleanupBeforeAck. Filters govern active kernel delivery; labels do not filter. Real-kernel filter matching is not independently verified here |
Explicit event sequence / ReducerTypeExtensions.GetEventSequenceId | Implemented: event-log default, WithEventSequence, WithEventLog | TestReducerPassiveAndInactiveBinding; explicit model/producer sequence conflicts fail. Sequence selection alone does not provision an inbox |
Stable behavior fingerprint / Reducers/ReducerFingerprint | Go-specific: SHA-256 of canonical fold shape, model/event/observer settings and WithVersion | TestExplicitAndDiscoveredFoldsSharePlanAndResults, TestReducerResubscribesAfterEOFAndGenerationReconnectWithoutRediscovery. No IL/body hashing: implementation-only changes require a version bump; generated source versioning is not implemented |
Generational payload selection / Reducers.cs:509–530, Events/EventTypeExtensions.cs:61–76 | Implemented: current/historical codecs, exact historical handler expansion and supplied target-generation selection, else C# current-content fallback | TestReducerRuntimeFoldOrderDeletionGenerationAndCleanupBeforeAck, TestHistoricalConcreteHandlersKeepTheirOwnExpansion, TestKernelHistoricalEvolutionAndSubscribedGenerations. Catalog coexistence does not permit mixed generations of one event ID in one observer; use separate reducer plans (TestObserverRejectsMixedGenerationBindings) |
Ordered stream, reconnect and cancel / Reducers.RegisterReducer, kernel Reducers contracts | Implemented with reactor-shared stream loop and observer supervisor | TestReducerCancellationJoinsScopeAndNeverAcknowledges, TestReducerResubscribesAfterEOFAndGenerationReconnectWithoutRediscovery, existing reactor lifecycle suite. Frozen registrations survive reconnect; stream failure is isolated. Go also resubscribes on normal EOF, unlike C#‘s missing completion callback. Whole-kernel restart remains unverified |
Zero-container authoring / DefaultServiceProvider | Go-specific plain constructors and closures, no runtime container requirement | TestPlainGoReducerHasNoDependencyInjectionDependency runs go list -deps; TestBothAuthoringPathsCompileAndFold; compiled example and snippet-equality test |
| Automatic source-store/inbox inference and subscription provisioning | Implemented for compiled folds and registered-store binding | TestObserverOriginsBindSequencesModelsAndSubscriptionUnion, TestExternalSubscriptionsFollowObserversPrecedeSeedingAndReplayPerGeneration; model sequence and fold fingerprint are rebound with the store. Explicit sequences suppress inference; shared provisioning is covered below |
Replay-wide and partition lifecycle / Reducers.cs:580–607, Observation/{ICanBeNotifiedWhenReplay,ICanBeNotifiedWhenPartitionReplayed} at 2e31b0dfb | Implemented: reducers.ReplayNotifier, PartitionReplayNotifier, explicit WithReplayCallbacks; same ordered observer runtime and activation leases | TestReducerReplayNotificationsShareProductionOrderAndFreshScopes, TestReducerNotificationSignaturesValidateAtNewClient, TestReducerFoldNamesAreNotLifecycleSelectors, TestReplayNotificationPreservesAmbientMetadataAndCleanupCauses, ExampleRegisterReducerHandlers_replay. Exact interface method pairs validate before constructors; prefixes have no policy, explicit fields are optional. Fresh scope/activation per delivery, opaque partition keys preserved, no shared folding artifact or synthetic event metadata; identity/correlation/causation preserved as in C# notification path, unlike ordinary System folds. Manual/reflective semantic plans have equal fingerprints; explicit implementation version bumps remain required |
Notification failure, unknown state and lifetime / same C# notification path and kernel Contracts/Observation/ReplayState | Go-specific fail-closed activation/cleanup, Implemented cancellation/ordered dispatch without result handshake | TestReducerNotificationFailuresReconnectOnlyObserverAndNeverAcknowledge, TestReplayNotificationActivationFailureIsClosedBeforeReturn, TestReducerNotificationCancellationUnregisterAndCallbackCloseContextJoin: errors/panics/unknown enum values terminate only the observer stream, not the shared generation. C# logs/ignores activation failures; Go propagates typed errors to avoid continuing after a missed hook. Cleanup ownership reused, operation contexts canceled after cleanup, duplicate notifications dispatched again. Callback CloseContext(ctx) releases via cancellation; synchronous own-worker Close/unregister still prohibited; no exactly-once guarantee. Bufconn fixtures use WaitForHandlers(true) and joined-handler leak assertions |
Kernel replay lifecycle / kernel ReducerReplayHandler, Reducers/Clients/ReducerMediator; C# reducer replay request specs | Implemented for the pinned 19.32.3 reducer-wide replay witness through #59 services | TestKernelReducerReplayLifecyclePreservesNormalFolding: Observers().Replay accepts a namespace-bound job, start/end callbacks arrive in stream order, materialized models survive replay and subsequent normal folding. Job acceptance and end notification are not durable job/checkpoint proof; partition callbacks independently covered by production-path transport tests, not a claim that every kernel-wide replay emits partition notices. Bounded collection folds are implemented below; reducer snapshots remain unsupported. This witness does not complete every #31 recovery profile |
Bounded historical folds / ReadModels.GetInstances, Reducers.GetInstances | Implemented: raw/typed GetAll with globally bounded, generation-aware active/passive local folds | TestReducerCollectionsGloballyBoundHistoricalFoldsAndKeepActualPositions, TestKernelModelHistoryReducerCollectionRoutingAndDeletion; every nonzero explicit count selects local folding, including Unlimited; default active reads remain materialized. Zero returns empty without I/O, and complete matching history is fetched before local truncation, not paged on the wire |
Reducer snapshots / Reducers.GetSnapshotsById | Not implemented: GetSnapshots refuses reducer models with ErrUnsupported | TestSnapshotsRefuseReducersBeforeRPCAndReturnOwnedEmpty guards the refusal; the pinned kernel returns an unconditional empty result for reducers. Do not interpret that result as evidence of absent history; sessions and protected decisions remain separate capabilities |
Local watch notifications / ReducerHandler.OnNext, IReducerObservers | Implemented for unclassified models: successful production folds notify namespace/generation-local watches in order before acknowledgement, validating final plaintext without Release. Classified Watch refuses before queue attachment without a delivery-time erasure fence | TestReadModelReactorInfersFirstSeenLocalReducerAddition, TestLocalReducerWatchPlaintextRemovalAndGenerationIsolation, TestClassifiedWatchesRefuseBeforeTransportOrLocalAttachment; namespace/generation isolation and lifecycle remain unchanged, with no sink-write/durable delivery guarantee. TestKernelReleaseRouteProfiles covers successful protected fold state before/after erasure, not safe classified local-watch delivery. Reducer sessions remain explicitly unsupported |
Definition preparation and borrowed client/store DI facades / ClientArtifactsActivator, ChronicleClientServiceCollectionExtensions | Implemented for config-only projection/constraint/migration factories, seed preparation, two-phase client composition and optional services.Bind* helpers | TestDefinitionFactoriesPlainAndFundamentalsProduceIdenticalFrozenArtifacts, TestFacadeConsumerCaptureBindPrepareAndResolveCoordinates, TestBorrowedFacadeScopeDisposalKeepsActiveObserverStream; see definition factories and facade composition. These SDK witnesses do not cover Arc.Go#29 adoption or the separate runtime projection addition API; provider disposal does not transfer ownership of borrowed client resources |
Reducer failed-batch recovery
Section titled “Reducer failed-batch recovery”Go preserves C#‘s failed-batch response: last successful observation with no state.
Up to 19.32.2 the kernel restarted recovery at that last success and lost the
unpersisted prefix of the failed batch
(Chronicle #4540). Kernel
19.32.3 retries a reducer batch that fails part-way from the start of the batch.
TestKernelReducerFailedPrefixRecovery witnesses this against the pinned kernel:
one appended batch folds 1, 2 and 3, the fold of 3 fails once, recovery folds the
whole batch again from 1 and the materialized balance settles at 6 rather than 5.
The kernel may fold the batch more than once during recovery; each attempt reloads
the persisted state, so the result stays 6. Go does not change the failure wire
contract independently.
Scenario testing
Section titled “Scenario testing”References below are Source/Clients/Testing/ and Testing.Specs/ at Chronicle
2e31b0dfba489159b3db323238f16d0f277056b4. Kernel sibling cases use
cratis/chronicle:19.32.3-development. The Go fixture package is
chronicletest; these rows do not claim that a Go
substitute executes C# kernel code. Root admission remains explicit; all registered
conventions use the production compilation pipeline.
| C# scenario type / source | Go surface and status | Executable evidence and exact fidelity |
|---|---|---|
EventScenario, EventSequences/EventScenario.cs:89–135,187–266 | Partial: OpenEventScenario / NewEventScenario, Given, production EventLog / Store, kernel and substitute engines | ExampleEventScenario, TestEventScenarioUsesProductionNamingAndIsolatedStorage, TestEventScenarioRefusesStrictFidelityAndConcurrency, TestEventScenarioRefusesDiscoveredConstraintsBeforeConnecting, TestEventScenarioCancellationCloseAndMissingKernel, TestKernelEventScenarioEnforcesConstraints, TestKernelEventScenarioOwnsRealObserverLifecycle. Real kernel runs registration/append/read and constraint enforcement. Substitute runs the production Go client over an in-memory grpc.ClientConnInterface, without hosting a gRPC server, with default no-check scopes (TestSubstituteUnaryCopiesRequestsAndResponses, TestSubstituteConnectSurvivesHalfCloseUntilCanceled, TestClientBorrowsSubstituteTransport); explicit concurrency and registered constraints are refused. No hashing, schema migration/validation, encryption, authorization, durable storage or observer fidelity from the substitute. Protected-data scenarios remain unimplemented, not falsely substituted |
ReadModelScenario<T>, ReadModels/ReadModelScenario.cs:48–52,110–120,402–459 | Partial: ReadModelScenario[M], inline projection precedence, Instance / InstanceFor / Instances, reducer initial state and dependency seeding | ExampleReadModelScenario, TestReadModelScenarioFoldsRegisteredConventionsAndSelectsInstances, TestReadModelScenarioPropagatesFoldFailureWithoutPartialResults, TestReadModelScenarioInlineProjectionOverridesReducerWithoutMutatingRegistry, TestReadModelScenarioTypedCallbacksShareFoldPlanAndInitialState, TestReadModelScenarioRefusesModelBoundProjectionWithoutKernel, TestKernelReadModelScenarioDiscoversProjectionAndRejectsAmbiguousInstance, TestKernelReadModelScenarioInlineProjectionOverridesReducer, TestKernelReadModelScenarioRefusesCustomKeyProjectionReplay. The positive inline override uses a supported source-key projection and produces inline instead of the original reducer’s event-derived name; the original registry remains unchanged. Constant/custom-key replay is explicitly refused with ErrUnsupported by the 19.29.4 safe admission profile, with no partial instance map or manufactured key. Unlike C#‘s custom-key scenario selection, this kernel replay profile currently supports only event-source keys. Reducers use the production fold plan locally with Substitute, like C#‘s in-process processor; explicit Kernel selection fails with ErrFidelityUnavailable (TestReadModelScenarioRefusesKernelReducerRatherThanSilentlyFoldingLocally). Projections use the real kernel’s bounded replay RPC. There is no Go projection evaluator. Inline declarations override reducers in a detached registry; production declaration conflicts still fail. Multiple instances reject single-instance access. Unlike C#‘s multi-source single reducer fold, Go folds per source. Supported source-key projection access requires a returned string ID; missing keys fail, never infer seed-order identity. Projection initial state remains unimplemented and explicitly refused. Opt-in strict subscription follows kernel key-resolver membership (next row), independently of replay admission. Projection fixtures refuse other registered Go observers before connecting, avoiding unrelated side effects; use EventScenario for live lifecycle. Replay does not prove sink/observer catch-up, delivery metadata or effect acceptance (TestProjectionScenarioFidelityRefusesUnusedDeliveryAndEffects) |
WithStrictEventSubscription, ReadModels/ReadModelScenario.cs:247–268, ProjectionReadModelProcessor.cs:509–529,733; kernel Source/Kernel/Core/Projections/Engine/Projection.cs:168–169,218–238, ProjectionFactory.cs:1006–1131 at v19.29.4 | Partial: ReadModelOptions[M].StrictEventSubscription, ErrUnsubscribedEventSeeded. Membership is the kernel key-resolver set derived from the selected compiled wire. It includes root and child From/Join/RemovedWith, child-only RemovedWithJoin, nested From/RemovedWith (Join outside child subtrees only), derivative and root/child event-property types, recursive children, and keys ignored. Nested join removals, children and event-property types are excluded. Pure or mixed All admits every registered type. Root RemovedWithJoin is not a member, matching C#. Variants and protected models/subscribed events (any generation, including child-only members) are refused before connection. Projection initial state remains refused. Reads stay bounded by the replay admission profile: relationships and custom keys return ErrUnsupported | TestStrictMembershipMatchesKernelKeyResolverSet, TestStrictProjectionAdmitsRelationshipProfilesWithKernelMembership, TestStrictRelationshipScenarioRejectsBeforeReplayRefusal, TestStrictProjectionRefusesProtectedChildSubscription, TestStrictProjectionChecksProtectionAcrossSubscribedGenerations, TestStrictProjectionRefusesProtectedModel, TestKernelStrictMembershipMatchesProjectionObserverEventTypes (projection observer EventTypes, Source/Kernel/Core/Projections/ProjectionsManager.cs:446–471 at v19.29.4), plus TestStrictProjectionGivenRejectsBeforeAnySeedEffect, TestDefaultProjectionGivenStillAcceptsRegisteredMarker, TestStrictProjectionGivenPreservesEarlierSeedsAndSyntheticOrder, TestStrictProjectionMembershipUsesCompiledRootSubscriptions, TestStrictProjectionInlineSelectionDoesNotMutateOriginalRegistry, TestStrictProjectionRefusesUnsupportedProfilesBeforeConnection, TestCompiledVariantMetadataSurvivesIdenticalWireAndClientSnapshots, TestReadModelScenarioStrictSubscriptionDoesNotChangeReducerFiltering, and TestKernelReadModelStrictEventSubscription. Default behavior is unchanged; reducers ignore the option. Rejection happens in Given before codecs/providers/append/history, preserving earlier successful seeds without rollback. Go deliberately raises from Given before remote append, unlike C# lazy result-time processing; callers must check the error. The membership witness compares admitted IDs, projection observer EventTypes and independent golden sets; it does not claim relationship delivery, sink catch-up or replay success. The root source-key sibling witnesses persisted accepted ordering/marker absence and typed synchronous replay. #60 asynchronous job completion remains unknown. No single-encoding/provider parity claim is added |
ReactorScenario<T>, Reactors/ReactorScenario.cs:212–275 | Partial: registered ReactorScenario[R], ID-selected callbacks, seeded read-model dependencies, real plan activation/invocation, monotonic synthetic deliveries | ExampleReactorScenario, TestReactorScenarioUsesProductionInvokerSeedsAndMonotonicDeliveries, TestReactorScenarioStillRejectsUnclaimedSynchronousReturnTypes. Production discovery, constructors/scopes, middleware, argument materialization, identity/causation setup and invocation are shared. Handler failures and cleanup errors propagate. Given calls are batch scopes unless production PerEvent is selected. Unsubscribed registered events advance the synthetic sequence without activation/middleware (TestReactorScenarioSkipsUnsubscribedEventsWithoutActivatingOrRunningMiddleware). Delivery metadata and read-model storage are substituted; no server retry, quarantine, checkpoint, replay lifecycle or exactly-once claim |
RecordingReactorSideEffectHandlers, Reactors/RecordingReactorSideEffectHandlers.cs:44–80 | Implemented for finite result recording: RecordingReactorSideEffectHandlers, Produced, ShouldHaveProduced[T], ShouldNotHaveProduced[T] | TestReactorScenarioUsesProductionInvokerSeedsAndMonotonicDeliveries, TestRecorderBoundsCyclicCollectionsWithoutPartialRecordings. Collections and event/source/concurrency wrappers flatten to payloads without running registered effect executors. Production declared-return validation and runtime classification apply before scenario recording (TestReactorScenarioRejectsInvalidEffectsWithoutRecordingPartialResults); invalid wrappers and unclaimed payloads fail. Unlike C#‘s permissive recorder, Go scenarios preflight the entire effect before recording through an internal hook. Recording does not prove transport acceptance, serialization, constraint checks or command execution. Nested collections are bounded at depth 64; payload objects are borrowed |
| Scenario naming/discovery and fidelity diagnostics | Go-specific: Config.Naming, CSharpScenarioNaming, Client.Artifacts, Registry.WithProjection, Fidelity.Substitutions/Require | Naming test above, producer-selection tests, TestWithProjectionPreservesEveryRegistryFieldExceptReplacedProducer, TestWithProjectionPreservesComposedConstraints and kernel siblings. Default naming follows production Go; C# scenario camelCase is explicit and preserves leading acronyms. No container/global registry or extra module dependency. Unknown/substituted fidelity requirements fail before an assertion can claim them; kernel encryption, generation/schema validation and multi-node fidelity remain unclaimed; zero Fidelity fails closed. Missing kernel configuration skips only through testing helpers; configured connection failures fail |
Supported typed scenario decoding / ReadModels/ReadModelScenario.cs, DotNET/ReadModels/ReadModels.cs:257–258 (baseline revision above) | Implemented for registered model shapes: reducer initial snapshots/results and projection Instances/Instance results use the same frozen descriptor plan | TestReducerScenarioUsesFrozenPlanForInitialAndResult, TestProjectionScenarioUsesFrozenNamingPlan, TestScenarioKeyRequiresNormalizedDeclaredProperty, TestScenarioKeyDoesNotInferIndependentIDWithoutDeclaredKey; distinct Id/ID and all three naming policies. Keyed replay requires the normalized declared key, never an independent ID field; missing keys fail instead of manufacturing an instance map entry. Projection decode is isolated through the substitute transport with supplied replay documents, not a Go projection evaluator or evidence that the kernel accepts case-distinct ID schemas |
Kernel projection replay supporting scenarios / DotNET/ReadModels/ReadModels.cs:261–295, Kernel/Grpc/ReadModels/ReadModels.cs:GetAllInstances (kernel path from repository root) | Partial: readmodels.Service.ReplayProjection, Descriptor.KeyProperty | TestProjectionReplayIsBoundedAndNormalizesKeysWithoutPartialResults and projection sibling cases above. Counts from 1 through math.MaxInt32 force kernel replay rather than an eventually consistent sink read; zero returns an empty non-nil result without an RPC and larger counts fail rather than overflow the kernel’s signed MongoDB limit. Empty scenarios borrowing populated stores remain empty (kernel projection test above). Normal registration barriers and ID normalization apply. Reducer replay through this RPC and unlimited/materialized reads are explicitly unsupported by this method |
Fixtures are serial and own their clients; New* accepts testing.TB and ties cleanup to it (including NewReactorScenarioForID),
while Open* returns errors and requires Close. Given-event snapshots preserve
production naming. Result reads re-run reducer folds or kernel projection replay,
unlike C#‘s cached processing; keep folds deterministic. Real-kernel stores are
unique by default and are not deleted on Close. No full-kernel restart or
production encryption assertion is established by these fixtures.
Next slices
Section titled “Next slices”Delivered event declarations, historical catalogs, operations, bounded folds and facade helpers are not future slices. The remaining porting work is:
- Safety: #48: on 19.32.3 the webhook authorization regression asserts the authorization schema, read-back and history in the selected store for SDK and C#-source-equivalent requests (Chronicle#4567 fixed the 19.29.4 registration scope); actual .NET client-side Register requests are now captured separately, but no actual .NET live-kernel witness or other historical intermittent trigger is established. #18 sequence sentinels, predicates and checked pure arithmetic are implemented in the sequence-number row above, not future work.
- Read and protection routes: #33 / #35 remaining unsupported window profiles, classified projection Watch blocked by Chronicle#4566, classified local-watch delivery-time erasure fencing, reducer snapshots and protected forwarding/erasure. Protected forwarding/erasure is unverified. Count-zero success is not protected-read evidence.
- Model-bound and representation depth: #26 other complex context mappings and clear representations beyond direct compiled nullable slice/string-keyed map pointers; #27 derived-child lowering, previously blocked by Chronicle#4564 (fixed in 19.32.1); #64 enum/flags schema and configured serializer profiles, binary/custom representations, complex/concept map keys and GeoJSON. Closed unprotected families do not imply protected polymorphism or safe direct family-valued derivative properties.
- Runtime ownership: Store eviction, lifecycle hooks and custom balancing from #62 are recorded above with their Go differences; #63 runtime registration of other artifact families and replacement profiles. Bounded runtime projection additions and coherent store publication are implemented. Private immutable startup preparation remains a prerequisite (
TestPreparedOutputCompositionNeverRepeatsApplicationPreparation,TestFailedPreparationNeverRetainsPartialOutputs), not a separate authoring framework. Registration sends still do not prove observer attachment. - Outgoing conventions: #65 ordered enrichment, actor/correlation/root-causation providers and high-level identity rename. Explicit metadata is not provider-discovery parity.
- Scenarios and declaration tooling: #38 initial state, richer strict-subscription profiles and lifecycle conformance; #23 generation and #39 declaration analysis. The module policy is delivered; generators/analyzers are not. Manual/runtime/generated equivalence and body-sensitive reducer versions still need executable witnesses.
Cross-client naming and two-generation evolution evidence, independent Arc.Go consumer adoption, whole-kernel restart and multi-node behavior remain distinct from the implemented SDK contracts. Keep upstream restrictions until the exact failing profile has a passing witness; no optional adapter experiment substitutes for this remaining parity work.