Contracts
Cratis.Showrunner.BoxOffice.Contracts holds the public events an operator and a managed product exchange about tenants. The operator, such as Direct, decides what a tenant may do and publishes it. The product honors it and publishes what happens in it. Both sides reference this package.
Rules every contract follows
Section titled “Rules every contract follows”- The organization is the event source. Every tenant-scoped event, in both directions, is appended under the organization’s
OrganizationId: a GUID in its lowercase, hyphenatedDform. It is the operator’s own identity for the organization, and it is event context, not a field in the payload. The product answers under the same id it received. - Product-wide events have their own event source.
OfferingDeclaredandDefaultOfferingDeclaredare appended under the product’sProductId.OfferPublishedandOfferWithdrawnare appended under the offer’sOfferId. Neither id is in a payload. - Every tenant-scoped payload carries
TenantId. It is the name of the tenant’s Chronicle namespace in the product. In Direct it is the lobby’s tenant name. The operator serves many tenants, and the product appends each event in the namespaceTenantIdnames. The product-wide events have no tenant. - Nothing is nullable. An optional collection is empty, and an unknown value is the concept’s
NotSet. An entitlement without an end date usesEntitlementEnd.Never. - No timestamps that repeat the event’s own time. When something happened is the event’s
Occurred. The only dates in payloads are business dates: a trial’s last day, an entitlement’s last day, and the metered or summarized day. - Every event is
[Public], and none names an origin store. No contract shares a name with a private event. Direct’s private events keep theirCustomer…names. Both sides subscribe explicitly to the store they read from. - The schemas are guarded.
EventSchemas.snapshot.jsonholds every event’s schema, andReleasedEventSchemas.jsonlists the generations that are released and so can never change. Changing a released event takes a new generation and a migration.
Namespaces are relative to Cratis.Showrunner.BoxOffice.Contracts. Concepts are ConceptAs<string> unless noted.
Operator to product
Section titled “Operator to product”| Event | Namespace | Payload |
|---|---|---|
TenantRegistered | Tenants | TenantId; Name: TenantName; Creator: Person; CreatorRole: TenantRole |
TrialOpened | Trials | TenantId; Ends: TrialEnd; Seats: SeatCount |
TrialEndMoved | Trials | TenantId; Ends: TrialEnd |
TrialClosed | Trials | TenantId; Closure: TrialClosure (Expired, EndedEarly, Converted) |
SeatCapacitySet | Seats | TenantId; Capacity: SeatCount; Until: EntitlementEnd; Source: SeatCapacitySource (Grant, Purchase) |
ModuleGranted | Entitlements | TenantId; Module: ModuleId; Until: EntitlementEnd |
ModuleTrialGranted | Entitlements | TenantId; Module: ModuleId; Ends: TrialEnd |
ModulePreviewOpened | Entitlements | TenantId; Module: ModuleId; Until: EntitlementEnd |
ModuleRevoked | Entitlements | TenantId; Module: ModuleId |
AllowanceSet | Compute | TenantId; Meter: MeterId; Quantity: MeterQuantity; Period: AllowancePeriod (Day, Month); Overage: OverageBehavior (Block, Park) |
TenantSuspended | Tenants | TenantId; Cause: SuspensionCause (TrialEndedWithoutSeats, PaymentOverdue, AdministratorDecision) |
TenantReinstated | Tenants | TenantId |
TenantClosing | Tenants | TenantId |
TenantRegisteredasks the product to create the tenant. The creator is aPerson: identity provider, subject, name and email. The product answers withTenantProvisioned, and the operator starts the trial’s clock from that, not from registering.SeatCapacitySetcarries the total the tenant may now assign, not the amount added. Granted or bought seats replace the trial seats, so a tenant on a trial of 3 that is granted 5 has 5, not 8.- A suspended tenant is read-only in the product until
TenantReinstated. A trial that ends without seats suspends the tenant withTrialEndedWithoutSeats. TenantClosingtells the product to tear the tenant down.
Operator, product-wide
Section titled “Operator, product-wide”These concern no single tenant. They let a product show its plan page without calling the operator.
| Event | Namespace | Event source | Payload |
|---|---|---|---|
OfferPublished | Offers | OfferId | Name: OfferName; Modules: IEnumerable<ModuleId>; IncludedSeats: SeatCount; Prices: IEnumerable<OfferPrice> |
OfferWithdrawn | Offers | OfferId | none |
An OfferPrice is an Interval: BillingInterval (Monthly, Yearly), an Amount: PriceInMinorUnits (cents, not a decimal) and a Currency: CurrencyCode. Publishing an offer again with the same OfferId changes it. A withdrawn offer is no longer sold; tenants that have it keep it.
Product to operator
Section titled “Product to operator”| Event | Namespace | Event source | Payload |
|---|---|---|---|
OfferingDeclared | Offering | ProductId | Modules: IEnumerable<ModuleDeclaration>; Meters: IEnumerable<MeterDeclaration>; Seats: SeatLicensing (NotLicensed, PerUser) |
DefaultOfferingDeclared | Offering | ProductId | Offer: OfferName |
TenantProvisioned | Tenants | OrganizationId | TenantId |
TenantStartedOnOffer | Tenants | OrganizationId | TenantId; Offer: OfferName; Seats: SeatCount; Modules: IEnumerable<ModuleId> |
SeatTaken | Seats | OrganizationId | TenantId; Holder: Person |
SeatReleased | Seats | OrganizationId | TenantId; Holder: Person |
ActivitySummarized | Activity | OrganizationId | TenantId; Day: ActivityDay; ActiveUsers: ActiveUserCount; Sessions: SessionCount; SignIns: SignInCount |
ComputeDayClosed | Compute | OrganizationId | TenantId; Day: ComputeDay; Usage: IEnumerable<MeterUsage> |
ComputeDayCorrected | Compute | OrganizationId | TenantId; Day: ComputeDay; Usage: IEnumerable<MeterUsage> |
AllowanceExhausted | Compute | OrganizationId | TenantId; Meter: MeterId; Day: ComputeDay; Overage: OverageBehavior |
- A product publishes
OfferingDeclaredonce per change to what it sells, not on every start. DefaultOfferingDeclarednames the offer of the catalog a new tenant starts on, and is published once per change, next toOfferingDeclared. A product that has no default publishes none.TenantStartedOnOffersays the product started a tenant on its default offering by itself, with the seats and modules that offer includes. The operator does not confirm it; it may change what the tenant has afterwards. Starting the same tenant again publishes it once. See Offering.Usageis empty when nothing was used. AMeterUsagepairs aMeterIdwith aMeterQuantityin the meter’s unit.ComputeDayCorrectedreplaces the usage ofComputeDayClosedfor the same day; it carries the corrected totals, not a difference.ActivitySummarizedis published once a day per tenant.
Mapping from the v1 contracts
Section titled “Mapping from the v1 contracts”Box Office replaces the v1 Admin.Contracts events the operator sent to a product. The first table lists every v1 contract and what replaces it. The second lists Direct’s own facts about seats, trials and suspension, so each maps one to one.
| v1 contract | Replacement | What changes |
|---|---|---|
OrganizationRegisteredForTenant(Tenant) | TenantRegistered | Carries the name, the creator and the creator’s role. The organization is the event source, as before. |
TrialEndChangedForTenant(Tenant, Expiration) | TrialEndMoved | Names the trial, not the seat it moved. Ends is a TrialEnd. |
SeatsGrantedToTenant(Tenant, Sku, Quantity, Expiration) | SeatCapacitySet | Carries the resulting capacity and its source. The product no longer assigns a seat to every user. |
TenantDeleted(Tenant) | TenantClosing | Names the fact the product acts on: the organization is being deleted, so the product tears the tenant down. |
AnnouncementPublishedToAllTenants, AnnouncementAmendedForAllTenants, AnnouncementWithdrawnFromAllTenants | Not Box Office | Announcements are a separate part. |
TermsAndConditionsPublished, PrivacyPolicyPublished, LegalDocumentsPublished | Not Box Office | Legal documents are not licensing. |
TrialOfferedForInvitation(TrialDays) | TrialOpened | The trial opens when the tenant is provisioned, with its end date, not at invitation. |
| Operator fact in Direct | Box Office event |
|---|---|
CustomerOrganizationRegistered | TenantRegistered |
CustomerOrganizationProvisioned (the product’s report) | TenantProvisioned |
CustomerTrialStarted | TrialOpened |
CustomerTrialEndMoved | TrialEndMoved |
CustomerTrialEnded | TrialClosed (Expired for the automatic end, EndedEarly for an administrator) |
CustomerSeatsGranted, CustomerSeatsRevoked | SeatCapacitySet (the resulting total) |
CustomerSeatAssigned | SeatTaken |
CustomerSeatReleased | SeatReleased |
RecordCustomerActivity | ActivitySummarized |
| The product turning read-only | TenantSuspended and TenantReinstated |
CustomerOrganizationDeletionStarted | TenantClosing |
ProductId and OrganizationId are the operator’s own GUID identities, so Direct appends under the ids it already has. Direct’s private events keep their Customer… names, so no contract here shares a name with one.