Rendering a $context path emits members Arc's CommandContext does not have, so the rendered application does not compile.
What happens
ExpressionRenderer renders every context expression by PascalCasing the declared path against a fixed parameter named context:
ContextExpressionSyntax context => RenderContextPath(context.Path), // "context." + PascalCase(path)
EventSourceIdExpressionSyntax => "context.EventSourceId",
CausedByExpressionSyntax causedBy => $"context.CausedBy.{…}",
That is right where Chronicle's EventContext is in scope — a reactor method, a projection. It is wrong in a command handler, which is where most $context reads in a real document land. A rendered command handler is an Arc model-bound Handle(), and Arc's Cratis.Arc.Commands.CommandContext carries CorrelationId, Type, Command, Dependencies, Values, AllowedSeverity, Response, ServiceProvider, CancellationToken — and none of Occurred, Identity, Tenant, CausedBy, Causation.
Those five are declared on Screenplay's own Cratis.Screenplay.Contexts.CommandContext, a different type that a rendered application never receives. The two share a simple name, so the renderer's using Cratis.Arc.Commands; binds the wrong one and every read fails to compile.
Measured
Compiled Screenplay/Source/DotNET/Screenplay/for_ScreenplayCompiler/invoicing.play (17 slices) with ScreenplayCompiler, rendered it with CratisRenderer.CreateDefault()'s renderer set into InMemoryCodeOutput, and compiled the result in memory against the real Cratis assemblies plus the global usings the Cratis package contributes.
30 compilation errors. 15 of them are the renderer's own rendering:
| Rendered |
Errors |
Files |
context.Occurred |
11 |
RegisterInvoice, CancelInvoice, ChangeInvoiceStatus, ApplyDiscount, WriteOffInvoice |
context.Identity.Id |
2 |
RegisterInvoice, CancelInvoice |
context.Tenant |
1 |
RegisterInvoice |
context.CausedBy.Subject / context.Causation.Type |
1 |
RegisterInvoice, CancelInvoice |
The other 15 come from authored csharp blocks in the document and from InvoiceLineItemAdded, an event the document references but never declares — separate problems, listed below.
Why it is the "invented value" case
The document says registeredAt = $context.occurred. The renderer answers with a member access that does not exist, rather than with the value or with a report. Nothing in the output says the value was not delivered — the file simply does not build, and it builds less the more carefully the document states its provenance.
What a rendered handler can actually reach
Every path the language names has a real counterpart the Cratis runtime registers; a command handler's parameters resolve from DI by type, so asking for one is how a hand-written slice reaches the same value:
| Path |
Rendering |
Collaborator |
$context.occurred |
DateTimeOffset.UtcNow |
— |
$context.tenant |
tenants.Current.Value |
Cratis.Arc.Tenancy.ITenantIdAccessor |
$context.identity.id / $context.causedBy.subject |
identities.GetCurrent().Subject |
Cratis.Chronicle.Identities.IIdentityProvider |
$context.identity.name / .userName |
.Name / .UserName |
same |
$context.identity.isAuthenticated |
principals.Current?.Identity?.IsAuthenticated == true |
Cratis.Arc.Authorization.ICurrentPrincipalAccessor |
$context.identity.roles |
(principals.Current?.FindAll(ClaimTypes.Role) ?? []).Select(…) |
same |
$context.identity.claims.<name> |
principals.Current?.FindFirst("<name>")?.Value ?? string.Empty |
same |
$context.causation.type |
causations.GetCurrentChain()[^1].Type.Value |
Cratis.Chronicle.Auditing.ICausationManager |
$context.command.<property> |
the command's own property |
— |
$eventContext.* and $eventSourceId have no counterpart in a command handler at all — it runs before anything is appended — so those are drops, not renderings.
IIdentityProvider has to be named in full. The Cratis package's GlobalUsings.cs brings in both Cratis.Arc.Identity and Cratis.Chronicle.Identities, and each declares an IIdentityProvider, so the short name is ambiguous in every rendered file whether or not the renderer adds a using of its own. This is only visible in a compilation that includes the package's global usings — the spec harness did not, which is why no spec saw it.
A context value can also be unfillable. invoicing.play maps registeredFor = $context.tenant onto an event property declared as TenantId, a Uuid concept. The runtime's tenant is a string, and no conversion exists in either direction. That is a genuine drop: it has to be reported, not rendered into a cast that cannot compile.
Not this issue, found by the same run
Listing them so the remaining 15 errors on invoicing.play are not read as this one:
- Authored
csharp blocks (11 errors). ProcessInvoiceBatch and the DetectOverdueInvoices reactor are handled by inline code written against Screenplay's contexts. A rendered application has no Cratis.Screenplay reference and receives Arc's CommandContext, so context.Identity.Id in an authored block cannot compile either. The block also references types from sibling slices (InvoiceSent, InvoiceCancelled, MarkInvoiceOverdue) that the renderer does not emit usings for, because it does not analyze raw code text. Whether a rendered application should reference Cratis.Screenplay so authored blocks compile against the context they were written for is a decision, not a defect.
InvoiceLineItemAdded (3 errors). The document projects from an event it never declares. Already reported by the renderer; the emitted [FromEvent<InvoiceLineItemAdded>] still names it.
- The
DetectOverdueInvoices block itself reads DueDate and InvoiceId unqualified and has a code path that returns nothing — the document's own C#, emitted verbatim.
Rendering a
$contextpath emits members Arc'sCommandContextdoes not have, so the rendered application does not compile.What happens
ExpressionRendererrenders every context expression by PascalCasing the declared path against a fixed parameter namedcontext:That is right where Chronicle's
EventContextis in scope — a reactor method, a projection. It is wrong in a command handler, which is where most$contextreads in a real document land. A rendered command handler is an Arc model-boundHandle(), and Arc'sCratis.Arc.Commands.CommandContextcarriesCorrelationId,Type,Command,Dependencies,Values,AllowedSeverity,Response,ServiceProvider,CancellationToken— and none ofOccurred,Identity,Tenant,CausedBy,Causation.Those five are declared on Screenplay's own
Cratis.Screenplay.Contexts.CommandContext, a different type that a rendered application never receives. The two share a simple name, so the renderer'susing Cratis.Arc.Commands;binds the wrong one and every read fails to compile.Measured
Compiled
Screenplay/Source/DotNET/Screenplay/for_ScreenplayCompiler/invoicing.play(17 slices) withScreenplayCompiler, rendered it withCratisRenderer.CreateDefault()'s renderer set intoInMemoryCodeOutput, and compiled the result in memory against the real Cratis assemblies plus the global usings theCratispackage contributes.30 compilation errors. 15 of them are the renderer's own rendering:
context.Occurredcontext.Identity.Idcontext.Tenantcontext.CausedBy.Subject/context.Causation.TypeThe other 15 come from authored
csharpblocks in the document and fromInvoiceLineItemAdded, an event the document references but never declares — separate problems, listed below.Why it is the "invented value" case
The document says
registeredAt = $context.occurred. The renderer answers with a member access that does not exist, rather than with the value or with a report. Nothing in the output says the value was not delivered — the file simply does not build, and it builds less the more carefully the document states its provenance.What a rendered handler can actually reach
Every path the language names has a real counterpart the Cratis runtime registers; a command handler's parameters resolve from DI by type, so asking for one is how a hand-written slice reaches the same value:
$context.occurredDateTimeOffset.UtcNow$context.tenanttenants.Current.ValueCratis.Arc.Tenancy.ITenantIdAccessor$context.identity.id/$context.causedBy.subjectidentities.GetCurrent().SubjectCratis.Chronicle.Identities.IIdentityProvider$context.identity.name/.userName.Name/.UserName$context.identity.isAuthenticatedprincipals.Current?.Identity?.IsAuthenticated == trueCratis.Arc.Authorization.ICurrentPrincipalAccessor$context.identity.roles(principals.Current?.FindAll(ClaimTypes.Role) ?? []).Select(…)$context.identity.claims.<name>principals.Current?.FindFirst("<name>")?.Value ?? string.Empty$context.causation.typecausations.GetCurrentChain()[^1].Type.ValueCratis.Chronicle.Auditing.ICausationManager$context.command.<property>$eventContext.*and$eventSourceIdhave no counterpart in a command handler at all — it runs before anything is appended — so those are drops, not renderings.IIdentityProviderhas to be named in full. TheCratispackage'sGlobalUsings.csbrings in bothCratis.Arc.IdentityandCratis.Chronicle.Identities, and each declares anIIdentityProvider, so the short name is ambiguous in every rendered file whether or not the renderer adds a using of its own. This is only visible in a compilation that includes the package's global usings — the spec harness did not, which is why no spec saw it.A context value can also be unfillable.
invoicing.playmapsregisteredFor = $context.tenantonto an event property declared asTenantId, aUuidconcept. The runtime's tenant is a string, and no conversion exists in either direction. That is a genuine drop: it has to be reported, not rendered into a cast that cannot compile.Not this issue, found by the same run
Listing them so the remaining 15 errors on
invoicing.playare not read as this one:csharpblocks (11 errors).ProcessInvoiceBatchand theDetectOverdueInvoicesreactor are handled by inline code written against Screenplay's contexts. A rendered application has noCratis.Screenplayreference and receives Arc'sCommandContext, socontext.Identity.Idin an authored block cannot compile either. The block also references types from sibling slices (InvoiceSent,InvoiceCancelled,MarkInvoiceOverdue) that the renderer does not emit usings for, because it does not analyze raw code text. Whether a rendered application should referenceCratis.Screenplayso authored blocks compile against the context they were written for is a decision, not a defect.InvoiceLineItemAdded(3 errors). The document projects from an event it never declares. Already reported by the renderer; the emitted[FromEvent<InvoiceLineItemAdded>]still names it.DetectOverdueInvoicesblock itself readsDueDateandInvoiceIdunqualified and has a code path that returns nothing — the document's own C#, emitted verbatim.