Skip to content

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.

  • 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, hyphenated D form. 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. OfferingDeclared and DefaultOfferingDeclared are appended under the product’s ProductId. OfferPublished and OfferWithdrawn are appended under the offer’s OfferId. 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 namespace TenantId names. 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 uses EntitlementEnd.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 their Customer… names. Both sides subscribe explicitly to the store they read from.
  • The schemas are guarded. EventSchemas.snapshot.json holds every event’s schema, and ReleasedEventSchemas.json lists 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.

EventNamespacePayload
TenantRegisteredTenantsTenantId; Name: TenantName; Creator: Person; CreatorRole: TenantRole
TrialOpenedTrialsTenantId; Ends: TrialEnd; Seats: SeatCount
TrialEndMovedTrialsTenantId; Ends: TrialEnd
TrialClosedTrialsTenantId; Closure: TrialClosure (Expired, EndedEarly, Converted)
SeatCapacitySetSeatsTenantId; Capacity: SeatCount; Until: EntitlementEnd; Source: SeatCapacitySource (Grant, Purchase)
ModuleGrantedEntitlementsTenantId; Module: ModuleId; Until: EntitlementEnd
ModuleTrialGrantedEntitlementsTenantId; Module: ModuleId; Ends: TrialEnd
ModulePreviewOpenedEntitlementsTenantId; Module: ModuleId; Until: EntitlementEnd
ModuleRevokedEntitlementsTenantId; Module: ModuleId
AllowanceSetComputeTenantId; Meter: MeterId; Quantity: MeterQuantity; Period: AllowancePeriod (Day, Month); Overage: OverageBehavior (Block, Park)
TenantSuspendedTenantsTenantId; Cause: SuspensionCause (TrialEndedWithoutSeats, PaymentOverdue, AdministratorDecision)
TenantReinstatedTenantsTenantId
TenantClosingTenantsTenantId
  • TenantRegistered asks the product to create the tenant. The creator is a Person: identity provider, subject, name and email. The product answers with TenantProvisioned, and the operator starts the trial’s clock from that, not from registering.
  • SeatCapacitySet carries 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 with TrialEndedWithoutSeats.
  • TenantClosing tells the product to tear the tenant down.

These concern no single tenant. They let a product show its plan page without calling the operator.

EventNamespaceEvent sourcePayload
OfferPublishedOffersOfferIdName: OfferName; Modules: IEnumerable<ModuleId>; IncludedSeats: SeatCount; Prices: IEnumerable<OfferPrice>
OfferWithdrawnOffersOfferIdnone

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.

EventNamespaceEvent sourcePayload
OfferingDeclaredOfferingProductIdModules: IEnumerable<ModuleDeclaration>; Meters: IEnumerable<MeterDeclaration>; Seats: SeatLicensing (NotLicensed, PerUser)
DefaultOfferingDeclaredOfferingProductIdOffer: OfferName
TenantProvisionedTenantsOrganizationIdTenantId
TenantStartedOnOfferTenantsOrganizationIdTenantId; Offer: OfferName; Seats: SeatCount; Modules: IEnumerable<ModuleId>
SeatTakenSeatsOrganizationIdTenantId; Holder: Person
SeatReleasedSeatsOrganizationIdTenantId; Holder: Person
ActivitySummarizedActivityOrganizationIdTenantId; Day: ActivityDay; ActiveUsers: ActiveUserCount; Sessions: SessionCount; SignIns: SignInCount
ComputeDayClosedComputeOrganizationIdTenantId; Day: ComputeDay; Usage: IEnumerable<MeterUsage>
ComputeDayCorrectedComputeOrganizationIdTenantId; Day: ComputeDay; Usage: IEnumerable<MeterUsage>
AllowanceExhaustedComputeOrganizationIdTenantId; Meter: MeterId; Day: ComputeDay; Overage: OverageBehavior
  • A product publishes OfferingDeclared once per change to what it sells, not on every start.
  • DefaultOfferingDeclared names the offer of the catalog a new tenant starts on, and is published once per change, next to OfferingDeclared. A product that has no default publishes none.
  • TenantStartedOnOffer says 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.
  • Usage is empty when nothing was used. A MeterUsage pairs a MeterId with a MeterQuantity in the meter’s unit.
  • ComputeDayCorrected replaces the usage of ComputeDayClosed for the same day; it carries the corrected totals, not a difference.
  • ActivitySummarized is published once a day per tenant.

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 contractReplacementWhat changes
OrganizationRegisteredForTenant(Tenant)TenantRegisteredCarries the name, the creator and the creator’s role. The organization is the event source, as before.
TrialEndChangedForTenant(Tenant, Expiration)TrialEndMovedNames the trial, not the seat it moved. Ends is a TrialEnd.
SeatsGrantedToTenant(Tenant, Sku, Quantity, Expiration)SeatCapacitySetCarries the resulting capacity and its source. The product no longer assigns a seat to every user.
TenantDeleted(Tenant)TenantClosingNames the fact the product acts on: the organization is being deleted, so the product tears the tenant down.
AnnouncementPublishedToAllTenants, AnnouncementAmendedForAllTenants, AnnouncementWithdrawnFromAllTenantsNot Box OfficeAnnouncements are a separate part.
TermsAndConditionsPublished, PrivacyPolicyPublished, LegalDocumentsPublishedNot Box OfficeLegal documents are not licensing.
TrialOfferedForInvitation(TrialDays)TrialOpenedThe trial opens when the tenant is provisioned, with its end date, not at invitation.
Operator fact in DirectBox Office event
CustomerOrganizationRegisteredTenantRegistered
CustomerOrganizationProvisioned (the product’s report)TenantProvisioned
CustomerTrialStartedTrialOpened
CustomerTrialEndMovedTrialEndMoved
CustomerTrialEndedTrialClosed (Expired for the automatic end, EndedEarly for an administrator)
CustomerSeatsGranted, CustomerSeatsRevokedSeatCapacitySet (the resulting total)
CustomerSeatAssignedSeatTaken
CustomerSeatReleasedSeatReleased
RecordCustomerActivityActivitySummarized
The product turning read-onlyTenantSuspended and TenantReinstated
CustomerOrganizationDeletionStartedTenantClosing

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.