Onboarding rendering
A model’s onboarding block says how people get into the application. An application rendered with it runs beside its own Ante, which presents sign-up and invitations, issues invitation tokens and publishes what happened. Ante sends no mail and creates nothing in the application, so the rendered application acts on its outcomes. This is provisioning for the application’s own model only.
What the renderer emits
Section titled “What the renderer emits”The files below are added to the application, in the namespace <Root>.GeneratedOnboarding.
| File | What it does |
|---|---|
AnteOutcomes.cs | A reactor on InvitationTokenIssued, InvitationRejected, InvitationToJoinTenantAccepted, InvitationToCreateTenantAccepted, OrganizationRegistrationCompleted, OrganizationRegistrationDeclined and LegalTermsAccepted from the Ante store Ante. |
Provisioning.cs | Creates the tenant and records members, once per outcome. |
OnboardingEmails.cs | Sends the invitation, welcome and sign-up declined emails. |
OnboardingInvitations.cs | The command that invites a member, and the policy that lets the personas invite member … by send it. Only rendered when the model names inviters. |
InvitationPublisher.cs | A reactor that appends the invitation to the application’s outbox for Ante, once per invitation. |
MemberPersonaClaims.cs | The only source of persona roles and claims: replaces those arriving with the sign-in with the personas granted in the tenant of the request. |
OnboardingRules.cs | The personas an invitation may grant and who may send it. |
OnboardingEvents.cs | The application’s own events: tenant provisioned, member provisioned, legal terms accepted, email queued. |
OnboardingRegistration.cs | Registers Herald, Box Office when there is an offering, the reactor, the services and the policy. |
OnboardingEnvironment.cs | Reads the environment values. |
OnboardingOffering.cs | A reactor that starts a new tenant on the default offering through Box Office. Only rendered when the model names an offering. |
Emails/ | The templates and images of the model, laid out the way Herald reads them. |
.runtime/ante.onboarding.json | The settings of Ante, in the shape of its Ante:Onboarding contract. |
The project references Cratis.Ante.Contracts and Cratis.Showrunner.Herald, and Cratis.Showrunner.BoxOffice when the model names an offering. Herald reads the event store name from the client options, which an ASP.NET Core host leaves empty, so OnboardingRegistration sets it to the application’s store. Cratis.Ante.Contracts requires a newer Chronicle client than the other renderings pin, so an application with onboarding pins that client and the matching kernel image in docker-compose.yml; every other application is unchanged.
Provisioning
Section titled “Provisioning”Ante’s Accepted means published to its outbox, not provisioned in the application. Every outcome is therefore recorded with AppendOnce, under an idempotency key made of the outcome and the invitation or registration id, so a delivery that arrives again provisions nothing twice.
InvitationToCreateTenantAcceptedandOrganizationRegistrationCompletedcreate the tenant, which is the Chronicle namespace named by the tenant, and record its first member. The member holds the persona ofinvite tenant as <Persona>; a sign-up uses the same persona.InvitationToJoinTenantAcceptedrecords a member with the personas the invitation granted.LegalTermsAcceptedrecords the version of the terms that was accepted.
The personas granted are checked against what the model allows: an invitation to create a tenant can give only its as persona and an invitation to join only the grant personas. Ante enforces the same list; the application does it again, and the command that invites a member rejects a persona outside grant before anything is published. MemberPersonaClaims is the only source of persona roles and claims. It removes persona roles and claims that arrive with the sign-in and adds the ones the member was granted in the tenant of the request. Roles the identity provider sends that no persona uses still reach the application unchanged.
Invitations
Section titled “Invitations”invite member … by A, B renders InviteMemberToTenant(Email, Personas). A caller who holds persona A or B in the tenant can send it. The command records OnboardingMemberInvited (the address is personal data) in the tenant of the caller, under a new invitation id, because a command cannot append to another namespace while read protection is on. InvitationPublisher then appends UserInvitedToJoinTenant under the same id to the application’s outbox in the default namespace, which is where Ante observes it.
An invitation to create a tenant names no inviting persona, so no command is rendered for it (STAGE-ONBOARDING-002); the operator invites a tenant by appending UserInvitedToCreateTenant to the outbox.
Emails
Section titled “Emails”The three email kinds map to the Herald kinds invitation, welcome and signup-declined. Each template is read from the model, given a subject when it has no header, and written as Emails/<kind>.<language>.html next to a manifest Emails/<kind>.json that declares the values the application passes:
| Kind | Values |
|---|---|
invitation | invitation.link (required), invitation.expiresAt, invitation.tenant, application.name |
welcome | member.name, tenant.name, application.name, application.link |
signup-declined | application.name, decline.message |
The language is the default locale of the model’s strings, otherwise en. Herald refuses a template that uses a value the manifest does not declare. Images are copied from the model’s images folder to Emails/images; refer to them with cid:<file>.
The invitation link is /invite/{token} on the public origin of AuthProxy, read from Cratis:Application:PublicBaseUrl. The sender and the mail server come from the environment keys onboarding.email.sender and onboarding.email.server.host, .port, .username and .password; the password is a secret. When CRATIS_MAIL_PICKUP_FOLDER is set, Herald writes .eml files there instead of sending.
Each kind is sent once per invitation or registration, Herald records what it queued, and a marker records the request. An invitation whose token has an unknown or past expiry is not announced. The sign-up declined email is sent when Ante publishes OrganizationRegistrationDeclined (Ante 1.6.0): to the address of the sign-up, with the reviewer’s message as decline.message, once per registration. A sign-up without an address is not answered.
Ante publishes that outcome only when sign-up review is on (Ante:Registration:Review). The Screenplay model has no syntax for review yet, so a rendered application leaves it off and .runtime/ante.onboarding.json does not mention it. A host that turns it on gets OrganizationRegistrationAwaitingReview in its inbox and has to decide, by appending OrganizationRegistrationApproved or OrganizationRegistrationDeclineRequested(Message) to its outbox under the registration id. Rendering those commands is not done. An invitation Ante rejects, because it asks for a persona or an invitation type the model does not allow, is a mistake in the invitation rather than a decision about the person. The application records it as OnboardingInvitationRejected on the invitation in the default namespace and logs a warning for an administrator; the invitee is not emailed.
Offering
Section titled “Offering”offering <Name> renders the product’s side of Box Office. The project references Cratis.Showrunner.BoxOffice, which brings Cratis.Showrunner.BoxOffice.Offering and supplies the offers the operator published; the offering alone cannot look the default offer up. OnboardingRegistration then registers:
builder.AddBoxOffice();builder.Services.AddBoxOfficeOffering(offering => offering .Product(Guid.Parse("<derived from the application name>")) .DefaultOffering("<Name>"));- Product. The product id is a GUID derived from the application name, so every render of the same application declares the same product. No module is declared: the modules of a Screenplay model are not Box Office modules, so nothing is gated. Declare modules in code if the product sells them.
- Starting a tenant.
TenantOfferingStarteris a reactor onOnboardingTenantProvisioned, which provisioning appends in the tenant’s namespace. It callsTenantOffering.Start, which callsIDefaultOffering.StartTenant(tenant, organization). The tenant id is the name of the tenant’s Chronicle namespace, which is what Box Office expects. The organization id is a GUID derived from the application and tenant name, because a self-service product has no operator-assigned organization; the same tenant is always the same organization.StartTenantis idempotent, so retries and replays start nothing twice. - Catalog not there yet. The operator publishes its catalog after the application starts. Provisioning and the welcome email do not wait for the offer. When
StartTenantthrowsDefaultOfferingNotPublished,DefaultOfferingNotDeclaredorTenantNotStarted, the reason is logged as a warning and the exception propagates, so Chronicle retries that partition until the operator has published the offer. A tenant is started on its offering as soon as the offer is published. - Operator.
AddBoxOfficeuses its defaults, so the application honors the operator event storeDirect. ConfigureOperatorStoresin code to use another one. - Policies. Box Office’s
BoxOffice.ManageSeatsandBoxOffice.ManageComputeauthorization policies are not rendered, so its seat and compute commands are unavailable until the host defines them.
STAGE-ONBOARDING-008 reports this at render time.
What is not rendered
Section titled “What is not rendered”- Legal documents stay with Ante. The settings name them by their path in the model; the runtime mounts them. Ante presents two documents, terms first and privacy second; another count is reported as
STAGE-ONBOARDING-004.
Diagnostics
Section titled “Diagnostics”Codes are permanent. STAGE-ONBOARDING-001 (the offering is not rendered) and STAGE-ONBOARDING-007 (the declined email is rendered but not sent) are retired because both are now realized; their numbers are not reused.
| Code | Meaning |
|---|---|
STAGE-ONBOARDING-002 | A tenant invitation has no command. |
STAGE-ONBOARDING-003 | A sign-up creates a tenant, but no persona is named for its first member. |
STAGE-ONBOARDING-004 | The model does not list exactly two legal documents. |
STAGE-ONBOARDING-005 | An email template could not be read from the model root. |
STAGE-ONBOARDING-006 | An invitation to join a tenant names no inviting persona. |
STAGE-ONBOARDING-008 | Information: the offering is started through Box Office, so the operator must publish an offer with that name (tenants are started as soon as it is) and the host defines the Box Office policies. |
STAGE-AUTH-001 | A provider kind AuthProxy does not know. |
STAGE-AUTH-002 | A persona that cannot be reduced to roles and claims. |