---
title: 'CHR0043: Key redirection carries a [PII] value across the compliance subject'
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

## Rule Description

A projection redirects the resolved document key and carries a `[PII]` value onto that document.

A read model document holds exactly one stored compliance subject. The kernel uses an explicit persisted event subject when it differs from the event source id; otherwise it uses the resolved document key. A root `UsingKey`, `UsingKeyFromContext`, composite key, or constant key changes that document key. In a child scope, `UsingKey` only identifies the child; `UsingParentKey`, `UsingParentKeyFromContext`, a parent composite key, or a constant parent key routes the containing document. The model-bound equivalents are `[FromEvent]` and `[ChildrenFrom]`.

Declaring `[Subject]` on the event type is not a suppression. Its runtime value can be null or empty, append metadata can override it, and already-stored historical events retain the subject metadata they were appended with. `EventSourceId` selects the stream; it is the default subject only when no different explicit subject was persisted. The analyzer recognizes the fluent controls that key directly from `EventContext.Subject` or `EventContext.EventSourceId`, because either choice stays aligned with the kernel's explicit-subject-or-resolved-key rule.

PII reach is recursive. A mapped event member counts when `[PII]` occurs directly, on its type, in an inherited member, or inside a nested value object, array, or collection element. The fluent analyzer follows the real public mapping surface: nested expression paths, Set/To, Add/Subtract, AddChild, SetThisValue, `PropertyPath` Set/To, and the final AutoMap state.

Nothing has to fail at read time. The cost can remain hidden as erasure reach: crypto-shredding the owner's key does not touch a copy resting under a different subject, so the value survives an erasure that reports success.

Keep the personal value on a read model keyed and subjected by its owner, or resolve it at the query edge under the owner-scoped read model. Do not move it onto a request-, case-, tenant-, or other subject's document merely to make a query convenient.

## Example

<Tabs syncKey="chronicle-client">
<TabItem label="C#">

```csharp
using System;
using Cratis.Chronicle.Compliance.GDPR;
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections;

[EventType]
public record Chr0043AdvisorNamed(Guid RequestId, [property: PII] string FullName);

public record Chr0043RequestSummary([property: Key] Guid Id, string AdvisorName);

public class Chr0043RequestSummaryProjection : IProjectionFor<Chr0043RequestSummary>
{
    public void Define(IProjectionBuilderFor<Chr0043RequestSummary> builder) => builder
        .From<Chr0043AdvisorNamed>(_ => _
            // Warning CHR0043: the resolved document is routed through RequestId while
            // FullName belongs to the persisted event subject, which may be another identity.
            .UsingKey(e => e.RequestId)
            .Set(m => m.AdvisorName).To(e => e.FullName));
}
```

</TabItem>
</Tabs>

## Existing Stored Documents

Fixing the projection prevents future wrong-subject materialization; it does not remove or re-encrypt copies already stored under the wrong subject. Before claiming erasure coverage, replace the affected read-model storage by dropping and rebuilding the projection into a clean container, rebuilding into a new container and switching readers, or performing an audited migration that proves the owner of every personal value and rewrites or removes each wrong-subject document. Replaying corrected code over the existing container is not sufficient evidence that stale copies are gone.

## Severity

The default severity is **warning**. This is a rollout signal, not a compatibility guarantee or an erasure grace period: repositories that treat warnings as errors can fail immediately, and affected persisted read models still require the cleanup above. Any later promotion to error is a separate compatibility decision; suppressing or retaining warning severity does not make the storage shape safe.

## Suppressing

Suppress with `#pragma warning disable CHR0043` around the declaration, or set `dotnet_diagnostic.CHR0043.severity = none` in `.editorconfig`. Suppressing leaves the value outside its owner's erasure — the defect is not decryptability, so nothing at runtime will report it.
