---
title: Chronicle.Go parity
editUrl: https://github.com/Cratis/Chronicle.Go/edit/main/Documentation/parity.md
description: Implemented Go client contracts, executable evidence and deliberate C# differences.
lastUpdated: 2026-10-07T00:00:00.000Z
---


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](#baselines-and-evidence), then the relevant capability. Migration guidance covers [connections](#connection-api-and-default-comparison), [constraints](#constraint-api-translation) and [read models](#read-model-migration); [remaining work](#next-slices) separates gaps from delivered APIs.

## 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](/chronicle/go/identities/) for prerequisites,
errors, listing cost and acknowledgment limitations.

## 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](/chronicle/go/projections/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

| 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](/chronicle/go/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

| 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](https://github.com/Cratis/Fundamentals/issues/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](/chronicle/go/derived-codecs/). Enum profiles beyond
the declared Int32 subset, binary map/derived/concept/protected placements,
custom representation, complex map-key and GeoJSON work stays under
[#64](https://github.com/Cratis/Chronicle.Go/issues/64). Derived children are
described in [project derived children](/chronicle/go/derived-codecs/#project-derived-children).

## 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](/chronicle/go/patterns/). 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

| 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](/chronicle/go/facade-composition/#use-one-application-owned-logger).

## 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](https://github.com/Cratis/Chronicle.Go/issues/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](https://github.com/Cratis/Chronicle.Go/issues/80).

## 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](https://github.com/Cratis/Arc.Go/issues/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

The binary slice admits only these acyclic, ASCII-named shapes:

- Event binary leaves (`[]byte`, including named slices over built-in `byte`
  without custom codecs) and one nullable pointer: captured `BinaryEvent.Payload`
  / `Optional` writes, reads and schemas in `serialization/testdata/binary/profile.json`,
  plus `BinaryChanged.Payload` / `Optional` kernel readbacks.
- Binary event leaves in acyclic nested objects, including pointers to objects:
  captured `BinaryEvent.Nested.Inner` and the kernel event-history readback of
  `BinaryChanged.Nested.Inner`.
- Read-model binary leaves or one nullable pointer emitted at the root, including
  promoted embedded leaves: captured `BinaryModel.Payload` / `Optional` schemas
  and Expando conversion pairs, plus `BinaryWitnessModel.Payload` / `Optional`
  kernel 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: `TestKernelBinaryEventsProjectionAndReadModel` uses only From for
  root `Payload` / `Optional` under 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](#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](https://github.com/Cratis/Chronicle.Go/blob/main/serialization/testdata/binary/README.md); `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](https://github.com/Cratis/Chronicle/issues/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](/chronicle/go/events/enrichment/) |
| 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](#identity-display-name-observation) |
| 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](#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](https://github.com/Cratis/Chronicle/issues/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](#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](#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](#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](#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

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. `TestKernelModelHistoryProjectionCollectionsAndCorrelationGroups`
  proves materialized absence/deletion, bounded/passive replay, namespace isolation,
  A/B/A global grouping, first-event occurrence and ambiguous `{}` deletion history.
  `TestKernelModelHistoryReducerCollectionRoutingAndDeletion` separates 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](https://github.com/Cratis/Chronicle/issues/4562)).
  `TestMixedAllHistoryRequiresMixedAllReplayKernel` (refused on older or unverified kernels) and `TestKernelModelHistoryMixedAndPureAllReplay`
  cover 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](https://github.com/Cratis/Chronicle/issues/4561)),
  and the SDK validates without a second decrypt
  (`TestProtectedProjectionRoutesUseKernelReleaseWithoutSecondDecryption`).
  `TestKernelModelHistoryProtectedReleaseProfile` exercises every route through the
  SDK for default `Id` and lowercase `id`, 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.
  `TestKernelModelHistoryReleaseOwnershipAndErasure` covers admitted collection
  routes and keyed passive folds. These witnesses are not general server security
  assurance and do not complete #33's protected-history capability.
  Explicit `Service.Release` remains an unreleased-document API with lineage
  groups. Legacy `PassiveReader` retains that contract;
  `WithReleasedPassiveReader` explicitly selects plaintext instead.
- **Decode and lifetime evidence:** `TestCollectionAndSnapshotsUseFrozenNamingAndPointerConcepts`,
  `TestCollectionRejectsLateErrorsWithoutPartialProgress`,
  `TestSnapshotsRejectLateFailuresAndEnvelopeFailures`,
  `TestReadDocumentsRejectDuplicateMembersBeforePublication`,
  `TestHistoryCodecFailuresArePayloadFreeAndAtomic`, and
  `TestModelHistoryCodecsCanCloseClientAndLateCancellationDiscardsEverything`.
  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.Instance` has no invented existence or
  last-handled marker. Catalog-only producers cannot establish replay fidelity.
  `TestProjectionReplayPolicyOwnsBoundReplacementAndInboxDefinitions` verifies
  immutable admission from the actual selected store's bound definitions/models,
  including replacement registries and inbox sequence binding.

See the [collection/history reference](/chronicle/go/read-models/collections-and-history/).

## 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](/chronicle/go/events/history-revision/) 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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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

The [operations reference](/chronicle/go/operations/) 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](https://github.com/Cratis/Chronicle.Go/blob/main/testdata/observation/README.md),
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

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

C102–C106 references below use `Source/Clients/DotNET` at
`2e31b0dfba489159b3db323238f16d0f277056b4`; kernel paths are repository-root
paths at the same revision. [Compliance reference](/chronicle/go/compliance/) 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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/4623)); the SDK has no client-side guard for this yet ([#90](https://github.com/Cratis/Chronicle.Go/issues/90)), so the [evolution guide](/chronicle/go/events/evolution/#protected-events-across-generations) 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

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](https://github.com/Cratis/Chronicle/issues/4562) |
| Revising an event with a protected replacement | 19.32.2 | [Chronicle#4525](https://github.com/Cratis/Chronicle/issues/4525) |
| Classified models on projection replay, history, hydration sessions and immediate reads | 19.32.2 | [Chronicle#4561](https://github.com/Cratis/Chronicle/issues/4561) |
| Registering protection beneath maps or on list- or map-valued array elements | 19.32.2 | [Chronicle#4551](https://github.com/Cratis/Chronicle/issues/4551), [Chronicle#4552](https://github.com/Cratis/Chronicle/issues/4552) |
| Decision reads (any model) | exactly 19.32.3 | characterized release |

## Definition factories

C02–C08/C10 and issues [#21](https://github.com/Cratis/Chronicle.Go/issues/21) /
[#22](https://github.com/Cratis/Chronicle.Go/issues/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](/chronicle/go/definition-factories/).

| 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

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](/chronicle/go/events/seeding/) 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](#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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/4558), fixed in 19.32.3); [reactors](/chronicle/go/reactors/#wait-for-observers-before-the-first-append) 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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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

C113–C114 references are relative to `Source/Clients/DotNET` at
`2e31b0dfba489159b3db323238f16d0f277056b4`; kernel evidence uses the pinned
19.32.3-development image. See [External integrations](/chronicle/go/integrations/)
and [Capture declarations](/chronicle/go/integrations/captures/).

| 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](/chronicle/go/integrations/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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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](/chronicle/go/integrations/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](https://github.com/Cratis/Chronicle/issues/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

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](https://github.com/Cratis/Chronicle/issues/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](/chronicle/go/read-models/#materialized-windows) and [compliance boundaries](#compliance-and-confidentiality) |
| 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](/chronicle/go/read-models/collections-and-history/) |

## 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](https://github.com/Cratis/Chronicle/issues/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](#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](/chronicle/go/events/event-types/#model-bound-event-metadata) for the public
options and [constraints](/chronicle/go/events/constraints/#model-bound-constraints) for
composition and migration guidance.

## 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](/chronicle/go/events/evolution/) and [offline example](https://github.com/Cratis/Chronicle.Go/blob/main/examples/evolution/main.go).

| 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](https://github.com/Cratis/Chronicle/issues/4623)). No SDK guard yet ([#90](https://github.com/Cratis/Chronicle.Go/issues/90)); documented in the [evolution guide](/chronicle/go/events/evolution/#protected-events-across-generations) |

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

1. **TLS validates by default.** C#'s local profile skips validation. Add `WithDevelopmentDefaults` only for development; configure real roots/client certificates with `WithTLS` for production. Explicit validating TLS wins over security opt-outs.
2. **Root registration is explicit and snapshotted.** Use `RegisterEvent[T]`, `RegisterReadModel[T]` and `Registry.AddConstraint` before `NewClient`. Model-bound projection tags and typed type-level options preserve attribute-driven declarations alongside the fluent builder; only root admission replaces assembly scanning. No global `init` registry. `WithRegistryForStore` replaces, rather than merges with, the default catalog.
3. **Context replaces ambient state.** Pass `metadata.WithCorrelation`, `WithIdentity` and `WithCausation`; the client does not invent application/program audit facts. Namespace selection remains explicit and does not grant tenant access.
4. **Errors describe write ambiguity.** Domain rejection is a result; operation failures remain errors. Unknown outcomes are never represented as known rejection. `Err` exposes both constraint and concurrency categories; no transparent append retry is added.
5. **Unsupported options fail.** API keys, plaintext and URI certificate/password formats return `ErrUnsupported`, rather than silently skipping security or routing behavior. Use `WithTLS` for PEM material.
6. **Serialization has a deliberately bounded schema contract.** Fundamentals `Concept[T]` and standard codecs replace `ConceptAs<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 above `MaxInt64` fail before dispatch because the kernel MongoDB append path parses bare JSON integers as signed BSON integers ([Chronicle#4588](https://github.com/Cratis/Chronicle/issues/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 through `double`. Use typed struct properties or strings for wider values. Nullable pointer schemas preserve absence; `omitzero` follows Go `IsZero` methods, but omitted non-nullable fields may materialize as kernel defaults.
7. **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; `AppendBatch` supports 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.
8. **Resource ownership is explicit.** Constructors are lazy; `Dial` performs preflight. Close cancels/joins owned work but never closes borrowed channels/token sources. `EventSequence` returns a construction error for invalid IDs rather than publishing an invalid handle. Scalar options are last-wins; conflicting auth modes are errors.
9. **Generation validation matches C# defaults.** Registrations include every concrete schema in `Generations` and default to disabling schema/migration validation, allowing generations above one without migrations. Use `WithEventTypeGenerationValidation(true)` to protect existing generation schemas; `NewClient` then 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.
10. **The default namespace is `Default`.** This corrects the earlier lowercase `default` value to match C# and the kernel. MongoDB rejects namespaces differing only by case; callers must use the kernel spelling or explicitly select another namespace.
11. **Recovery is supervised, not application replay.** Slice 3 replaces slice 1's explicit-connect-after-loss behavior with automatic generation replacement, following `Connections/ChronicleConnection` and `ConnectionWatchdog` at 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 explicit `Connect`; transient reconnect runs with time-bounded attempts and capped jittered backoff until shutdown.
12. **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](#runtime-projection-additions), `Published` records retained local admission separately from stage acknowledgements; other artifact families and replacement remain unsupported.
13. **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 unchanged `Close() error` remains an unbounded cancellation/join convenience. Borrowed channels retain caller ownership.
14. **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.
15. **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 explicitly `Resolve` an 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 becomes `ErrUnsupported`, not a no-op success.
16. **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. `TestKernelOrderedAtomicBatchAndHistory` records this server behavior; `TestReadPreservesEnvelopeMetadataAndGenerations` proves 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](https://github.com/Cratis/Chronicle.Go/blob/main/examples/batches/main.go) also uses the public production APIs.

## 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](/chronicle/go/events/evolution/)
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](/chronicle/go/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

Register the subject policy once alongside the event schema:

```go
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

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](#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](https://github.com/Cratis/Chronicle.Go/issues/62) hooks and balancing are implemented with the deliberate Go ownership/dispatch differences above. See [connection hooks](/chronicle/go/connection-strings/lifecycle/#connection-hooks) and [custom load balancing](/chronicle/go/connection-strings/#custom-load-balancing). Sink defaults are recorded below and captured process/software audit metadata in the enrichment section |

## 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

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

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.

1. **Separate participant and owner.** Replace ambient `IUnitOfWorkManager.Begin/Current/SetCurrent/TryGetFor` with `transactions.Begin` and explicit participant context accessors. Go has no `AsyncLocal`; there is no global correlation registry. `UnitOfWork.Stage` translates `AddEvents`. Only the separately retained `Owner` exposes `Commit`/`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.
2. **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 explicit `Resolve` is not C#'s absent `NotSet`: it includes a filter and cannot overwrite an enrolled expectation. Use omission for the default strategy.
3. **Inspect final disposition, not completion alone.** `Commit` returns the existing Go `BatchResult` and operation error; `Result`, C#-named diagnostic getters, `IsSuccess` and `TryGetLastCommittedEventSequenceNumber` preserve the query workflow. `GetEvents` returns 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.
4. **Explicit rollback instead of disposal.** Defer `Owner.Rollback` for 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 with `ErrCompleting`. Rollback reports `RolledBack` and is not `IsSuccess`, avoiding C#'s success-shaped empty append result being mistaken for commitment.
5. **Completion notification retains the C# name and replacement semantics.** `OnCompleted` replaces the callback and runs synchronously after final state, including commit failure and rollback. Go rejects late registration with `ErrCompleted` rather 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](/chronicle/go/events/unit-of-work/) and [executable example](https://github.com/Cratis/Chronicle.Go/blob/main/examples/unitofwork/main.go) 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](/chronicle/go/read-models/decision-reads/) supply opaque optimistic tokens; ordinary concurrency scopes must not be presented as decision tokens.

## 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](https://github.com/Cratis/Chronicle/issues/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](#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](/chronicle/go/read-models/decision-reads/) and [typed example](https://github.com/Cratis/Chronicle.Go/blob/main/examples/decisions/main.go) cover admitted reads, explicit detached enrollment, checked scopes and the optimistic-versus-authenticated boundary.

## 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](https://github.com/Cratis/Chronicle.Go/blob/main/examples/constraints/main.go) uses the same production registry and append APIs.

- **Choose model-bound declarations or explicit definitions.** Field `unique` tags, `events.WithUnique` and remover options compile through the same builders as `UniqueValues(name).On(descriptor, paths...)`. Explicit paths are exact serialized JSON names validated against the descriptor; tags use the shared field plan. `Registry.ConfigureDeclaredConstraint` explicitly composes a declared constraint, while duplicate `AddConstraint` calls fail rather than silently merge. `TestCompositeDeclarationImprovementMatchesExplicitBuilderGolden` and `TestDeclaredConstraintsRequireExplicitComposition` cover this boundary. Same-event, same-name fields form one ordered composite in Go; the pinned C# provider instead throws on its repeated `On` call. 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.Unique` and `constraints.UniqueEventType` remain result-kind constants; builders are named `UniqueValues` and `UniqueEventTypes` to 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 across `AddConstraint` calls 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. `TestMessageDetailValuesAreLiteral` fixes 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 the `constraints` stage; the RPC returns `google.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](#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.New` has no registry/message provider and preserves kernel text; obtain sequences from `EventStore` for registered message resolution. Claim removal and batch ordering are server behavior, not emulated by a Go test fake.

## 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`.

1. **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, `Client` owner, `Code` source 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. Repeated `WithIndexes` and `WithPII` calls accumulate copied property paths in declaration order and reject duplicates within or across calls with `ErrInvalidConfiguration`. [Runtime projection additions](#runtime-projection-additions) admit a new ordinary model together with its new projection; no standalone runtime `RegisterReadModel[T]` or replacement API is provided.
2. **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, use `WithIdentifier("CSharp.Namespace.Model")`. `WithContainerName` corresponds to C#'s `ReadModelNameAttribute`, not a model identifier override. Generic Go instantiations require both `WithIdentifier` and `WithContainerName` explicitly: reflection's argument-qualified type names are not stable persisted identities or valid conventional container names. `TestGenericReadModelRequiresExplicitNames` covers 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 Go `ID` becomes C# `Id`; nested fields and other initialisms retain their policy names. `TestNamingPoliciesCompileOneFieldPlan` and `TestReadModelPlanTranslatesOnlyUntaggedRootID` cover 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.
3. **Presence and progress are separate.** `Instance[T].Exists` replaces a nullable model; `LastHandled` replaces C#'s injected `__lastHandledEventSequenceNumber` property. 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/typed `GetAll`, materialized paging, watches and admitted projection snapshots are available. Their count, presence and replay rules differ: use the [collection/history reference](/chronicle/go/read-models/collections-and-history/) and [watch reference](/chronicle/go/read-models/#watch-changes). Reducer snapshots remain unsupported.
4. **Sessions have explicit cleanup contexts.** `NewSession` creates 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 not `io.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 by `TestReadModelSessionRetainsCoordinatesAndRetriesFailedCleanup`.
5. **Release fails closed.** C# returns unchanged values for missing subjects or reported release failure. Go returns `ErrRelease` with 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](/chronicle/go/read-models/#materialized-windows)). Classified projection Watch refuses before streaming ([Chronicle#4566](https://github.com/Cratis/Chronicle/issues/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 `__subject` taking precedence over a configured property and the Go `ID` fallback. 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 kernel `ReadModelsCompliance.HandleBySubject`, top-level properties are grouped by their `__subjects` entry, 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](/chronicle/go/read-models/), `ExampleRegisterReadModel` and [runnable example](https://github.com/Cratis/Chronicle.Go/blob/main/examples/readmodels/main.go) 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

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

- `declarations.Parse(declarations.V1, tag)` is the shared, bounded parser. `FuzzParse`, grammar/error tests and `TestDeclarationsOnIgnoredFieldsFailClosed` cover 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. `TestFieldMetadataSharesPlanAndOwnsSnapshots` covers snapshot isolation. Fundamentals wrapper compatibility is centralized in `scalarCompatible` and `concepts.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 typed `BindEvent` alias. 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. `TestModelBoundPrecedenceAndFluentDuplicateRejection` fixes 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. `TestFluentBuildSnapshotsValuesAndCallbacks` and 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; `TestProjectionDiscoveryInheritsModelDefaults` and `TestFluentBuilderInheritsModelDefaults` cover both front ends. Zero `Definition` accessors return zero metadata (`TestZeroDefinitionAccessors`); `WithIdentifier` or a builder ID selects shared C# identities. Container naming remains a read-model setting.
- `NewClient` fails 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. `TestProjectionStartupValidationIsAtomicAndStoreLocal` proves forward references and failure atomicity. Models with mapping tags discover projections; type-level options require `AddProjection`. Explicit foreign model handles and conflicting producer settings fail.
- Registration follows event types and read models, in the observers/constraints portion of the pass. `TestProjectionDiscoveryRegistrationAndFrozenReplay` proves store-wide sharing, producer binding, read barriers, unchanged reconnect definitions and namespace isolation. `TestProjectionFailureCannotPublishReadiness` proves old-kernel rejection. Nonempty projection sets register with `Client` ownership and `FullSet=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}.cs` at the revision above restricts literals to BMP letters, combining marks, decimal digits, connector punctuation, space and `._/:*+-`. `TestKernelUnrepresentableModelMappingsFailAtNewClient`, `TestKernelUnrepresentableKeysFailAtNewClient` and `TestFluentLiteralUTF16Boundary` cover UTF-16 limits. Exact event paths `true`, `True`, `false` and `False` fail because the kernel resolves them as boolean literals first; `TestBooleanPropertyPathsRejectLiteralResolverAmbiguity` covers 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](#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](#registered-derived-codecs); protected families and other custom representations remain unsupported.

The [projection guide](/chronicle/go/projections/) presents model-bound declarations first and fluent declarations second. `TestProjectionSnippetsMatchCompiledExample` prevents drift from the [executable example](https://github.com/Cratis/Chronicle.Go/blob/main/examples/projections/main.go).

## 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](https://github.com/Cratis/Chronicle.Go/issues/38) owns scenario fidelity. No test-only initial-state compiler |

The [initial-values guide](/chronicle/go/projections/defaults/) and compiling stock example
are tied together by `TestProjectionDefaultsSnippetMatchesCompiledExample`.

## 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](#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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/4263)); nested join removal is also unwired ([Chronicle#4125](https://github.com/Cratis/Chronicle/issues/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](/chronicle/go/projections/orders/) 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

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](#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](https://github.com/Cratis/Fundamentals/issues/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](#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](https://github.com/Cratis/Chronicle/issues/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](/chronicle/go/projections/variants/) 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](#registered-derived-codecs)
for admitted unprotected families; those codecs do not implement derived-child
lowering. Subscription provisioning is covered under
[external integrations](#external-integrations).

## 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](/chronicle/go/reactors/) 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](#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](#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](#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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle/issues/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](#observer-and-job-administration) distinguishes a retry request from completed recovery |

## 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](#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](#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](#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](/chronicle/go/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

References use C# `2e31b0dfba489159b3db323238f16d0f277056b4`, relative to
`Source/Clients/DotNET`. The [reducer guide](/chronicle/go/reducers/) 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](https://github.com/Cratis/Chronicle/issues/4548)
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](#definition-factories) and [facade composition](/chronicle/go/facade-composition/). These SDK witnesses do not cover Arc.Go#29 adoption or the separate [runtime projection addition API](#runtime-projection-additions); provider disposal does not transfer ownership of borrowed client resources |

### 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](https://github.com/Cratis/Chronicle/issues/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

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`](/chronicle/go/testing-with-scenarios/); 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

Delivered event declarations, historical catalogs, operations, bounded folds and
facade helpers are not future slices. The remaining porting work is:

- **Safety:** [#48](https://github.com/Cratis/Chronicle.Go/issues/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](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle.Go/issues/18) sequence sentinels, predicates and checked pure arithmetic are implemented in the sequence-number row above, not future work.
- **Read and protection routes:** [#33](https://github.com/Cratis/Chronicle.Go/issues/33) / [#35](https://github.com/Cratis/Chronicle.Go/issues/35) remaining unsupported window profiles, classified projection Watch blocked by [Chronicle#4566](https://github.com/Cratis/Chronicle/issues/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](https://github.com/Cratis/Chronicle.Go/issues/26) other complex context mappings and clear representations beyond direct compiled nullable slice/string-keyed map pointers; [#27](https://github.com/Cratis/Chronicle.Go/issues/27) derived-child lowering, previously blocked by Chronicle#4564 (fixed in 19.32.1); [#64](https://github.com/Cratis/Chronicle.Go/issues/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](https://github.com/Cratis/Chronicle.Go/issues/62) are recorded above with their Go differences; [#63](https://github.com/Cratis/Chronicle.Go/issues/63) runtime registration of other artifact families and replacement profiles. Bounded [runtime projection additions](#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](https://github.com/Cratis/Chronicle.Go/issues/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](https://github.com/Cratis/Chronicle.Go/issues/38) initial state, richer strict-subscription profiles and lifecycle conformance; [#23](https://github.com/Cratis/Chronicle.Go/issues/23) generation and [#39](https://github.com/Cratis/Chronicle.Go/issues/39) declaration analysis. The [module policy](/chronicle/go/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.
