Scope: externally observable API behavior and source-defined versioning. Source: API routes and generated OpenAPI contracts.
- Breaking: remove plaintext provider credentials from API contracts.
AI model creation and instance AI provider patches no longer accept
apiKey. Instance AI settings no longer returnapiKeyConfigured. Authentication settings and onboarding reads no longer returngoogleClientSecretorkeycloakClientSecret, and authentication provider patches no longer acceptgoogleClientSecret. Authorization-provider reads no longer exposecerbosAdminCredentialsOwnership,cerbosAdminPasswordConfigured, orcerbosAdminUsernameConfigured. Tenant onboarding settings and completion no longer carryaiAssistantApiKey. Configure provider credentials through the selected deployment secret authority, not administrative JSON payloads. - Breaking: remove the internal authentication-provider onboarding GET.
GET /api/instanceonboarding/auth-provider-configuration/internalis removed. Regenerate callers against the supported onboarding and settings contracts; do not restore the internal credential-bearing route. - Breaking: require explicit action and order intent.
Confirming an AI proposed action requires its request body.
POST /api/events/{eventId}/registration-ordersrequiresIdempotency-Key. Preserve the same key for retries of the same order intent. - Breaking: publish localization import bounds.
POST /api/admin/localization/bundlerequireslanguageCode(1..35 characters) andtranslations; translation values are bounded at 16,384 characters. Existing duplicate-key and aggregate-budget validation still applies independently of OpenAPI schema validation. - Deploy the API and regenerated clients together. Update automation that populated removed fields or read removed credential-status properties. Rollback must preserve secret isolation and idempotency rather than restoring plaintext fields or retrying an uncertain order with a new operation key.
- Breaking: native draft program schedule nullability. Nested event-creation
sessions accept absent
StartTimeandEndTimefor genuine unscheduled drafts. Metadata and valid graph references persist; no date, day or public occurrence is invented. Fixed scheduled entries require a valid pair; OpenEnded remains start-only and prayer-relative rules remain authoritative. - Publishing with any unscheduled nested entry rejects before rollups/writes. Validation retains session index/property paths in ProblemDetails. Existing tenant-qualified references, initial owner assignment and serializable graph/publication-outbox persistence remain the authority.
- Deploy the regenerated API/client contracts together. No database migration is introduced. Once nullable drafts are stored, do not downgrade to code that assumes every session has a start, erase retained programs, or automatically retry an uncertain creation response.
- Tenant-owned external API-key creation without a resolved trusted tenant now
returns HTTP
400validation ProblemDetails instead of a server error, without a key or receipt. Global instance-administrator issuance remains tenant-independent. - Breaking: success-only external API key disclosure. Creation HTTP
200now publishesExternalApiKeyIssuanceDto, not a generic command-response envelope. Required fields areid,keyId,disclosureStatus, andapiKey. Disclosure is exactlyIssuedorPreviouslyIssued, never null; recovery requires explicit nullapiKey. Failures remain HTTP errors with ProblemDetails. - The regenerated SDK and browser enforce field presence before accepting an acknowledgement. Missing, null, unknown or numeric disclosure tokens cannot silently default to first issuance. Fixed local failure guidance and logging do not reflect credential-bearing response bodies or exceptions.
- Coordinated deployment: update the API, generated SDK and browser together.
Automation must branch on HTTP status and then disclosure, rather than a
successBoolean. Preserve the original operation key and policy after an uncertain acknowledgement. Rollback must retain receipts and secret-free recovery; reverting to raw credential replay is not a safe rollback.
- Breaking: bounded external API key issuance.
POST /api/ExternalApiKeynow requires exactly one case-sensitiveIdempotency-Keycontaining 1..128 ASCII[A-Za-z0-9._:-]characters. Native creation requiresOperationKey; HTTP/OpenAPI and the generated SDK carry the required header. Clients must preserve one key and the same normalized DTO per intent after response loss. - Successful command results distinguish
"Issued"(rawapiKeyreturned) from"PreviouslyIssued"(apiKey = null, metadata only). Authorized recovery of committed operations does not mint credentials. Changed normalized policy returns409, revoked owner authority returns403, an unbound identity returns401, and an unavailable issued key returns404. Recovery never reveals metadata to a caller who has lost owner authority. - Generic idempotency response storage is suppressed for creation and no-store remains. A new digest-only issuance-receipt table retains operation evidence independently of credential deletion; its unique operation fingerprint prevents a deleted key from making the original intent reusable. Credential and receipt writes share a serializable transaction under retained-erasure and current persisted authority fences.
- User-owned tenant issuance and recovery require active tenant membership. Organization/group issuance requires positive active persisted management permission; empty permission mappings do not authorize. Linked tenants must have an active status. The browser freezes the canonical request and operation key for retries, then offers metadata-only recovery guidance and refreshes the parent list on Done.
- Deployment and rollback boundary: install the generated receipt-table migration before running the new executable. Final provider/migration proof remains pending; this bounded change is not release security readiness and the other 25 remediation phases remain launch blockers. Reverting executable code cannot restore discarded raw material. Historical generic key-route replay records require inspection, authorized purge, and rotation of actually exposed keys under explicit operator authorization; this change does not perform those operations. See the HTTP contract and operator recovery and rollback guidance.
-
Homepage deadline visibility and supply-aware overlap. Featured and Upcoming now select independently; carousel inclusion never excludes an otherwise eligible event from its chronological Upcoming position. Recently Added prefers identities absent from both shelves, then Featured-only identities, then Upcoming identities as needed, preserving newest-added display order. Each shelf remains internally unique. Existing limits, disclosure checks, section failures and transport shapes remain authoritative.
-
Homepage occurrence urgency. Upcoming uses explicit ongoing-or-future instant eligibility rather than a UTC-calendar date cutoff. Home cards retain their existing public timezone metadata; matching start times wrap before secondary titles, Today/Tomorrow labels use that event timezone and the response timestamp, and Ongoing observes finite half-open or explicit open-ended intervals. English/Arabic labels are shipped without inventing freshness or treating a missing end as indefinite.
-
Fresh registration after ordinary erasure. External authentication retains the combined sign-in/JIT-signup flow. An erased provider identity or released verified email may create a fresh User/Actor graph without restoring old profile data, roles, memberships or consent. Retained old-subject fencing, atomic disposal and sequence-validated startup replay remain effective. Replay no longer erases a different fresh UUID by matching external identity. Ordinary signup, erasure and startup do not require reserved fingerprint secrets. The optional key and key-ID definitions resolve from
/api; moderation enforcement remains a future feature. Routes and payload shapes are unchanged.
-
Breaking public discovery traversal.
GetEventsaccepts opaquecursorandpageSizeinstead of publicpageNumber. The HAL resource exposessnapshotCount,truncated,expiresAt,hasMoreand forwardnext, without total pages or a global match count. Continuations retain criteria and captured rank but recheck current authority. Invalid, expired and changed traversals return metadata-free no-store400,410and409ProblemDetails; unavailable authority or capacity returns503. Private management pagination is separate. -
Reversible discovery identity surfaces. Event identity status and bounded duplicate candidates are no-store HAL resources. The status route accepts an optional public candidate and advertises current group-authorized review and reversal actions. Unavailable primaries expose no target or relationship hint. Decisions preserve original event/session/registration targets; stale tenant identity revisions return
409ProblemDetails, and denied authority returns403. Candidate fields remain flattened alongside_links. -
Occurrence-correct discovery. Public date, temporal and governed-area filters match the same published session. Cards expose
matchingSessionand nullableadditionalSessionCount, with permitted regional city/country. Unknown or inactiveareaIdvalues no longer become unfiltered searches. -
Current disclosure reads. Public discovery no longer replays cached DTO pages or shared/conditional-304 responses; public details no longer replay fields removed by committed redaction. Unresolved configuration or regional authority returns no-store
503withdiscovery_unavailable. -
Breaking: storage provenance versus delivery. Persisted
StorageObject.Uriis replaced by nullableSourceUriforeign-origin provenance. Ordinary storageuriis now nullable and derived only from eligible captured managed IDs: public safe-raster/public, authenticated/content, with resource files retaining their resource-only content route.legacy_externalrecords do not acquire managed delivery. Detail/list and upload-session responses omit backend provider identity; source, binding, bucket, key and version are not disclosed. HAL remains the action authority. API schema, inventory, NSwag client and generated records are refreshed through their native generators. -
Breaking: explicit Actor profile-media ownership. Actor/User responses distinguish
profilePictureStorageObjectIdfromexternalProfilePictureUri. Display URLs are derived independently; UserprofileImageKeyis removed. The userprofileImagePATCH group selects a managedprofilePictureIdor explicit external source, rejects both together, and clears with an empty group. Omission preserves the image. Managed selection retains tenant/public safe-raster checks and rejects resource-only or differently Actor-owned bytes. External URLs never authorize ownership or deletion. Existing participation background/banner delivery is ID-derived. API and client artifacts are generator-owned; the database upgrade must accompany deployment. -
Unified instance administration. Internal administration routes move from
/api/admin/control-plane/*to/api/admin/instance/*, with instance-named operation IDs and generated contracts. Update direct clients to the regenerated contract or follow current HAL links; no old route aliases remain. Tenant lifecycle, plan assignment, configuration, deployment-mode and operator diagnostics retain their existing authority and concurrency boundaries./api/management/*remains the separate external fleet connector. Whole-instance configuration export and import sessions share the new/api/admin/instance/configuration-*boundary; browser downloads use/bff/admin/instance/configuration-manifest/export.
- Private administrator sessions. Current-user and administrator-authority reads remain available as tenantless instance sessions when no directory is selected. An explicitly selected private directory instead requires fresh persisted administrator authority and returns 404 when unavailable; an unknown explicit slug cannot fall through to another directory's host. Session responses are no-store. For these two reads, lifecycle concealment runs after authentication and trusted tenant binding, before authorization and response replay. Anonymous active-directory requests retain 401, and credential replacement retains its dedicated authentication scheme. Other endpoints retain authorization before lifecycle enforcement, including anonymous writes.
- Response metadata completeness. Explicit action-level response metadata now describes 29 existing lookup, feature-flag, agenda, organization, user, and add-on operations. Routes, operation IDs, authorization and runtime payloads are unchanged. Add-on mutations return 200 HAL resources, not 201 command envelopes; session-agenda deletion returns empty 204. Category-type, language and tag-type detail reads declare their existing empty 204 outcome when no lookup exists. Organization member commands continue to return their command envelope at 200, including application-level failure results. Non-success HTTP responses are described as ProblemDetails, with the user synchronization validation response typed as ValidationProblemDetails. OpenAPI and the C# client are regenerated through the build pipeline.
- Organization member authorization errors. Create, role-update and delete operations explicitly declare their existing 403 ProblemDetails response from resource authorization. Generated clients now deserialize those denials as typed errors; permissions and runtime status codes are unchanged.
- SetupLive configuration imports. Enrollment-bound tenant administrators
can create a bounded configuration-import session and request a preview under
/api/tenants/{tenantId}/setup/enrollments/{enrollmentId}/configuration-import/sessions. The server advertises apply only for a current, reviewable preview. Each step checks the ephemeral enrollment capability, explicitconfiguration.importscope and current tenant-setting authority. Session origin is bound to the enrollment generation and actor; ordinary tenant import routes cannot consume an enrollment-bound session after revocation or rotation. Conflicting or replayed applies return RFC 7807 errors instead of reapplying mutations. - Instance operator identity portability. Instance administrators can
export
GET /api/instance-operator-identity/manifestand importPOST /api/instance-operator-identity/manifest/import. The closed JSON manifest contains a canonical document, SHA-256 content digest and revision hash. The import requires a matching target revision, rechecks administrator authority on every request (including idempotency-key replays), rejects unofficial-to-official escalation, and atomically writes value-free outbox audit evidence. Stale revisions and concurrent writes return 409; malformed manifests fail closed. The published OpenAPI component describes the required closed envelope and document fields.
-
Event-resource operator settings. Instance administrators can read and strictly batch-update the resource category at
GET/PUT /api/settings/instance/event-resources. The authenticated, private/no-store read returns eight effective values with source, lock, editability and a conditional HALeditrelation. Anonymous callers receive 401; signed-in non-administrators receive 403. The existing tenantEventResourcescategory now advertiseseditonly when a current setting and tenant permission allow it. Tenant mutations still cannot widen instance limits or opt in to unscanned documents, and instance changes use the coordinated writer. See native resource policy surfaces. -
Protected external resource destinations. An authenticated, idempotency-revalidated
PUT /api/eventresource/{id}/destinationaccepts a full, strictly validated HTTPS link but never reads it back. Only the safe allowed origin and a tenant/resource/version-bound Data Protection envelope persist. After a freshly authorized final header check,GET /api/eventresource/{id}/accessreturns an empty 302 with the destination inLocation, private/no-store caching and no-referrer policy. Audience and management DTOs gain only the safe origin; eligible HAL has a same-originaccessaction. Invalid keys, tightened policy, withdrawal and tampering return no Location. See protected destinations. -
Portable resource metadata. A private/no-store export query uses exact parent and per-resource
exportauthority with version-bound final checks. The explicit bounded projection preserves semantic metadata, audience rules and relative timing without delivery material, audit rows or attribution. The management collection gains a scoped export action; generated consumers retain the typed JSON contract. See metadata portability. -
Audience resource disclosure. Two private/no-store native read routes add explicit safe metadata and teaser projections, complete bounded discovery at the 500-active-resource ceiling and purpose-bound 15-minute continuation. Pagination contains no global counts; each response revalidates versions, disclosure level, alternative references and any continuation witness after HAL assembly. Cursor failures are bounded 400 and hidden resources remain 404. No delivery surface is enabled. See audience discovery.
-
Governed resource draft management. Native resource commands and queries expose private/no-store management detail, event collections, and bounded audit reads. Client-retained UUIDv7 create IDs, required idempotency keys with replay revalidation, and expected versions protect mutation intent. After asynchronous HAL assembly, a final version-bound native authority check guards metadata disclosure. Semantic drafts accept no delivery references and expose no publication, access, or download affordance. Resource mutation and minimal audit commit atomically; hourly retention deletes complete rows, retention zero suppresses collection, and subject erasure clears attribution. See draft management.
-
Native resource governance. The
event_resources.*setting family is available through native settings and configuration manifests. Tenant writes cannot widen instance ceilings or set the instance-only unscanned-document opt-in; malformed and rejected tenant setting updates return validation ProblemDetails. Coordinated batches and manifest imports preserve atomicity. Current instance locks apply in SingleTenant mode, and resource authorization rereads effective policy rather than relying on cached setting values. See resource governance. -
Resource-policy deployment activation. Current instance administrators can read and bind deployment aliases at
GET/PUT /api/event-resource-provider-activation/bindings, begin a closed operation atPOST /api/event-resource-provider-activation/begin, and attest convergence atPOST /api/event-resource-provider-activation/activate. The admin binding GET uses endpoint authentication before the handler's fresh instance-administrator check (401 anonymous, 403 signed-in non-admin). Activation requires matching operation/epoch, scope/version, complete replica evidence and explicitfrozenParentPolicyContractConfirmed. Invalid binding shapes return 400 ProblemDetails; stale revisions or rejected activation return 409. Responses are private/no-store and writes never replay generic idempotency responses. Activation does not publish resource metadata or content.
-
Breaking: retired Keycloak startup reconciliation. AppHost and Docker Compose no longer launch
keycloak-initor accept callback/API-secret synchronization variables. They also no longer mount or automatically import the sample realm. New and existing deployments use the explicit, reviewed operator workflow; provider volumes and existing realms remain untouched. -
Reviewed Keycloak operator experience. Initial setup and instance authentication settings now share one HAL-driven operator panel for connection, inspection, planning, apply, reconciliation and cancellation. The panel clears request credentials after every attempt, requires explicit receipt confirmation before Apply, suppresses replay after an ambiguous response, and gives step-level partial/unknown-outcome recovery guidance.
-
Breaking: create-only Keycloak provisioning and deployment-owned credentials. Legacy Keycloak bootstrap, realm-wide sync and application-managed secret rotation routes are removed; runtime credentials remain deployment-owned.
POST /api/instance/keycloak/plansnow requires a closedRepairClient,CreateClientsorCreateRealmintent. Creation is allowed only for fresh, proven-absent resources; races and existing names conflict without adoption. Inspection, apply and reconciliation use distinct write-only credential inputs. The connection projection publishes value-free deployment ownership and restart guidance, never a client secret. -
Breaking: reviewed Keycloak operator operations. Keycloak repairs use approved operation receipts instead of unrestricted bootstrap or sync requests. The seven private, no-store routes are
GET /api/instance/keycloak/connection,POST /api/instance/keycloak/inspect,POST /api/instance/keycloak/plans,GET /api/instance/keycloak/operations/{id}, andPOST/api/instance/keycloak/operations/{id}/apply,/reconcile, and/cancel. Every request requires current setup or instance-administrator authority. Receipts expire after 15 minutes and are bound to their verified administrator creator or server-derived setup generation. Inspect and apply accept advanced administrator credentials only for the submitted request; no receipt includes credentials. Receipt reads remain private, reconciliation only inspects provider state, and the service performs neither automatic mutating retry nor rollback. These routes and their credential-bearing requests bypass generic idempotency response storage, so a prior response cannot replay current authority.
-
Internal public-address establishment. Setup has no URL field, confirmation or authoritative-host launch blocker. Authorized setup establishes the effective BFF address internally, preserving scheme, port and path base; background email links use deployment overrides or this established address. Subdomain routing requires its own explicit base domain; path-based tenancy does not. Response-only ownership flags are absent from writable profiles, fixing generated-client null binding on Finish setup. Listening addresses are never used as public addresses.
-
Setup-read availability under rate limiting. Setup-secret GET requests now use IP-and-route fixed-window partitions, while setup mutations retain the shared IP partition and the existing five-per-60-second bound. Repeated status or journey reads can no longer starve the authoritative authorization-provider configuration read and make the onboarding UI display unavailable provider state. Routes, payloads, setup-secret validation, write limits, and OpenAPI are unchanged.
-
Authoritative operator form metadata. Authenticated
GET /api/operator-identity-metadata(GetOperatorIdentityFormOptions) returns value-free operator-kind codes, runtime country/region display choices, shared legal-identity constraints and stable label/help identifiers. Missing runtime globalization data is explicitUnavailable, withoperator_identity_countries_unavailable; no partial mandatory selector is supplied. Registration identifiers remain optional; authority options are empty withregistrationAuthorityState: NotSupportedbecause no registry exists. The tenant-independent resource is private/no-store and exposes onlyselfandrefresh, never edit authority. Normal authenticated instance identity readers discover it throughform-options; setup-secret authority is not extended. No persisted identity values, credentials, migration or new dependency is added.
-
Breaking: setup finishes before publication. Completion requests must submit the current journey
generationasexpectedJourneyGeneration(nested undersettingsfor Local completion). Stale or unavailable state returns HTTP 409 with a HAL refresh link before mutation. Legal identity is no longer an installation prerequisite. A new SingleTenant default is Provisioning with authoritative draft documents; existing tenants/documents are preserved and MultiTenant creates no directory. Completed status retains the provider for credential-free recovery. Setup stays locked; administrators land at/settings/instance?section=getting-started. Disclosure, activation and paid commerce keep their separate readiness and authorization checks. -
Breaking: authoritative onboarding journey. Setup clients use private HAL
GET /api/instanceonboarding/journeyfor one snapshot of bootstrap, provider readiness, persisted profile and categorized preflight checks. Failed or changing sources return bounded failure state and refresh authority, not fallback defaults. The duplicate System onboarding-preflight route and generated client method are removed without aliases. Existing profile PATCH precedes readiness refresh. Completion semantics, schema and dependencies are unchanged. -
Private default-directory administration. Existing control-plane tenant detail and activation routes now accept the fixed default tenant in SingleTenant mode with unchanged instance-administrator authorization. Arbitrary targets are rejected; fleet operations remain MultiTenant-only. SingleTenant detail HAL omits fleet-only actions. Identity/branding edits retain exact tenant permissions, and identity completion does not activate. Explicit activation reads the current identity under its existing mutation lock, not a stale cached revision. OpenAPI is regenerated to remove the two obsolete multi-tenant-required annotations; route, payload and generated-client method shapes are unchanged. No migration or deployment configuration change is required.
-
Breaking: capability-specific instance identity readiness. Identity GET and PUT responses replace top-level readiness fields with
publicDisclosureandpaidCommerceassessments. Valid incomplete administrative drafts can be saved after setup. Public disclosure permits missing commercial terms; commerce still requires them and current immutable acceptance evidence. Update generated clients; no database migration, route alias, or configuration change is required. -
Security: provider credential HTTP nonretention. The seven existing credential-bearing provider-management POST operations are now
private, no-storeand bypass generic idempotency response storage. A retry re-evaluates current setup or instance-administrator authority and the current provider outcome rather than replaying a historical successful response. The secret-bearing internal authentication-configuration GET is alsoprivate, no-store. Routes, DTOs, operation IDs, authorization, handlers, HAL, provider reconciliation, OpenAPI, and generated contracts are unchanged. This HTTP-only fix does not remove database secrets or provider fallbacks, clear UI data, harden logs, change lifecycle-service identity, or isolate Infisical.
- Session status not-found contract repair. Unknown integer IDs at
GET /api/eventsessionstatus/{id}now return the declared 404 ProblemDetails (resource_not_found) instead of an empty 204. Existing 200 DTOs, all ten global catalogue IDs, routes, anonymous access and cache policies are unchanged. The native detail query explicitly returns a nullable DTO; the controller owns HTTP failure mapping. OpenAPI and generated clients already declare 404 and require no regeneration. No configuration or database migration is required.
- SQLite directory temporal queries. The existing event directory keeps
filtering, counts and pagination in SQLite instead of failing on unsupported
DateTimeOffsetcomparisons. A connection-local instant collation preserves offset equivalence and 100ns boundaries for the default current/upcoming filter and explicit temporal views. Visibility and tenant predicates remain enforced; no endpoint, schema or migration changes. - Additive public support-contact readback. Existing
BrandingSettingsDtoadds nullablesupportEmail, sourced from instance-scopedbranding.support_email.GET /api/instance/settings/branding(GetInstanceBrandingSettings) retains its route and administrator/setup authority boundary; no endpoint is added. The existing onboarding support contact input now persists public site identity without modifying SMTPemail.from_addressoremail.delivery_enabled. This is a DTO field addition, not a new sender configuration, environment key or database migration. - Event-bound anonymous data access. Participant/ticket projections and registration exports omit anonymous names and answers after their original retention deadline, including before cleanup. Missing historical bounds do not grant a fresh window. Registration-owned content downloads enforce tenant-qualified lineage and expiry; anonymous exports use mediated content access instead of presigned URLs. Storage HAL omits unavailable download actions without adding a public DTO field or endpoint. The sibling registration-answer-file GET and release responses also redact expired filenames, including after authorization waits, without removing release evidence or changing administrative release authority.
- Explicit free guest confirmed cancellation.
POST /api/events/{eventId}/guest-registration-orders/{orderId}/cancellation(CancelConfirmedGuestRegistration) accepts the existing capability header and no body. Success and valid duplicate success return 204; missing post-confirmation authority returns generic private 404, while valid but ineligible authority returns 409guest_registration_cancellation_ineligible. The server-authoredcancel-registrationPOST relation appears only after native eligibility checks. No GET, generic checkout DELETE, paid/refund path or session authority is expanded. - Atomic admission and capacity release. Eligible anonymous confirmations use a dedicated transition that revokes admission and releases exact consumed holds once, excluding real check-in and issuance races. Generic Confirmed state remains terminal. Every POST revalidates authority rather than serving generic idempotency response storage.
- Private post-confirmation guest status.
GET /api/events/{eventId}/guest-registration-orders/{orderId}/status(GetGuestRegistrationStatus) accepts only the established guest capability header and returns PII-free lifecycle facts and the persisted access deadline. Invalid, foreign, deleted, unpromised or expired access receives generic private 404. Self and eligible public-calendar links do not grant checkout, attendee-data, payment, claim or cancellation authority. - Breaking: finite guest-status promise and HAL confirmation discovery.
New guest allocation requires a finite authoritative event-end-plus-30-days
window, recorded before payment. Live promises may extend but never shorten
across earlier/null schedules; missing or expired promises do not revive.
Guest continue/finalize responses now use a typed HAL lifecycle wrapper and
advertise
guest-statusonly after independent authorization. The general checkout expiry guard is unchanged. - Private bookmark transport. The status landing page consumes and removes
fragment capability material before analytics/network startup; explicit copy
or download is the durable save action. BFF landing/status responses enforce
private/no-store/no-referrer on success and failure. Public calendar export
remains capability-free and does not use authenticated
calendar/my-access. - Breaking: guest reservation requires bound proof.
POST /api/events/{eventId}/guest-registration-challenges(CreateAnonymousRegistrationChallenge) accepts the existing guest-start business body and its intendedIdempotency-Key, returning a private HAL challenge without allocating inventory.StartGuestRegistrationOrderkeeps its route/body and now requiresX-Registration-ChallengeandX-Registration-Proof. Proof validation precedes idempotency replay disclosure. - Exact committed guest recovery. A retry after allocation commit and response storage failure recovers the same protected order/capability without renewing holds or allocating again. Historical proof only authorizes that exact committed result through original expiry plus 24 hours; expired proof never authorizes new allocation, and a new envelope with the same key/body cannot reveal an older response. Uncommitted live-owner requests retain conflict behavior.
- Bounded anonymous intake. Issuance and guest start use dedicated effective-IP, subnet and concurrency limits alongside durable tenant/event issuance quotas. Existing antiforgery, capability response headers, Location, private/no-store responses and ticket/capacity/approval checks remain authoritative.
- Breaking: visitor onboarding bounds AccountRequired participation. Create, import, draft/configuration update and ordinary/privileged publication reject AccountRequired participation without an eligible public onboarding path. Provider or visitor-policy changes that remove the last path conflict instead of rewriting events. DirectoryListingOnly rejects new native allocation, not existing lawful registration status or cancellation.
- Shared visitor discovery. Existing public settings, shell, authentication
provider configuration and event-detail responses add
visitorAccess. Typed mode and provider enums match native string JSON. Provider HAL emitssignup:atproto,signup:keycloakorsignup:googlewith the exact shared destination; there is nosignup:local. Public metadata and event-detail candidates remain no-store, and mutations recheck authority transactionally. - Secondary ATProto onboarding. Verified, enabled, tenant-usable ATProto can create a new visitor's platform account through its provider-selected flow when shared policy permits it. Existing linked login and exact configured administrator authority remain separate; no Local public signup is restored.
- Local lifecycle operations.
POST /api/auth/local/email-verifications(RequestLocalEmailVerification) and/password-recoveries(RequestLocalPasswordRecovery) return empty 202 responses without disclosing missing or ineligible accounts. Proposed-address verification instead requires the current ordinary Local session and its server-derived credential stamp./email-verifications/consume(ConfirmLocalEmail) and/password-recoveries/consume(CompleteLocalPasswordRecovery) accept the exact operation pointer and native purpose-bound token. Completion returns 204 without issuing a session; wrong purpose, account, generation, expiry or replay is denied. - Protected ordinary password change.
POST /api/auth/local/password(ChangeLocalPassword) requires current Local authority plus the current and proposed passwords. It works independently of SMTP, rotates credential stamps and does not accept first-use replacement authority. - Breaking: authentication-provider discovery uses HAL.
GetInstanceOnboardingAuthProviderConfigurationretains its flattened configuration fields and adds server-authored lifecycle links. The generated response isHalResourceOfAuthProviderConfigurationDto; current-user HAL publishes the applicable Local actions without changing external-provider ownership. BFF lifecycle POSTs preserve antiforgery, private/no-store responses and suppression of generic response replay. Private mail-link material travels only in the browser fragment, never its query string.
-
Guarded SMTP delivery disable. Instance administration exposes
POST /api/instance/settings/smtp/disable-previewand/disable; current-tenant administration exposesPOST /api/settings/email-delivery/disable-previewand/disable. Preview returns non-secret scope/revision impact and an expiring, actor-bound confirmation token. Commit requires that token, the observed revision and the exact acknowledgementDISABLE EMAIL DELIVERY; stale evidence returns 409 without mutation. Current persisted administrator authority and settings locks are checked independently of the token. -
Breaking: SMTP read responses use HAL. Instance SMTP settings publish
deliveryEnabledand server-authored action links. Clients discoverdisable-previewanddisablerather than inferring authority from roles; generic settings writes cannot bypass confirmed disable. Preview/commit responses are private and no-store. The native generated client now usesHalResourceOfInstanceSmtpSettingsDtoinstead of its former plain response type. -
Breaking: Local sign-in accepts an identifier. API and BFF Local login requests use
identifierinstead ofemail, without a compatibility alias. Usernames and email addresses resolve existing Local credentials; successful identity data permits absent email, and JWTs omit the email claim when absent. -
Additive: setup-authorized Local administrator completion.
POST /api/InstanceOnboarding/complete-local(CompleteLocalInstanceOnboarding) accepts an operation ID, username, temporary password, optional account email/profile fields and existing setup settings. It requires native SetupSecret authority, incomplete setup and an active Local provider. Completion returns no credential or session authority; private replacement and fresh login remain required. Responses are private/no-store and excluded from generic idempotency storage. Onboarding status exposespendingOperationIdonly to active setup authority, and advertisescomplete-localthrough HAL for eligible setup. -
Breaking: managed Local administrators use existing identity references. Managed provisioning accepts
localIdentity.localSubjectIdinstead of an administrator invitation. Explicit directory-operator identity is carried through validation and provisioning; credential email cannot supply it. The referenced account's live global binding is checked without pinning request replay to a password operation or security stamp. Provisioning links tenant grants without issuing credentials or producing administrator email. -
Additive: instance-owned Local credential administration. The following private/no-store operations require a current persisted instance-admin grant:
Method and route Operation GET /api/instance/local-identitiesListLocalIdentitiesPOST /api/instance/local-identitiesCreateLocalIdentityPOST /api/instance/local-identities/{userId}/temporary-credentialResetLocalCredentialGET /api/instance/local-identity-operations/{operationId}GetLocalCredentialOperationPOST /api/instance/local-identity-operations/{operationId}/reconcileReconcileLocalCredentialOperationCreate returns 201; reset, safe replay, status and reconciliation return 200. Caller-owned operation IDs support recovery, but generic response replay is disabled for create, reset and reconciliation: only successful issuance contains the generated temporary password, and every retry rechecks current administrator authority. Bodies cannot supply actor, verification or initial-password authority. Missing targets return 404, conflicts 409, and denied instance authority 403; unauthenticated writes retain native 401. HAL advertises create, eligible reset and pending-create reconciliation; the control-plane overview links to the collection. Regenerate clients from the native OpenAPI export.
-
Correction: nullable HAL enum contracts preserve unknown state. The native HAL schema transformer now retains CLR nullable enum properties on both DTOs and flattened wrappers. Generated clients can read
credentialState: nullfor missing or invalid credential metadata instead of throwing or inventing a state. Nonnullable enum properties retain their existing wire contracts. -
Breaking: Local access tokens require current credential authority. Ordinary JWTs now carry
local_session_stampand an explicit verification fact. Protected API requests validate current Ready credentials, completed operation and active account binding before claims enrichment. Reset, stamp or verification changes, invalid binding, and enabled email intent for unverified credentials deny access. Invalid or unreadable current authority returns bounded HTTP 401. Previously issued stamp-less tokens require fresh login; no compatibility path is retained. External-provider and restricted replacement schemes are unchanged. -
Security: Local browser sessions recheck current credential authority. Native cookie validation and subsequent interactive activities probe the existing current-user API with the original server-held token. Invalid authority rejects the cookie or stops circuit dispatch and publishes anonymous state. Cleanup is limited to the original session and cannot adopt or remove a newer same-user login. No new endpoint, browser token exposure or client regeneration is required.
- Additive: first-use Local credentials yield restricted replacement authority.
Local login can return
replacementChallengeinstead of an ordinary token.POST /api/auth/local/credential-replacementaccepts that challenge as Bearer authorization and a password-only body. Success is empty HTTP 204 and requires fresh login; password rejection is 400, invalid authority 401, and concurrency conflict 409. Ordinary access tokens cannot authorize replacement. Responses are private/no-store and excluded from generic idempotency storage and replay. Regenerate clients fromschemas/openapi_islamu-event.json. - Additive: browser Local password replacement uses a separate restricted cookie.
Temporary-password login directs the browser to
/auth/local/change-password.POST /bff/auth/local/credential-replacementrequires antiforgery and accepts only the new password; the BFF supplies its protected challenge server-side. Completion clears the challenge and returns fixed/loginnavigation without signing the browser in. Local credential responses are private/no-store, including early request rejection. - Breaking: public Local registration is removed.
POST /api/auth/local/register(RegisterLocalIdentity) andPOST /bff/auth/local/registerno longer create credentials. The registration command and request/response contracts are retired; there is no compatibility alias. Local login remains available. - Security: instance email intent gates unverified Local login. After valid
credentials, enabled intent returns
email_verification_required/401 without issuing a token. Missing/disabled intent preserves unverified state; malformed or unreadable intent returnsauthentication_failed/503. SMTP availability and tenant overrides cannot bypass this gate. External-provider authority is unchanged. Local login responses are private/no-store and excluded from generic idempotency response storage and replay, so a repeated key cannot reuse an earlier success.
- Status resources expose nullable
parkReasonwithCapabilityUnavailableorOperator.deliveryStatusis now a typed enum in the contract; JSON continues to use the existing named status values. - Permission-qualified
parklinks include capability-parked messages so an operator can take ownership of the hold. Repeating an existing operator hold does not overwrite its reason or timestamp. - Undefined statuses do not advertise mutation links. Processing, redacted and uncertain-send rows cannot be parked; existing reconciliation rules remain.
-
Additive: instance bootstrap status reports typed onboarding state. The status response gains value-free
state,mode,provider, andgenerationfields describing where a headless instance stands in its onboarding lifecycle. No configured identity value is ever returned. Existing operation identifiers and routes are unchanged and no new write endpoint is added, but generated clients must be regenerated fromschemas/openapi_islamu-event.jsonto see the new fields. -
Breaking (pre-v1): provider accounts are identified by an authority-qualified key. An account is matched only by a normalized OIDC issuer with its exact subject, or by an authoritative verified ATProto DID. Email, username, role, handle, and session-id matching are removed and there is no raw-key fallback. Clients that sent a mutable attribute as the account key must send the qualified key instead; regenerate clients from
schemas/openapi_islamu-event.json. -
Additive: authenticated Setup live enrollment and write-only secret-binding contracts are available but not release-activated. Seven private, non-cacheable tenant routes create, inspect, revoke, and rotate short-lived Setup target enrollments; inspect value-free binding readiness; write one approved secret binding; and inspect its value-free operation receipt. Enrollment capabilities are returned only in the
X-Setup-Enrollment-Capabilityresponse header, mutations require fresh UUIDv7Idempotency-Keyvalues, and every action remains scoped to the exact tenant, actor, enrollment, capability generation, expiry, and server-authored HAL affordance. Raw secret values are accepted only by the bounded binary write route and are never returned. Regenerate clients fromschemas/openapi_islamu-event.json. The generated Setup live release manifest keeps target enrollment, readiness, writes, and saved profiles disabled until a later workflow-activation phase closes.
-
Breaking (pre-v1): admission write bodies now use authoritative DTO schema names. Check-in, undo, batch-item, batch, scanner check-in/undo/batch, and scanner-capability issue/revoke request schemas now end in
RequestDto. The former unsuffixed schema names and generated client types are removed without aliases; payload shapes and routes are unchanged. Regenerate clients fromschemas/openapi_islamu-event.json. -
Additive: bounded configuration import sessions now have separate instance and tenant APIs. Instance administrators upload strict v1alpha2
ConfigurationManifestbytes throughPOST /api/control-plane/configuration-import/sessions; tenant administrators upload strictTenantConfigurationPackagebytes throughPOST /api/tenants/{tenantId}/configuration-import/sessions. Uploads use the exact artifact media type, a 4 MiB raw-binary ceiling, a dedicated rate and timeout policy, and private no-store responses. The one-time response capability is supplied only throughX-Configuration-Import-Tokenfor preview, refresh, and cancellation. Request bodies can select sections, mappings, apply mode, and granted approvals but cannot select target, revision, digest, or snapshot authority. Missing, wrong-scope, and invalid capabilities are indistinguishable; stale, expired, cancelled, replayed, and integrity-invalid sessions fail through stable value-safe RFC 7807 codes. Control-plane and tenant HAL resources advertise creation only after exact instance- or tenant-setting authorization. Regenerate clients fromschemas/openapi_islamu-event.json; no compatibility aliases exist. -
Breaking (pre-v1): configuration portability is v1alpha2-only and now includes reviewed apply, tenant packages, receipts, and forward rollback. The v1alpha1 schema/media identities are removed. Instance and tenant session routes expose server-authored selectable sections, preview/refresh, atomic apply, bounded history, operation receipts, cancellation, and snapshot-backed rollback sessions. Tenant administrators export a distinct
TenantConfigurationPackagefromGET /api/tenants/{tenantId}/configuration-package/export; source metadata never selects the target tenant. Operation results expose typed effect state, retry count, fidelity verification/digest, and named omissions without values. All write affordances remain authorization- and HAL-gated; stale previews, unavailable snapshots, and unsupported sections fail closed. -
Additive: mutually approved direct configuration transfer stages artifacts without bypassing import authority. Instance and tenant routes under
/api/configuration-transfersbind public HTTPS/443 destination proof, nonce, digest, size, expiry, distinct source/destination actors, and bounded resumable chunks. Completion is replay-safe; promotion creates an ordinary import session that still requires preview and apply. The protocol never deletes source state or transports secrets, application data, or backup authority. -
Additive: role-labeled published legal documents are available through one anonymous rendering contract.
GET /api/legal-documents/{kindCode}selects only the target scope's latest active immutable publication, resolves the negotiated locale and reviewed operator identity, and returns deterministic constrained-Markdown HTML with publication version, effective time, and content digest. Unknown, unpublished, retired, non-public, unresolved, or integrity-invalid content fails closed through value-safe RFC 7807 responses. The response cache varies by tenant host andAccept-Language; the generated client operation isGetPublicLegalDocument. -
Breaking (pre-v1):
/termsand/privacyno longer contain static legal prose. Both pages consume the generated API contract and identify the instance or tenant operator responsible for the publication. If no reviewed version exists, they show a neutral unavailable state rather than inventing substitute terms or privacy claims.
- Added: machine-readable ticketing deployment capability status.
GET /api/deployment/ticketing-capabilitiesreturns the authoritativeproduction-approved,test-only, ordisabledstatus, reason code, required external gate codes, revision, and reference topology for every ticketing capability. The endpoint is read-only and non-cacheable. The authoritative OpenAPI and generated Blazor client include the matrix. - Added: event add-on catalog, order, fulfillment, and refund allocation contracts. Public catalog reads, organizer HAL management, buyer reservation, fulfillment, and exact partial-refund allocation use immutable minor-unit snapshots and stable idempotency. Add-on authority remains separate from admission credentials and check-in.
- Breaking: ticketing recovery and release status fail closed. Restored
deployments require the exact consistency manifest, advanced bearer
generations, resolved provider ambiguity, and ordered worker/sales reopen.
Protected delayed payout remains
disabled; there is no payout route, operation, HAL relation, scheduler job, secret, generated-client method, or UI action.
- Breaking: legal identity is structured and responsibility-specific.
Tenant admins now use the HAL-gated
GET/PATCH /api/tenant/settings/documents/directory-operator-identityresource with grouped PATCH presence and optimistic concurrency. Anonymous public settings/shell expose separatedirectoryOperatorandinstanceOperatorobjects and return non-cacheable RFC 7807503withtenant_identity_unavailablewhen tenant disclosure identity is unavailable. - Breaking: paid disclosures no longer expose branding-derived prose or
flattened operator fields. Checkout composition exposes structured
directoryOperator; paid acceptance groupsorganizerMerchant,tenantDirectoryOperator,instanceOperator, andpaymentOperationsand carries a versioned acceptance template. The immutable server-side snapshot additionally fences the exact organizer payment connection, Connect platform, external account, and merchant country before claim or provider handoff. Consumers must regenerate from the authoritative OpenAPI document; no compatibility alias is provided. - Breaking: tenant onboarding completion now carries identity explicitly.
POST /api/tenant/onboarding/completeacceptsCompleteTenantOnboardingRequest, grouping policy settings with the required directory-operator identity and the expected current identity concurrency stamp. Existing identity updates without the exact stamp conflict rather than overwrite; new identities, policy, branding, and onboarding completion commit atomically.
- Additive: check-in identity and incident controls are explicit in the initial Phase 21 surface. Successful single and batch check-ins include the persisted UUIDv7
checkInId; undo is bound to that exact active fact, and HAL embeds per-result undo affordances only while valid.GET /api/events/{eventId}/admission/check-ins/{checkInId}returns bounded fact detail. Authenticated health, stop, restore, and reconcile routes operate on one body/querytargetId, use closed reason codes, and expose permission-matched HAL relations. Scanner issuance rejects missing, cross-event, stopped, or non-PlatformManagedtargets before secret material is created. - Additive: admission check-in reporting is target-exact and export-safe.
GET /api/events/{eventId}/admission/check-ins/summaryrequirestargetIdand returnslongCheckedIn/Undone and Active/Inactive counts, target type, and an hourly last-activity bucket; rejected attempts are not reported as persisted facts. AuthenticatedGET /api/events/{eventId}/admission/check-ins/audituses an opaque keyset cursor over immutable occurrence time and UUIDv7 fact identity, accepts a page size up to 100, and returns only cursor, action, outcome, target type, hourly time bucket, and next cursor. Both reads requireEventCheckInView, are private/no-store and rate limited, return generic absence, and are exposed through permission-matched HAL relations. Admission limiter saturation telemetry uses fixed enum labels only. - Breaking (pre-v1): admission undo reasons are closed codes. Staff and scanner undo bodies now require
reasonCodewith one ofOperatorCorrection,DuplicateScan,WrongTarget, orExceptionalReconciliation; the former free-formreasonproperty is removed. Append-only facts persist only the enum identity and the compensated check-in UUIDv7.
-
Breaking (pre-v1): generated browser response contracts now use deterministic nominal records. The pinned NSwag output is passed through the repository-owned Roslyn transformer, producing 653 record contracts with init-only properties and value-free diagnostic printing while preserving JSON names, requiredness, nullability, methods, HAL relations, exceptions, file responses, and wire payloads. System.Text.Json extension data remains settable and AOT unknown
_linksround trips are supported. Protocol input graphs, both sides of inheritance relationships, HAL wrappers, and 25 reasoned mutable UI/service contracts intentionally remain classes. C# consumers must rebuild and replace post-construction response mutation with object initialization orwith; no compatibility-generated client is retained. See RECORD_CONTRACTS.md for the migration and verification workflow. -
Breaking (pre-v1): account ticket detail is expanded and QR/print delivery is an explicit credential-reissue write.
AdmissionTicketDtonow includesregistrationOrderId,holderDisplayName,ticketTypeName,issuedAtUtc, and typedentitlementswith human event/day/session presentation; recovery, QR, and print delivery documents carry the same presentation facts. Its HAL resource includesregistration-orderso clients follow authoritative order affordances rather than infer refund/support authority./api/tickets/{ticketId}/qrand/api/tickets/{ticketId}/printchanged from GET to POST and operation IDs are nowReissueCurrentAdmissionTicketQrandReissueCurrentAdmissionTicketPrint. Each successful call intentionally rotates the admission credential before returning the one-time bearer document; ordinary ticket list/detail GETs never mutate credential authority. Active-ticket HAL relationsqr-codeandprintadvertise POST, and revoked tickets omit both relations while direct writes fail closed. Browser clients use/tickets,/tickets/{ticketId}, and/tickets/recovery; recovery capabilities are scrubbed from the URI fragment before same-origin BFF exchange. -
Added authenticated
POST /api/geocoding/address-suggestionsas a private, no-store HAL collection with bounded local search, governed source/visibility metadata, dedicated throttling, and no client-owned provider or coordinate authority. AddedPOST /api/location/{id}/address-approvalwithIf-Matchoptimistic concurrency; clients must render approval only from theapprove-tenant-addressHAL relation. Regenerated OpenAPI, endpoint inventory, and the NSwag Blazor client. -
Breaking (pre-v1): location address acquisition is now provider-neutral and token-authorized.
CreateLocationDto,UpdateLocationDto, and nested event location writes no longer accept caller-authored coordinates or current-tenant authority. Create/update may instead carry an opaque, short-livedaddressSelectionToken; the server binds it to tenant, actor, optional organization, write purpose, location target, expected concurrency stamp, provider configuration, and Photon dataset revision before applying the normalized address and coordinate pair atomically. The optional self-hosted Photon adapter is disabled by default, uses bounded resilience and query-free readiness, and never exposes its endpoint or exact coordinates to the browser. Address suggestions remain useful with local-only results when Photon is disabled, limited, unavailable, or timed out. Generated clients must consume typed HAL_embedded.itemsandAddressProviderOutcome, preserve provider attribution, and gate autocomplete/create/edit/delete/approval actions solely from advertised HAL links. Apply the generated address-governance migrations for the configured database provider and regenerate clients fromschemas/openapi_islamu-event.json; no compatibility reader or alias is provided. -
Breaking (pre-v1): location writes no longer accept raw coordinates.
CreateLocationDto,UpdateLocationDto, and nested event creation'sCreateEventLocationDtoremovelatitudeandlongitudewithout aliases or compatibility readers. Generated-client and browser consumers must remove raw coordinate controls, assignments, and defaults. Manual address create/update transitions carry address data only and clear previously derived coordinates on change. Authorized coordinate reads remain available throughLocationDtoand the existing purpose-specific event-location disclosure contracts; routes, operation IDs, HAL capabilities, GET/write authorization, private-home classification/ownership consent, ETag/If-Matchconcurrency, and provider-selection seams are unchanged. Regenerate in order withdotnet build src/Explore.API/Explore.API.csproj --configuration Release --no-restore, theApiContractInventory_Generate_WritesMarkdownToDocsintegration-test filter, anddotnet msbuild src/Explore.Blazor.Client/Explore.Blazor.Client.csproj /t:GenerateApiClient /p:Configuration=Release /p:Restore=false /m:1. -
Breaking (pre-v1): ConfigurationManifest export is one explicit whole-instance download resource. Authenticated instance administrators export the current deployment from
/api/control-plane/configuration-manifest/export. The operation defaultsview=Overrides, accepts explicitview=Portable, returnsapplication/vnd.islamu.configuration-manifest.v1alpha2+jsonattachments, and exposes permission-filteredexport-configuration-overrides/export-configuration-portableHAL relations. Each file contains the approved instance section and all active tenant sections, declares sensitive-value omission, and never represents credentials or backup state. Tenant administrators use the separate tenant-package contract and never receive a partial instance manifest. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility window before v1.0. -
Breaking (pre-v1): reporting-intake administration is now a dedicated server-authored policy resource. Authenticated current-tenant clients read and update
/api/tenant/settings/reporting-intakethrough stableGetTenantReportingIntakePolicyandUpdateTenantReportingIntakePolicyoperations. The response owns effectiveenabled, source, instance-lock,canDisable, and reason metadata; the update body accepts onlyenabled, with tenant and actor identity derived by the server. Clients must gate mutation through the HALeditrelation rather than local role, lock, or publication-safety calculations. Instance locks and unsafe disablement return stable RFC 7807 conflict codes. -
Breaking (pre-v1): eight create/import request bodies no longer accept current-tenant authority.
CreateCategoryDto,ImportEventRequestDto,CreateEventSessionDto,CreateEventSessionAgendaItemDto,CreateEventSessionLanguageDto,CreateEventSessionSpeakerDto,CreateLocationDto, andCreateTagDtoomittenantId. Controllers derive tenant identity fromITenantContextor the route-selected persisted session/event context; sending the removed property now fails closed as an unsupported JSON field with a 400 validation ProblemDetails response. Routes, operation IDs, HAL relations, and authorized success behavior are unchanged. Regenerated clients must stop populating these tenant properties; no compatibility alias or reader is provided. -
Contract metadata correction: affected authenticated create/import operations document authorization denials as 403 ProblemDetails. Runtime behavior is unchanged; OpenAPI now accurately describes the existing fail-closed MediatR authorization path for category, tag, location, event-session agenda-item, and event-session language creation.
- Breaking (pre-v1): paid start requires explicit current acceptance. Authenticated and guest payment-start requests now carry
PaidOrderAcceptanceAcknowledgementDto; absent, false, or stale acknowledgement fails closed. New private/no-storeGET .../payment/acceptanceoperations return exact merchant, independent instance operator/official status, delivery, line/money, refund language/version/text, support/complaint, provider/charge, and statement facts. HAL addspayment-acceptance; clients must render and acknowledge those server facts before usingstart-payment. - Behavioural: paid publication/claim/dispatch stop-sale. Incomplete instance governance and global/event stop-sale remove new-sale HAL affordances and block direct sale paths while existing payment status, signed webhook intake, reconciliation, support, and recovery remain available. Historical attempts without acceptance remain historical and cannot be backfilled or dispatched.
- Breaking (pre-v1) contract correction:
EventLocationDisclosureStateis published with its real wire values. The OpenAPI schema previously declared the CLR names (ToBeAnnounced,PrivateVenue,NeedsPrivacyReview), while the API has always serialized snake_case (to_be_announced,private_venue,needs_privacy_review) through a type-level converter. Any generated client therefore failed to deserialize every multi-word state. The schema now reports what the server actually sends, and the OpenAPI enum generator resolves values through the type's own converter rather than assuming CLR names, so a converter-backed enum can never ship a mismatched contract again. Regenerate fromschemas/openapi_islamu-event.json. - Breaking (pre-v1) contract correction: HAL-wrapped enum properties are typed, not integers.
HalResourceOf*schemas built from the HAL catalog previously inlined cataloged string enums asinteger(for exampleentityTypeName,propertyType,exposureLevelon custom-property definitions) and emitted converter-backed enums as an untyped{}. Both now reference their component schema, so generated clients expose the enum type instead ofint?/object.EventLocationManagementDto.fieldsand.policylikewise referenceEventLocationManagementFieldsDtoandEventLocationDisclosurePolicyDtoinstead of anonymous generated shapes. - Breaking (pre-v1):
GET /api/location/by-city/{city}andGET /api/location/by-country/{country}are removed, along with their CQRS slices, repository reads, and generated client methods. They enumerated exact venue addresses — including private homes — for any caller holding tenant-wide location view, with no disclosure evaluation. Event-scoped venue reads live under/api/events/{eventId}/locations. - Additive:
GET /api/events/{eventId}/locations/managementreturns every EventLocation on the event asHalCollectionResource<EventLocationManagementDto>. The existing/locations/reviewroute is now the remediation-filtered specialization of the same read; both are authenticated,private, no-store, and carry per-rowedit/remediate-locationaffordances. - Additive: consent-backed private home ownership.
POST /api/location/{id}/private-homeandPOST /api/location/{id}/private-home/ownershiptakePrivateHomeOwnershipConsentDto(consentAcknowledged,consentVersion) plus the current stamp inIf-Match. Ownership is always claimed by the authenticated actor; the aggregate rejects any transfer where the consenting user is not the new owner.LocationDtoadds the descriptivelocationKindIdso management surfaces can offer the right consent flow. - Additive:
event-location-privacy-reviewreadiness check and theExplore.EventLocationPrivacymeter. The check reports aggregate remediation backlog depth and its operator threshold and degrades above it. The meter exposesevent_location_privacy_disclosures_total(purpose,state),event_location_privacy_corrections_total(event_type,status), and theevent_location_privacy_review_queue_depthgauge. All dimensions are bounded and identifier-free.
- Breaking (pre-v1): Cerbos policy sync accepts
AuthorizationPolicyPackageSyncRequestDto; saved authorization-provider DTOs no longer accept Admin API credentials. An empty sync request uses deployment/Infisical credentials. A complete username/password pair overrides them for that request only; partial pairs fail validation and values are never persisted or returned. - Behavioural: runtime Cerbos authorization is gRPC-only. HAL capability planning no longer reads policy revision/status through the Admin HTTP API. Package sync/status remain explicit privileged operations, and the Admin HTTP client now times out after 10 seconds.
- Additive: registration-order payment contract. Account-owned and guest-capability orders add idempotent start/retry and private authoritative status resources; Studio adds a bounded
manage-paid-event-commercestatus read. HAL addsstart-payment,payment-status,checkout-redirect,retry-payment, andstudio-payment-status, with retry emitted only for parked pre-handoff work. Start/retry never perform synchronous provider creation, status omits provider/account/request/idempotency/capability/PII/raw-error fields, and Checkout navigation resolves through an antiforgery-protected same-origin BFF route. - Security correction: payment replay and redirect scope. Payment idempotency now includes resolved route values and a one-way guest-capability scope; matching guest replays re-run current access/expiry checks instead of returning stale success. Every
RequireIdempotencyKeyoperation exposes a required generated header argument. Checkout targets require an unexpired payable order and an exact open unpaid unexpired money-matching session. Direct forwarding headers no longer control BFF host or client-IP context unless the peer is explicitly trusted. - Breaking (pre-v1): Event HAL no longer advertises
archivefor a Published Event. Published Events retain authorizedcancel, light/heavy moderation, and other applicable actions; ordinary fixed lifecycle affordances are owned by Domain predicates, while heavy redaction remains a distinct Application/API-owned irreversible safety override. Clients must render lifecycle controls from_linksand remove any local status/claim fallback. Routes and operation IDs are unchanged, with no compatibility affordance before v1.0. - Breaking (pre-v1):
EventSessionDtoandEventSessionListDtoaddparentEventStatusId. Session detail and collection lifecycle links now evaluate the current session status, parent Event status,startTime,endTime, andendTimeTypethrough the same Domain predicates as command execution. In particular, publish and complete require a Published parent Event. Generated clients must regenerate fromschemas/openapi_islamu-event.json; there is no compatibility alias before v1.0. - Behavioural: same-target ordinary fixed Event and EventSession lifecycle commands succeed as idempotent no-ops. They do not mutate lifecycle state, audit timestamps, or downstream effects, and HAL omits the redundant same-target action. Already-Moderated light moderation may repair missing child-session moderation, but runs only actual repair effects and does not duplicate the moderation record, outbox, or federation work. This aggregate-level guarantee is independent of
Idempotency-Keyresponse replay.
- Additive:
GET /api/instance/settings/authz-provider/package/statusreturnsAuthorizationPolicyPackageStatusDto— provider mode, package id and content hash, the policy revision observed in the Cerbos store, whether that revision is certain, health, warnings, and a per-issue recovery action. Requires instance administrator or active setup-secret authority. Deliberately distinct from the anonymousauthz-provider/statusreadiness probe, which must not disclose whether authorization is currently degraded. - Breaking (pre-v1): the
cerbos.failure_modegovernance setting was deleted. It was inert in two ways at once: its definition documented the valuesdeny/allowwhile its parser only recognisedopen, andopenwas ignored at runtime regardless. BYO PDP outages have always failed closed into safe mode and continue to. No client contract changes; the setting simply no longer exists.
- Breaking (pre-v1): control-plane command failures now return RFC 7807
ProblemDetailsinstead of a bareBaseCommandResponsebody. The sharedMapCommandResponsehelper — used by 15 control-plane endpoints covering tenant plans, tenant configuration, tenant lifecycle, and deployment mode — previously serialized the command response object itself on failure, while every other failure path in the API returned ProblemDetails. Two endpoints in the same product could therefore fail in two different formats. Failures now map as:not_found→ 404 ProblemDetails,admin_required→ 403,authentication_required→ 401,concurrency_conflict→ 409 (carrying the command id/message as extensions), everything else → 400ValidationProblemDetails. Success responses are unchanged.ProducesResponseTypemetadata was corrected accordingly, so generated clients now type these error bodies as ProblemDetails. - Controller partition, no contract change: the five oversized controllers (
Event,RegistrationOrder,Webhooks,InstanceSettings,ControlPlane) were split by route capability into focused controllers. Every route template, HTTP verb,Name = RouteNames.*, authorization attribute, endpoint classification, and response metadata was carried over verbatim. Verified: 756 operations before and after, identical operationIds, identical request/response schemas, identical components, and 756 identical generated client methods.InstanceSettingsControllerwas deleted after all 47 of its actions moved; it had become an empty shell. - The only externally visible difference is the OpenAPI
tagsarray, which is derived from the controller class name. Tags now name the real capability — for exampleControlPlaneTenantPlan,WebhookEndpoints,InstanceStorageSettings— instead of grouping every operation of a former mega-controller under one label. This affects documentation grouping only; the NSwag client usesSingleClientFromOperationId, so no generated method name, namespace, or signature changed. No client regeneration is required beyond the checked-in artifact already updated here. - Contract fix:
HalCollectionEmbeddedOfSchedulerAdminJobDtonow referencesHalResourceOfSchedulerAdminJobDtoinstead of emitting an untyped item schema, so generated clients type scheduler job collections rather than falling back toICollection<object>. - Contract fix:
HalResourceOfRegistrationAnswerAnalyticsDtopreviously emitted an empty wrapper schema because it was never registered inHalOpenApiSchemaCatalog. It now emits its full property set, and the three nested analytics aggregates are emitted as named components (RegistrationAnswerFieldAggregateDto,RegistrationAnswerAggregateCellDto,RegistrationAnswerNumericAggregateDto) rather than anonymous inline shapes. The hand-written Blazor mirror that compensated for this was deleted. - Breaking (pre-v1):
RegistrationFormTemplateInputandInstantiateRegistrationFormTemplateInputwere renamed toRegistrationFormTemplateInputDtoandInstantiateRegistrationFormTemplateInputDtoto satisfy the repository's DTO naming convention. Clients must regenerate.
- Additive provider-neutral promotion contract for API version
0.1: authenticated organizer management under/api/events/{eventId}/promotionsadds private/no-store list/detail plus idempotent draft, revise, publish, immediate server-timed revoke, and code-rotate operations authorized exclusively bymanage-paid-event-commercefor the persisted organizer actor. Draft HAL permitspublish; published HAL permitsrevise-promotion,revoke, androtate-promotion-code; the collection permitscreate-promotion. Safe DTOs expose versioned definition, eligibility, discount, window, limits, and masked-code facts only; raw codes are returned asissuedCodeonly on successful create/rotate, while lookup digests, key versions, secret metadata, tenant/actor authorization IDs, and reservation IDs stay out of JSON and generated clients. - Additive registration-order promotion contract for API version
0.1: account-owned andX-Registration-Order-Capabilityguest orders add idempotent apply/remove routes beneath/api/events/{eventId}/registration-orders. Guest mutations usePublicTransactionalrate limiting and BFF antiforgery for browser traffic. Invalid ownership/capability, an empty, overlong, or unavailable submitted code, wrong scope, exhausted limits, and active-reservation conflicts collapse to the same registration-order404; syntactically malformed JSON retains the global safe400contract. Order reads add pre-discount, discount, post-discount, fee, organizer-earnings, contribution, and final totals plus a masked applied label; HAL emits exactly one ofapply-promotionorremove-promotionwhile checkout-ready. Phase 18 payment-attempt, Stripe Checkout, webhook, and reconciliation APIs remain absent. - Contract correction: all five organizer promotion writes and all four authenticated/guest redemption writes now declare
Idempotency-Keyas an explicit OpenAPI header parameter. Regenerated clients expose the parameter on every mutation, and the Blazor services send a fresh UUIDv7 key per operation. Guest generated-client hooks preserve their existing fallback for other guest writes but do not append a second key when the generated method already supplied one.
- Additive paid-event policy and publication-preflight contract for API version
0.1: private/no-store instance and tenant policy resources expose an instance ceiling and tenant-only narrowing without policy or tenant identifiers; ticketing addspublication-preflight, commercial disclosures, bounded organizer payment-connection readiness, hosted onboarding start/return/refresh, and fresh preflight-enforced publish. Browser connection state contains only readiness fields, while onboarding returns only an absolute HTTP(S) URL and a reuse flag. Paid actions are available exclusively through exact HAL relations and requiremanage-paid-event-commercefor the event's persisted organizer actor; no administrator fallback merchant exists.
- Additive governed contact-sharing contract for API version
0.1:EventContactShareConsentnow supportsUser,RegistrationPurchaser,RegistrationParticipant, andGuestContactsubjects with one current row per tenant/typed-subject/recipient/purpose and append-only grant, regrant, and withdrawal history. Export requests accept optional event provenance and select only active matching-purpose consent; every CSV/TSV export persists an export audit and per-consent item evidence. Managed platform Event HAL may advertiseexport-attendeesonly for a verified organization organizer and an authorizedExportSharedContactsdecision. Studio renders that relation as an in-page Export consented contacts action and performs no role or claim inference. - Additive registration-retention schema: normalized policies assign immutable
RetentionUntildeadlines to answers, sensitive values, order PII, and participant PII. The bounded daily cleanup deletes expired registration data while retaining consent history and export audit evidence.
- Additive registration-provider framework contract for API version
0.1: anonymousPOST /api/integrations/registration/{provider}/{bindingId}/callbackrecords bounded provider-submission callbacks through the shared incoming-webhook ledger and returns202 Acceptedfor verified, duplicate, malformed, unknown, stale, or parked evidence, with413reserved for oversize payloads. Worker execution validates the Data Protection receipt and parks unverifiable, unknown-tuple, blocking-drift, low-trust, stale/out-of-order, and unsupported mirror/manual cases for reconciliation. - Additive authenticated provider-management APIs under
/api/tenants/{tenantId}/events/{eventId}/registration-providersmanage connections, approved origins, bindings, draft mappings, channel order/fallback, launch descriptors, health, queue, manual import, retry/resolve, and reconciliation. Health and queue DTOs expose bounded status, issue codes, callback age, drift, lag, generation, and capability codes only; they omit answers, PII, capabilities as auth, raw provider payloads, provider URLs, secret refs, and callback bytes. HAL relationsmanage-registration-channelsandview-registration-provider-healthare the event-level authority; generated clients must gate Studio actions from_links. - Additive BFF/browser embed boundary: same-origin
/bff/registration-provider-embed/tenants/{tenantId}/events/{eventId}/workflows/{workflowId}/requirements/{requirementId}/channels/{channelId}/bindings/{bindingId}fetches the API launch descriptor server-side, rejects query strings and non-approved HTTPS targets, emits per-routeContent-Security-Policywith exactframe-src, and renders only a sandboxed iframe plus new-tab fallback. Browser iframe load/navigation events are never completion authority; clients poll registration requirement/order status.
- Additive native registration-submission contract for API version
0.1: authenticated order owners and capability-scoped guests can read typedrequirement-progress, launch an exact requirement/channel/form/version descriptor, submit answers, and skip optional requirements beneath/api/events/{eventId}/registration-orders. Progress and launch share one authoritative resolver and fail closed when the event has zero or multiple published forms, so a descriptor omitted by progress cannot be launched directly. Launch returns a one-time attempt capability in bothX-Registration-Attempt-Capabilityand its private/no-store response body so generated clients can retrieve it without URLs or browser storage; only its hash is persisted. The launch resource includes the attendee-safe pinned form, server-authored subject keys, progress,submit, and conditionalskipHAL affordances. Optional skip atomically records skipped fulfillments, consumes the attempt under optimistic concurrency, and evaluates the idempotent finalization effect in one database transaction. Transactional operations declareIdempotency-Keyexplicitly, guest writes also validate the order capability, and validation ProblemDetails expose stable issue codes and form field keys without answer values. Order HAL advertisesrequirement-progressonly while requirements await completion, while the separate walk-in flow retainsoptional-questionnaireas its policy-gated relation. - Additive registration-form authoring contract for API version
0.1: verified event organizers and explicitly assigned registration managers can manage workflows, requirements, forms, immutable draft versions, sections, fields, options, bounded rules, publication preflight, and publication under/api/events/{eventId}/registration-workflows. Every write requires a strong quotedIf-Match; stale writes return409, failed preflight returns stable RFC 7807 validation details, and publication pins the generator-produced data, UI, logic, and mapping artifacts plus their SHA-256 schema hash. Event and authoring resources expose only server-authorized HAL affordances, includingmanage-registration-workflow; contributors, listing submitters, tenant-only curators, machines, ambiguous organizers, and unrelated tenants receive no management relation. - Additive participation-requirement attachment contract for API version
0.1: verified organizers and explicit registration managers attach or idempotently detach event-owned requirements at/api/events/{eventId}/participation/requirements/{requirementId}with strongIf-Matchconcurrency. Workflow requirement resources expose persistedisAttachedstate and advertise exactly one permission-checkedattachordetachHAL relation. Walk-in events may expose one published standalone form descriptor at the anonymous/api/events/{eventId}/participation/optional-questionnaireroute, advertised only through theoptional-questionnaireHAL relation; invalid or absent descriptors return non-leaking404, and attachment changes create no registration-order or participant state.
- Additive registration-participant contract for API version
0.1: current-account and capability-header guest order surfaces now expose private/no-store participant reads plus idempotent participant, assignment, and deferral writes. Organizer discovery uses permission-gatedview-participantsHAL relations only for platform-managed events; guest capability values remain header-only and never enter JSON or links. - Breaking registration-order lifecycle contract naming for API version
0.1:RegistrationOrderLifecycleResponseandGuestRegistrationOrderLifecycleResponseare renamed toRegistrationOrderLifecycleResponseDtoandGuestRegistrationOrderLifecycleResponseDtoacross Application, API, and generated Blazor contracts. The pre-1.0 contract has no aliases or compatibility shims; regenerate clients fromschemas/openapi_islamu-event.json.
- Additive deletion-conflict contract for API version
0.1:DELETE /api/eventday/{id}andDELETE /api/eventsession/{id}now return typed409 ConflictProblemDetails when a published ticket entitlement references the schedule target. Successful deletion remains204 No Content, missing targets remain404, and generated clients now surface the conflict response instead of treating a guarded no-op as success. - Setup rate-limit reliability correction for API version
0.1: authenticated setup-secret-gatedPATCH /api/instanceonboarding/profileandPOST /api/instanceonboarding/completerequests retain the configured five-per-60-second fixed-window protection but now use a separate IP partition from anonymous setup-secret attempts. Legitimate validation and provider setup traffic can no longer exhaust the final instance-claim budget; anonymous and setup-secret-authenticated provider routes remain in the shared anonymous IP partition. - Reliability fix for API version
0.1: concurrentPOST /api/events/{eventId}/ticketing/draftandPOST /api/events/{eventId}/ticketing/draft:cloneattempts that collide on catalog-version uniqueness, plus duplicate capacity-pool name races, now return retryable409 Conflictwithevent_ticketing_concurrency_conflictinstead of500; clients should reload ticketing state and retry. - Breaking Event pricing correction for API version
0.1:EventDto,EventListDto, and generated client models no longer expose the legacy scalarEvent.PriceorEvent.CurrencyCode, and EventSession contracts no longer exposeEventSession.Price. The persistence model removes the matching Event and EventSessionpriceandcurrency_codecolumns without a compatibility field or decimal fallback. Published Event reads instead expose nullableticketPriceSummary, derived from the publishedEventTicketCatalogVersion, withsummaryCode, nullablecurrencyCode,currencyMinorUnitDigits, andfromAmountMinor. Clients must regenerate fromschemas/openapi_islamu-event.jsonand render the summary rather than treating an Event or session scalar as pricing authority.
- Breaking storage-upload hardening for API version
0.1:POST /api/storageobject/generate-upload-url(GenerateStorageObjectUploadUrlAsync) and caller-authoredPOST /api/storageobject(CreateStorageObjectAsync) are removed withUploadRequestDto,UploadUrlResponseDto, andCreateStorageObjectDto. Browser uploads now converge on authenticated same-origin BFF sessions backed by provider-neutralCreateStorageUploadSessionAsync,UploadStorageUploadSessionContentAsync, andCancelStorageUploadSessionAsync; there is no compatibility route, DTO, client method, or fallback before v1.0. Browser and AI image ingress accepts exact JPEG, PNG, GIF, and WebP, while server and ATProto paths additionally accept AVIF; every accepted raster is structurally framed through exact EOF and rejects active tails. ID-based public delivery serves only active safe raster images inline, authenticated non-raster content uses a sanitized attachment disposition, and presigned downloads force attachment disposition and remain no-store. Authenticated StorageObject detail/list responses, their HAL schemas, and generated client types no longer expose providerobjectKeyvalues. - Additive authenticated setup contract for API version
0.1:PATCH /api/instanceonboarding/profile/SaveInstanceOnboardingProfileacceptsSelfHostOnboardingProfileDtoonly while setup-secret authority is active and onboarding is incomplete. It persists only the existing non-secret display name, default sender address, authoritative instance host, and default locale settings; it does not persistPurpose,TimeZone, secrets, route history, snapshots, or browser state. Instance onboarding status advertises thesave-profileHAL relation only for authenticated callers with active setup-secret authority. The endpoint returns typed validation/auth/setup/rate-limit ProblemDetails and is represented by regeneratedSaveInstanceOnboardingProfileAsyncclient code. - Breaking global/contextual Actor read correction for API version
0.1: publicActorDtoandActorListDtoresponses no longer serialize legacytenantId, privateuserId, or tenant storage-object identifiers. Global list, ID, DID, and handle repository reads now return only active global Actors and active unsuspended ATProto identities.GET /api/actor/by-tenant/{tenantId}returns only Actors discoverable through approved visible unsuspended Organization/Group participation or eligible public federated Event materialization and may apply public tenant profile overrides. Actor subscriptions remain tenant-local: create/detail/list/fanout use that same discoverability rule, hidden and cross-tenant targets fail closed, and unsubscribe can still reach an existing durable row. Actor HAL emitssubscribeandsubscriptiononly from a non-serialized request-local discoverability decision plus normal authorization; global collections expose no tenant-local subscription affordance. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility field or global-follow behavior before v1.0. - Additive Organization participation evidence contract for API version
0.1: authenticated Organization administrators use an organization-bound BFF route to reserve a private PDF Document upload without exposing participation storage ownership. Evidence submission accepts only the finalized active exact-tenant private Document owned by the pendingOrganizationTenant; tenant administrators review the retained row separately, and review does not auto-approve participation. Safe HAL DTOs omit tenant/participation IDs, provider keys, object keys/URIs, reviewer identity, and document content. Protected document download and review remain HAL-gated. - Breaking Actor identity authority cleanup for API version
0.1: genericPOST /api/actor,PATCH /api/actor/{id}, andDELETE /api/actor/{id}are removed from routing, OpenAPI, and the generated browser client. Creation, promotion, linking, moderation, and retirement remain dedicated verified workflows. Blazor renders only authoritative/actors/{actorId}through the global detail read; tenant-aware consumers use the separate exactGET /api/actor/by-tenant/{tenantId}/{id}fail-closed contextual API lookup. Both surfaces use HAL-only subscription affordances, and no tenant-contextual Actor profile page is exposed. - Additive event-ticketing management contract for API version
0.1: authenticatedGET /api/events/{eventId}/ticketingreturns the versioned catalog, ticket types, capacity pools, normalized lookup metadata, and state-specific HAL affordances. Draft creation, cloning, ticket-type and pool mutations, and publication remain separate write operations under the same event route. Money is represented as integerlong ...Minorvalues. Catalog relations usecreate-draft,clone-draft,create-type,create-pool,publish, and item-leveleditordelete. The response does not embed instance monetization settings. - Additive instance platform-monetization contract for API version
0.1: authenticated Admin-classGETandPUT /api/instance/settings/platform-monetizationread and replace the active versioned fee policy and separate contribution setting. Fixed charges use integer minor units and percentages use integer basis points, where10_000 = 100%. The HAL response emitseditonly for authorized instance-setting updates, and both handlers independently recheck instance-administrator authority. Defaults remain disabled and zero. Tenant administrators, organizers, and curators have no management path.
- Additive global Actor moderation contract for API version
0.1: authenticated instance administrators can callPOST /api/actor/{actorId}/moderation/suspend,POST /api/actor/{actorId}/moderation/reinstate,POST /api/actor/atproto-identities/{identityId}/moderation/suspend, andPOST /api/actor/atproto-identities/{identityId}/moderation/reinstate. Each request body contains onlyreasonCode; the route selects the moderation action. Commands authorize the existingglobal-actor-moderationinstance-setting update and handlers recheck instance-admin authority. Same-state retries are idempotent, identity reinstatement preservesIsActive, and successful moderation invalidates public Event caches. All four operations declareWritePolicyand typed429 Too Many Requestsmetadata. OpenAPI and the generated client were regenerated from source. - Public Event and federation eligibility now compose global Actor state, exact ATProto identity state, tenant participation, Event lifecycle, record ownership, current tenant presentation, and record tombstone state. One central eligibility gate protects public actions, locations, program, agenda, days, sessions, session languages, aspects, sitemap, AI reference search, and Open Graph reads before disclosure, counting, or pagination. Authorized
view-managementreads remain available for ineligible Events through additiveGET /api/eventday/management/by-event/{eventId},GET /api/eventsessionlanguage/management/by-event/{eventId}/by-session/{eventSessionId},GET /api/event/{id}/management-aspects/islamic, andGET /api/event/{id}/management-aspects/techoperations. The generated client, MCP management context, and Blazor management flow use those secured operations; management Event collection items use management detail/session links and omit public report affordances. Inbound public Draft, Cancelled, and Completed projections remain visible, Published projections deduplicate to the local Event branch, and Moderated, Archived, deleted, non-public, or identity-ineligible projections are hidden. Outbound planning skips ungrounded ineligible Creates. Grounded work transactionally converts settled ownership and pending/processing Event mutations to fenced Delete compensation after Actor or exact-DID suspension; exact-identity reconciliation is DID-scoped, soft-deleted source Events remain available for cleanup, and local echoes require exact outbound ownership plus current local eligibility. Delete compensation proceeds only after exact ownership, session, source-version, record-key, and CID checks. RSVP behavior is unchanged. Moderation evicts tagged Event-detail HybridCache entries and five process-local output-cache tags, so cross-replica cache consistency is not claimed. This slice has no schema, migration, or Cerbos policy change.
- Breaking tenant-settings write cleanup for API version
0.1:PUT /api/tenantonboarding/settingsand operationUpdateTenantOnboardingPolicySettingsare removed with their broad CQRS handler and generated client method. Independent tenant controls use exact-key or category settings writes; public-experience and announcement-bar changes use their dedicated explicit actions.POST /api/tenantonboarding/completeretainsUpdateTenantPolicyRequestfor initial onboarding completion, andGET /api/tenantonboarding/settingsremains available for the management read model. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility route or window before v1.0. - Breaking federation-state authority removal for API version
0.1: allGET,POST,PUT, andDELETEoperations under/api/actorkeystoreand/api/syncstateare removed, together with their public DTOs, CQRS slices, HAL routes, OpenAPI operations, and generated client methods. Browsers can no longer read or submit encrypted private keys, public signing keys, key-purpose state, provider service names, ingestion cursors, sequence timestamps, or provider-owned update timestamps through generic CRUD. Key storage remains internal persistence, and ATProto ingestion cursor advancement is owned by the private fenced Jetstream consumer state workflow. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility route or window before v1.0. - Breaking moderation-reporting policy migration for API version
0.1: reporter communication consent, instance reporting-provider locks, and tenant reporting routing changed from flatPUTbodies to groupedPATCHat their existing routes and operation IDs. Consent requires one explicit group containing both communication purposes. General, Osprey, and Coop lock groups update independently. Routing separates policy, Osprey, and Coop groups; provider credentials are accepted only in explicit nested write-only groups and remain absent from routing-state reads. Omitted groups preserve stored settings and secrets, the general provider lock blocks every routing patch, and provider-specific locks block only the matching supplied provider group. HAL advertises PATCH and keeps the routing edit affordance available while the general lock is open. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility DTO, flat property, PUT route, or overload before v1.0. - Breaking integration-policy update migration for API version
0.1: tenant Listmonk settings, localization governance, and external API-key policy updates changed from broadPUTbodies to groupedPATCHat their existing routes. Listmonk separates connection from behavior and validates a supplied group against the current effective settings before writing only that group. Localization separates TMS configuration, language policy, and runtime switches. External API-key policy uses route-owned identity with optional metadata and access-policy groups; key material, owner/tenant binding, status, and usage state are excluded. Listmonk credential rotation and connection tests, localization secret-provider and bundle operations, and external-key issuance/revoke/delete remain dedicated workflows. OpenAPI and NSwag clients are regenerated without compatibility DTOs or PUT aliases before v1.0. - Breaking operational-resource update migration for API version
0.1: StorageObject metadata, WebhookEndpoint configuration, and webhook consumer provider-mode changes now use groupedPATCHat their existing routes. StorageObject bodies contain only optionalmetadata,access, andownershipgroups; route identity is authoritative, and provider location/key, provider, checksum, byte size, lifecycle, and tenant fields are no longer client writable. WebhookEndpoint bodies split destination, subscriptions, and delivery policy while requiring one governance group for configuration-version concurrency, pending-work disposition, reason, and uncertain-publication acknowledgement; omitted configuration groups preserve the locked aggregate state. Provider-mode transitions remain one atomic group with the same governance controls. Upload, quarantine/delete, signing-secret rotation, delivery, and endpoint credential operations remain dedicated actions. OpenAPI and NSwag clients are regenerated without compatibility DTOs or PUT aliases before v1.0. - Breaking template update migration for API version
0.1: EventTemplate and EventSessionTemplate updates changed from broadPUTbodies to groupedPATCHat/api/eventtemplate/{id}and/api/eventsessiontemplate/{id}. Route identity and persisted tenant/parent ownership are authoritative, and both operations require the current quoted GUID concurrency stamp inIf-Match. Metadata leaves update independently; explicit wrappers clear nullable description/event-type values; supplyingdefinitions.itemsatomically replaces definitions and nested options, while omitting the definitions group preserves them. Template detail reads now exposeconcurrencyStamp; sync diff/apply/history operations remain separate. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility route, flat DTO, body ID, or overload before v1.0. - Breaking custom-property definition update migration for API version
0.1: shared, Event, and EventSession definition updates changed from flatPUTbodies to groupedPATCHat/api/custompropertydefinition/{id},/api/eventcustomproperty/{id}, and/api/eventsessioncustomproperty/{id}. Route IDs and persisted tenant/parent context own identity; each operation requires a strong quoted GUIDIf-Match. Metadata and validation groups preserve omitted leaves, the shared relation group can change entity type, and supplying the options group atomically replaces the option set while omission preserves it. Event and EventSession projection refresh remains transactionally coupled to persistence. Exact custom-property value and multi-value replacement routes remainPUT. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility route, flat DTO, or overload before v1.0. - Breaking event-participation migration for API version
0.1:EventDto,EventListDto, event create/import bodies, and generated clients no longer exposeisRegistrationRequiredorexternalRegistrationUrl. They now use typedparticipationConfigurationlookup facts whose mode and obligation are required. Organizers configure the isolated participation resource through authenticatedPATCH /api/events/{eventId}/participation(ConfigureEventParticipation) with one required strong quoted GUIDIf-Match; event HAL exposesconfigure-participationonly through organizer/assignmentmanage-registrationsauthority. Public clients render participation actions only fromstart-registration,sign-in-to-register, orexternal-registrationon published public events; external destinations remain behind the stored-action redirect and never accept a caller URL. Information-only and walk-in modes emit no CTA, external-managed emits at most one reviewed external CTA, and platform-managed emits the native/sign-in CTA. Create Event collects this configuration explicitly instead of deriving it from registration policy. Regenerate fromschemas/openapi_islamu-event.json; there is no compatibility property, route, header shape, or window before v1.0. - Breaking grouped program/aspect/relationship update migration for API version
0.1: EventLocation disclosure changed fromPUTtoPATCH; EventSessionAgendaItem and EventSessionGroup changed from flatPUTbodies to route-ID groupedPATCH; EventSessionSpeaker update moved fromPATCH /api/eventsessionspeaker/management/by-session/{eventSessionId}/{id}toPATCH /api/eventsessionspeaker/management/{id}and now takes only the authoritative assignment ID from the route. Session-group and speaker updates require strongIf-Match; group list/detail responses exposeconcurrencyStamp. Islamic aspects now usePOST /api/event/{id}/aspects/islamic(CreateEventIslamicAspect) and groupedPATCH(UpdateEventIslamicAspect); Tech aspects use the matchingPOST(CreateEventTechAspect) and groupedPATCH(UpdateEventTechAspect) operations at/api/event/{id}/aspects/tech. TagTypeTags and CategoryTypeCategories remain Application-only grouped relationship commands. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility route, flat DTO, or overload before v1.0. - Additive event-list provenance contract:
EventListDtonow exposes nullableprovenanceTypeCode, allowing list and card clients to disclose immutable community-reported provenance without parsing extension data. The checked-in OpenAPI and NSwag client are regenerated from the source DTO; no handwritten compatibility property is retained. - Breaking notification-preference update migration for API version
0.1: current-user, Organization, and Group matrix saves changed fromPUTtoPATCHat their existing paths and operation IDs. Their requestcellscollection is now presence-aware: omitted cells preserve persisted choices, while absent/empty updates and any required or broader-locked cell reject the whole request before writes. Actor-subscription notification-level PATCH bodies no longer accepttargetActorId; route identity is authoritative, and the nullablenotificationLevelgroup plusexpectedConcurrencyStampform the update contract. Missing subscriptions return typed404ProblemDetails. The three matrix mute endpoints remain focusedPUTactions, and HALsavenow advertises PATCH. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility DTO, property, route, or overload before v1.0.
- Breaking grouped entity update migration for API version
0.1: tenant-plan draft, footer link-group, footer link, Tenant metadata, tenant navigation-link, and Tag updates changed fromPUTto route-IDPATCHat their existing paths and operation IDs. Request bodies now contain nullable logical groups and no body-owned resource, tenant, or user IDs; omitted groups preserve stored values. Tenant activation/suspension/archive/reactivation/purge, navigation reorder, and footer reorder remain dedicated actions. Footer link reads now includeisActiveso editing an inactive link preserves its state. Tenant/footer external-link writes accept relative paths and HTTPS URLs by default; instance settingsecurity.require_https_external_urls=falseadditionally permits HTTP only for an explicitly trusted HTTP-only private network. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility route, DTO, or method before v1.0. - Breaking Event provenance migration for API version
0.1:EventDto/EventListDtono longer expose the legacyisUserReportedor externaleventUrlfields. They expose typed provenance and reviewed active public actions instead. New anonymous public-action list/detail and stored-ID redirect routes live under/api/events/{eventId}/public-actions; authenticated action management and organizer-claim workflows live under/api/events/{eventId}/public-actionsand/api/events/{eventId}/organizer-claims, with claimant reads at/api/actors/{claimantActorId}/organizer-claims. Public-action responses add fixedopenInNewTab: trueandrel: "noopener noreferrer"guidance. Public action, claim, correction, and unsafe-link HAL affordances require a published public event; claim lifecycle checks use the dedicated organizer-claim resource and state-gate withdrawal/review candidates. Withdrawal now useswithdraw-organizer-claimand authorizes only a human controller of the persisted claimant actor; claimant ownership is server-derived and remains absent from public JSON/OpenAPI. The redirect operation accepts only stored event/action ids and never accepts a caller URL. Clients must regenerate fromschemas/openapi_islamu-event.json, follow returned HAL relations, and must not infer organizer capability from provenance, contributor identity, claim state, or local claims. There is no compatibility alias or window before v1.0. - Breaking identity-authority removal for API version
0.1: allGET,POST,PUT, andDELETEoperations under/api/userexternalloginand/api/indexeddidare removed, along with their HAL resources and generated client methods. Browsers can no longer submit aUserId, tenant, provider key, DID, handle, PDS host, or signing-key metadata to claim identity ownership. InternalUserExternalLoginandIndexedDidpersistence remains available only to verified authentication and federation flows until the global actor migration replaces the DID cache model. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility route or window before v1.0. - The exact designated provider routes
GETandPATCH /api/instance/settings/auth-providerplusGETandPATCH /api/instance/settings/authz-providernow genuinely accept either active setup-secret authority or authenticated instance-admin authority. Unrelated instance-settings and provider-management routes remain excluded from setup-secret authority. All four operations declare typed429 Too Many RequestsOpenAPI responses; PATCH operations also declarex-rate-limit-policy: Write, and the regenerated client handles typed 429 responses. Setup-secret traffic across anonymous setup endpoints and these exact provider GET/PATCH routes consumes one shared IP-keyed 5-per-60-second window; authenticated profile and completion requests use the separate IP-keyed setup window documented in the 2026-07-30 correction. Bearer-authenticated GETs without a setup-secret header do not enter the setup bucket, while bearer PATCH requests without setup-secret authority remain on the separate per-userWritewindow. - Task 1.3 persistence hardening synchronizes default-tenant module capabilities only in
SingleTenantmode and only for supplied PATCH leaves, with cancellation propagated. Resolver cache reads return copies. In the six transaction-owned PATCH handlers, notifications for value changes, mixed value-and-lock changes, and lock-only changes are all deferred until successful commit; lock transitions report the correctSystemLockedorSystemDefaultsource.
- Privacy-erasure contract finalization:
DELETE /api/usernow returns202 Accepted,Location,Retry-After, and a one-time receipt after local commit. The receipt-status route isGET /api/privacy-erasure/status, documented in OpenAPI with the customPrivacyErasureReceiptapiKeyscheme on theAuthorizationheader (Authorization: ErasureReceipt <receipt>), markedprivate, no-store, and bounded tofenced,provider_pending, orcompletedplus aggregate provider-work counts/timestamps. Missing, invalid, wrong, and expired receipts all collapse to the same401challenge. The API boundary covers durable provider-work reconciliation and cache-convergence signaling, but specialized provider execution and later compaction/retention follow-ups remain outside the user-facing contract where they are not yet complete. - Wire-compatible generated-client correction: the EventLocation management review collection now emits
HalCollectionEmbeddedOfEventLocationManagementDto.itemsasHalResourceOfEventLocationManagementDto[]instead of an untyped array. The wire payload and route behavior are unchanged; regenerated NSwag clients useICollection<HalResourceOfEventLocationManagementDto>rather thanICollection<object>, so source consumers may need to recompile or update assignments.
-
Breaking instance-secret read hardening for API version
0.1:GET /api/instance/settings/smtpno longer exposesusernameorpasswordinInstanceSmtpSettingsDto; clients useusernameConfiguredandpasswordConfigured, then send replacement credentials only throughPatchInstanceSmtpSettingsDto.configuration.GET /api/instance/settings/ai-assistantreplaces readableapiKeywithapiKeyConfigured; model discovery may carry a transient key, while persisted replacement keys are accepted only throughPatchAiAssistantGovernanceSettingsDto.providerConfiguration. Instance governance reads never return persisted key material. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility window before v1.0. -
Breaking grouped settings write migration for API version
0.1:- Tenant storage changed from
PUT /api/tenant/settings/storage, operation IDUpdateTenantStorageSettings, and generated methodUpdateTenantStorageSettingsAsynctoPATCH /api/tenant/settings/storage, operation IDPatchTenantStorageSettings, and generated methodPatchTenantStorageSettingsAsync. The request now carries optionalpolicyands3groups with presence-aware leaves. The server validates the supplied shape and merged effective model, persists only supplied leaves, and preserves omitted leaves. - Tenant branding changed from
PUT /api/tenant/settings/documents/branding, operation IDReplaceTenantBrandingSettingsDocument, and generated methodReplaceTenantBrandingSettingsDocumentAsynctoPATCH /api/tenant/settings/documents/branding, operation IDPatchTenantBrandingSettingsDocument, and generated methodPatchTenantBrandingSettingsDocumentAsync. The request requiresexpectedConcurrencyStampand at least onedisplayNameorassetsgroup. Stale stamps and database concurrency races return409 Conflictwith/problems/concurrent_update. Requested instance-locked fields are rejected before mutation. Incompatible persisted branding JSON fails closed before the transaction, document mutation, or tenant cache invalidation. A successful response returns the refreshed HAL document, field capabilities, and concurrency stamp. - Tenant footer settings changed from
PUT /api/footer/settings, operation IDUpdateTenantFooterSettings, and generated methodUpdateTenantFooterSettingsAsynctoPATCH /api/footer/settings, operation IDPatchTenantFooterSettings, and generated methodPatchTenantFooterSettingsAsync. The request uses optionalgeneral,template,description,socialLinks, andcopyrightgroups. The authenticatedGET /api/footer/settingsoperation IDGetTenantFooterSettingsand generated methodGetTenantFooterSettingsAsyncreturn the scalar settings, typed social links, lock state, and HAL capabilities used by the admin client. - The three former
PUToperations and generated update/replace methods are removed, with no compatibility alias or window before v1.0. Regenerate fromschemas/openapi_islamu-event.json, send only the group leaves being changed, and drive editability from returned HAL relations. Exact-key setting routes that still usePUT, such as instance ATProto key updates, are unchanged and are not part of this grouped-resource migration.
- Tenant storage changed from
-
Breaking reporter-communication contract for API version
0.1:SubmitEventReportDto,MyEventReportDto,ModerationReportQueueItemDto,ModerationReportDetailDto, and their HAL wrappers replaceReporterContactConsentwith required booleansReportCaseUpdatesConsentandReportFollowUpContactConsent.POST /api/event-reportsnow requires clients to choose each purpose independently; reporter-owned and moderation reads return both values. Anonymous submissions force both values tofalse, and there is no legacy alias or compatibility window before v1.0. The additive authenticatedPUT /api/event-reports/my/{reportId}/communication-consentoperation accepts both required booleans, updates only a reporter-owned report, and returns its refreshed HAL resource. My Reports detail and collection items exposeupdate-communication-consentonly through the server authorization pipeline. Clients must regenerate fromschemas/openapi_islamu-event.jsonand gate withdrawal UI from that relation. -
Breaking organization/group membership projection normalization:
OrganizationListDto.currentUserRoleandGroupListDto.currentUserRoleare replaced by numericcurrentUserRoleIdlookup fields. The former enum property serialized as a string at runtime while generated HAL collection clients expected an integer, causing authenticated tenant shells to fail deserialization. Clients must regenerate and use the stableroles.idvalue throughcurrentUserRoleId; there is no compatibility window before v1.0. Contract evidence is regenerated through the API/OpenAPI/NSwag workflow and covered by organization/group projection plus browser E2E tests. -
Breaking ATProto settings client-contract correction for API version
0.1: thesettingsitems returned byGET /api/settings/instance/atproto-federationand the tenant setting-group route now referenceEffectiveSettingDtoin OpenAPI instead of an anonymous inline schema. Generated clients therefore exposeICollection<EffectiveSettingDto>and deserialize the runtime string-valuedsourceproperty asSettingSource, replacing the invalid anonymousint?projection that caused successful200 OKresponses to fail in Blazor. Clients must regenerate and replace uses of the former generatedSettingstype; the server JSON payload is unchanged, so there is no server-side data migration. Contract evidence is retained inschemas/openapi_islamu-event.json,EventApiClient.g.cs, and the OpenAPI plus generated-client deserialization regression tests.
Intentional breaking API changes must add an entry to this file in the same PR that changes schemas/openapi_islamu-event.json, docs/API_CONTRACT_INVENTORY.md, or Explore.Blazor.Client/Clients/EventApiClient.g.cs.
Each breaking-change entry must include:
- affected route, operation, schema, or generated client method;
- old behavior and new behavior;
- affected clients or operator workflows;
- migration guidance or compatibility window;
- release version or target milestone;
- link to retained OpenAPI contract evidence from
openapi-contract-guardwhen available.
Non-breaking additive changes may be summarized in the current-mainline section, but removals, renamed operations, authentication/authorization contract changes, response shape changes, problem type changes, pagination/cursor changes, and generated-client breaking changes need explicit evidence here.
Key behavior in current code:
-
Additive authenticated workspace-shell context:
GET /api/ui-shell/context/GetUiShellContextreturns one private, no-store DTO containing tenant-local workspace availability, managed organization/group actors, explicit Personal/Organization/Group/Tenant/Instance settings scopes, deployment mode, organization-centric pinned actor, and resolved navigation defaults. Studio is available only through an event-publishing actor or personal event-submission eligibility; instance administration alone never grants Studio or Tenant scope. Regenerated clients callGetUiShellContextAsync. -
Additive purpose-specific EventLocation routes: anonymous public disclosure is available at
GET /api/events/{eventId}/locations; authenticated attendee disclosure usesGET /api/events/{eventId}/locations/my-access; authenticated management reads and policy updates useGET /api/events/{eventId}/locations/{eventLocationId}/managementandPATCH /api/events/{eventId}/locations/{eventLocationId}/disclosure. Public responses always use the public disclosure purpose and have no shared output cache; private routes sendprivate, no-store. PhysicalLocationIdvalues are not emitted. ELP-420A owns generated OpenAPI/client adoption. -
Additive authenticated EmailDispatch operations: instance administrators can inspect HAL-wrapped global SMTP processor state and pause/resume admission or set/clear a bounded persisted global rate override under
/api/admin/email-dispatch/control; tenant/instance administrators can explicitly reconcile anUnknownrow asDeliveredorNotDeliveredthrough the row HALreconcileaction. Generic replay no longer acceptsUnknown, and every reconciliation atomically aligns outbox, attempt, receipt, and notification-delivery state. -
Additive ATProto instance-governance contract: authenticated instance administrators can read the HAL-wrapped capability and validation profile through
GET /api/settings/instance/atproto-federation, update either exact administrator key throughPUT /api/settings/instance/atproto-federation/{key}, and lock or unlock it throughPOST/DELETE /api/settings/instance/atproto-federation/{key}/lock. The allowlist rejects every non-ATProto and user-consent key before CQRS dispatch; accepted operations delegate to the existing hierarchical setting handlers, apply the write rate limit, and emit permission-bound update plus current lock-transition affordances from serverCanEdit,IsLockable, andIsLockedmetadata. -
Breaking AT Protocol federation boundary: every raw
/api/atprotorecordread and mutation route, its create/update DTOs, commands, serializers, HAL links, and generated client methods are removed. Anonymous discovery now uses the typed event projection atGET /api/event; a federated item may exposeGET /api/event/federated/{atprotoRecordId}/source, which rechecks governance and tenant visibility before redirecting to its normalized HTTPS source. Federation records may be mutated only by lifecycle-owned transactional outboxes or authoritative Jetstream ingress; no compatibility routes or aliases are retained before v1. Target milestone: API version0.1development mainline; contract evidence is the runtime OpenAPI/HAL boundary suite and regenerated NSwag client. -
Additive anonymous home-discovery contract:
GET /api/public-experience/home/GetHomeDiscoveryaccepts optional coarseareaIdandmode=area|online|allquery context and returns one boundedHomeDiscoveryDtocontaining manual hero, upcoming, explicit spotlight/curated, most-viewed, recently-added sections, per-section availability status, and active public discovery areas. The server validates tenant-owned area mappings, applies published/public event filters through existing CQRS reads, deterministically deduplicates by section priority, and isolates section failures. Upcoming is capped at 18 items for three responsive six-item columns; other standard sections are capped at 10 items. At most two explicit curated rails are returned, each section has a one-second timeout, and composite execution has a three-second timeout that returns already-composed safe sections. Maximum-count source-generated JSON is regression-tested at no more than 256 KiB uncompressed, 128 KiB under Brotli, and 132 KiB under gzip. The response exposes no internal location IDs, venue coordinates, addresses, user origin, or distance claims; reserved proximity fields remain null until the separately approved PostGIS phase. Output caching varies by tenant/host,areaId, andmode; generated clients callGetHomeDiscoveryAsync(...). -
Media-type API versioning (
Accept: application/json;v=0.1orapplication/hal+json;v=0.1). -
HAL responses with
Prefer: return=minimalsupport. -
Output caching policies (
LookupData,ListData,DetailData) and HybridCache usage. -
ETag middleware for successful JSON/HAL
GETresponses with304 Not Modifiedsupport. -
Security headers middleware (
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Permissions-Policy,Content-Security-Policy). -
Additive optional managed-mode upgrade assessment contract: authenticated Control Plane callers with the dedicated
event-management:readmachine scope can send bounded JSON bodies toPOST /api/management/upgrade/preflight/EvaluateManagementUpgradePreflightandPOST /api/management/upgrade/postflight/VerifyManagementUpgradePostflight. Preflight reports Event-owned deployment mode, current/target semantic versions, management API compatibility, registration and directional-credential freshness, bootstrap completion, aggregate readiness, and stable blocker codes without changing Event state. Postflight verifies the expected Event and management API versions against the same bounded evidence. Managed mode remains disabled by default, no private Control Plane dependency is introduced, and upgrade execution remains outside Event. Generated clients useEvaluateManagementUpgradePreflightAsync(ManagementUpgradePreflightRequest, ...)andVerifyManagementUpgradePostflightAsync(ManagementUpgradePostflightRequest, ...). -
Additive managed tenant preflight contract: registered Control Plane callers with the dedicated
event-management:readmachine scope can send the strict versioned provisioning request toPOST /api/management/tenants/preflight/EvaluateManagedTenantProvisioningPreflight. The side-effect-free200 OKassessment reports normalized request hash, Event and management API versions, managed/Event instance identity, persisted deployment mode, registration state, stable blocker codes, conditional MultiTenant capacity, resolved immutable plan quotas, accepted modules/domain/branding/settings intent, and assessed correlation. SingleTenant or unconfigured-capacity instances return typed blockers with no capacity snapshot; malformed or unknown request members return400. The assessment is explicitly provisional because scheduling rechecks mode, capacity, slug, domain, catalog, and setting policy under its mutation lock. The route uses the managed read credential, authenticated rate policy, and Control Plane timeout and never creates an operation, outbox/audit row, tenant, or provider call. Generated clients useEvaluateManagedTenantProvisioningPreflightAsync(ManagementTenantProvisioningRequest, ...). -
Additive managed tenant provisioning contract: a registered managed instance authenticates with the dedicated
X-Control-Plane-Keyscheme and can schedule Event-owned tenant creation throughPOST /api/management/tenants/provision/ScheduleManagedTenantProvisioning(202 Accepted), read safe operation state throughGET /api/management/tenant-provisioning/{operationId}/GetManagedTenantProvisioningOperation, and cancel a still-pending operation throughPOST /api/management/tenant-provisioning/{operationId}/cancel/CancelManagedTenantProvisioningOperation. Scheduling is available only for persistedMultiTenantinstances with configured capacity, is idempotent by managed-instance plus external request id and normalized input hash, and writes the bounded request snapshot plus an internal outbox pointer atomically under the deployment-mode lock. Execution rechecks persisted mode under the same lock before tenant mutation; terminal outbox failure reconciles the operation to a redacted failed state. Event does not call Coolify or any private Control Plane infrastructure provider. Managed mode remains disabled by default; read/write policies and write rate limits are published in OpenAPI through the dedicated api-key security requirement. -
Tiered rate limiting and request-timeout middleware.
-
Correlation ID propagation (
X-Correlation-ID/X-Request-ID) and structured request logging. -
Additive browser Web Push endpoints under
/api/notification/web-pushexpose public VAPID configuration and authenticated current-device subscribe/status/unsubscribe flows.GET /vapid-public-keyadditionally exposes only the public key as anonymoustext/plainfor the generated Blazor API client. Subscription status omits endpoint and encryption-key material; enrollment and opt-out affordances are HAL-gated, and the VAPID private key remains server-only. -
Additive Listmonk integration settings endpoints under
/api/integrations/listmonkexpose sanitized settings reads, non-secret settings updates, write-only credential rotation, and server-side test connection.registration.createdoutgoing webhook payloads now use schema version 2 with requiredconsentToEmailShare; attendee PII fields are optional and present only whenShareEmailWithOrganizer == true, so webhook consumers must tolerate missing PII when consent is false. -
Additive public event URL endpoint:
GET /api/event/public/{slugCode}/GetEventByPublicCoderesolves clean public URL tokens such as{slug}-{publicCode}while existing GUID-based API routes remain available for API and management flows.EventDtoandEventListDtonow include server-ownedpublicCode; create/update requests still do not acceptpublicCode, and clients should build public event links fromslug+publicCodeinstead of raw event GUIDs. -
Additive public event Open Graph image endpoint: anonymous
GET /api/event/public/{slugCode}/og-imagereturns the authoritative 1200x630image/pngfor eligible published/public events, with a strong quoted ETag,Cache-Control: public, max-age=0, must-revalidate,Vary: Host, X-Tenant-Slug, and304 Not Modifiedsupport; missing or ineligible events use the generic event-not-found ProblemDetails contract and the route is intentionally not output-cached. Process-wide concurrency protection permits 2 renders with no queue by default, so excess requests return429 Too Many Requests. -
ProblemDetails behavior hardening: authorization middleware challenge/forbid results now return shared RFC 7807 bodies with
code=authentication_requiredorcode=forbidden, and rate-limit429 Too Many Requestsresponses includecode=rate_limitedplus retry/rate-limit headers when available. This aligns runtime behavior with existing typedProblemDetailsmetadata; no route/schema/client regeneration is required for this envelope-only hardening. -
Runtime authorization provider routing uses explicit deployment intent before stored settings. Explicit Cerbos stays fail-closed and never silently falls back to Local; tenant BYO PDP failures now also activate safe mode even when legacy
failure_mode=openis configured. -
Additive authorization onboarding fields on
AuthorizationProviderConfigurationDto:authorizationProviderManagedByDeployment,authorizationProviderBootstrapStatus,cerbosPoliciesSynchronized, andauthorizationProviderBootstrapMessage.AUTHORIZATION_PROVIDERmaps to the validated blank/local/cerbosselector. Explicit Local is immediately ready without Cerbos calls; explicit Cerbos verifies the instance PDP and publishes the bundled package to the instance Admin API with bounded background retries before reporting ready. Blank intent preserves manual Local-first onboarding. Existing clients may ignore the additive fields, but should use them to skip the chooser or render safe remediation rather than infer intent from endpoints or credentials. -
Additive multi-tenant control-plane overview endpoint:
GET /api/admin/control-plane/overview/GetControlPlaneOverviewreturns a HAL-wrapped instance-operator overview for multi-tenant deployments. The route is authenticated, multi-tenant-only, rate-limited with the authenticated policy, request-timeout bounded, and MediatR-authorized asResourceKinds.InstanceSetting/AuthorizationActions.InstanceSettings.View. The response exposes safe deployment/operator metadata only: version, deployment mode, public/admin host hints, tenant totals/status counts, provider summaries, warnings with remediation text, and_linksfor related instance-setting surfaces such as domains, plans, storage, authentication, and authorization. Theplansrelation reuses the tenant-plan list query's instance-setting permission metadata so clients can gate catalog navigation from HAL. It does not return secrets, tokens, provider credentials, tenant private content, or destructive actions. -
Additive multi-tenant control-plane domain/DNS endpoint:
GET /api/admin/control-plane/domains/GetControlPlaneDomainsreturns a HAL-wrapped operator read model for public platform host, wildcard tenant host, dedicated admin host, custom-domain enablement, expected DNS records, and configuration warnings with remediation guidance. The route is authenticated, multi-tenant-only, rate-limited with the authenticated policy, request-timeout bounded, and MediatR-authorized asResourceKinds.InstanceSetting/AuthorizationActions.InstanceSettings.View. The response derives guidance from existing instance domain settings and control-plane/public-origin configuration; it does not perform external DNS lookups or expose provider secrets. -
Breaking control-plane tenant-plan HAL correction:
ControlPlaneTenantPlanDetailDto.versions[]now carries each version's own optional_links; root plan resources retain only plan-scoped relations. Draft versions may advertiseupdate-version-draftandpublish, published versions may advertisearchiveandclone, and archived versions advertise no lifecycle mutation. Clients must read the selected version's links rather than root relations.ControlPlaneTenantEffectiveConfigurationDtoalso addsrollbackAssignment;rollbackis emitted only when the repository resolves a same-tenant, most-recent superseded assignment whose version remains published, and its URL targets that previous assignment ID. Publish-with-assignment-migration, switch, and rollback transitions now run inside oneIUnitOfWorktransaction. Regenerated clients exposeControlPlaneTenantPlanVersionDto._linksandHalResourceOfControlPlaneTenantEffectiveConfigurationDto.RollbackAssignment. Target milestone: API version0.1development mainline; no compatibility window for the incorrect pre-v1 root-link shape. -
Additive multi-tenant control-plane operations endpoint:
GET /api/admin/control-plane/operations/GetControlPlaneOperationsreturns a HAL-wrapped, bounded operator read model for general outbox, email dispatch, and storage status. The route is authenticated, multi-tenant-only, rate-limited with the authenticated policy, request-timeout bounded, and MediatR-authorized asResourceKinds.InstanceSetting/AuthorizationActions.InstanceSettings.View. The response uses existing repository/service status counts, caps general outbox samples at 100, emits warnings with operator remediation for capped due backlog, failed/dead-lettered outbox rows, email dead letters, stale email processing, due email backlog, missing SMTP configuration, and unavailable storage configuration, and links back to overview and storage settings through HAL permission metadata. It does not expose tenant payloads, recipient details, provider secrets, object keys, raw provider errors, or mutation actions. -
Additive multi-tenant control-plane tenant lifecycle endpoints:
GET /api/admin/control-plane/tenants,GET /api/admin/control-plane/tenants/{tenantId},POST /api/admin/control-plane/tenants, and the lifecycle POSTsactivate,suspend,archive,reactivate, andschedule-purgeexpose bounded tenant fleet metadata for instance operators. Reads are MediatR-authorized asResourceKinds.InstanceSetting/AuthorizationActions.InstanceSettings.View; lifecycle transitions are authorized as instance-setting updates, require reasons for suspend/archive/schedule-purge, require exact tenant-slug confirmation text for schedule-purge, writeTenantLifecycleLogaudit rows, and emit HAL affordances by status. All five lifecycle POSTs apply a status-qualified compare-and-swap update. If another writer changes the tenant after it is read, the losing request writes no lifecycle audit and returns typed RFC 7807409 Conflictwithtype=/problems/concurrent_updateandcode=concurrent_update; clients must reload the tenant resource and use its current HAL affordances before deciding whether to retry. Tenant creation reuses the existing tenant create command and advertises the matchingResourceKinds.Tenant/AuthorizationActions.CreateHAL permission.POST /api/admin/control-plane/tenants/{tenantId}/schedule-purge/ScheduleControlPlaneTenantPurgeis exposed only from archived tenant resources as aschedule-purgeHAL affordance; it records audited destructive intent by moving the tenant to the non-activePurgedlifecycle state and does not delete tenant data in the request path. Irreversible data deletion remains a later delayed/audited destructive-operation worker concern. -
Additive tenant effective-configuration HAL metadata: each registered, tenant-overridable, non-sensitive, non-system-locked
ControlPlaneTenantEffectiveSettingDtocan now include its own_linksdictionary for independently authorizedoverride,lock, andunlockactions. Root configuration resources no longer flatten repeated setting mutation relations, so clients must gate each setting row from that row's links. Tenant detail and collection items advertiseconfigurationonly when the effective-configuration read is authorized.Prefer: return=minimaland responses without authorized root links omit nested setting links. Direct setting mutation handlers independently enforce the same registry, tenant-scope, sensitivity, system-lock, and lock-transition constraints instead of trusting HAL, andoverridevalidates and serializes the registry-defined value type and allowed values before persistence. Runtime system and tenant setting writers serialize by authoritative setting key through a transaction-scoped PostgreSQL advisory lock; tenant value updates never writeIsLocked, lock/unlock use expected-state compare-and-swap, and tenant mutations recheck the system lock after acquiring the shared lock. Rejections return safeBaseCommandResponse.FailureCodevalues such assetting_not_found,setting_scope_not_supported,sensitive_setting_not_supported,setting_system_locked,setting_validation_failed,setting_override_not_found, andsetting_state_conflict. -
Additive event-session speaker management reads and creates are available under
/api/eventsessionspeaker/management/by-session/{eventSessionId}; updates use the authoritative assignment routePATCH /api/eventsessionspeaker/management/{id}. Authenticated management callers can read HAL speaker assignment rows withGetEventSessionSpeakersBySession, assign actors withCreateEventSessionSpeaker, update actor/session assignment groups withUpdateEventSessionSpeaker, and remove assignments withDeleteEventSessionSpeaker. The contract is parent-session resource protected, derives update identity from route-ownedid, requires strongIf-Matchon updates, emits EventSessionspeakersand assignment edit/delete HAL affordances only when authorized, and is represented by generated client methodsGetEventSessionSpeakersBySessionAsync,CreateEventSessionSpeakerAsync,UpdateEventSessionSpeakerAsync, andDeleteEventSessionSpeakerAsync. Public speaker display should use a separate minimized projection rather than these management assignment DTOs. -
Additive support-access API surface under
/api/support-access: authenticated actors can read current support-access state, start short-lived target-tenant sessions, stop their own current session, force-stop sessions when authorized, list tenant support-access history, and read bounded audit events. Responses are HAL resources/collections; clients must gate support-access affordances from_links. Runtime support context is accepted only from trusted BFF/server-forwarded support-access session ids and is revalidated against persisted session state, actor id, tenant id, expiry, mode, and governance settings on use. -
Breaking scheduler pause contract:
POST /api/admin/scheduler/pause(PauseScheduler) now requires aSchedulerPauseRequestDtobody whoseconfirmationTextequals the running scheduler's name. Pausing stops every background subsystem at once — email dispatch, retention sweeps, storage reconciliation — so it is guarded like other instance-wide destructive intent; a missing or mismatched value returns400withscheduler_confirmation_required.ResumeSchedulerand all per-job actions are unchanged and unguarded. The scheduler overview additionally exposeserroredJobCountandexecutingViewIsNodeLocal; the latter is true on a clustered instance, where currently-executing reads and the interrupt action are answered by the responding node only and must not be read as cluster-wide. Every control action now emits a structured audit record (principal, action, target, outcome, correlation id — refusals included) and anexplore.scheduler.admin_actionsmetric, and aschedulerreadiness check reports scheduler posture on/health. Regenerate clients fromschemas/openapi_islamu-event.json; there is no compatibility overload before v1.0. -
Additive scheduler recovery actions:
POST /api/admin/scheduler/jobs/{group}/{name}/reset-error(ResetSchedulerJobErrorState) clears every trigger of a job out of the scheduler's error state, andPOST /api/admin/scheduler/jobs/{group}/{name}/interrupt(InterruptSchedulerJob) requests cooperative cancellation of its executing instances. Both are authorized asInstanceSettings.Updateunder thescheduler.adminsetting key and honourScheduler:Quartz:AdminApiReadOnly. HAL emitsreset-erroronly for a job whose state iserrorandinterruptonly for a job that is executing, so every condition the job table reports now carries the action that clears it. Interruption is cooperative: the scheduler signals the running job's cancellation token, and a job that does not observe it keeps running. When an action no longer applies — nothing was executing, or no trigger was in error — the response is409 Conflictwith the new failure codescheduler_action_not_applicablerather than a success that changed nothing. Regenerate clients fromschemas/openapi_islamu-event.json. -
Additive scheduler administration API surface under
/api/admin/scheduler: instance administrators can read scheduler lifecycle state (GetSchedulerAdminOverview), list every scheduled job with owner, trigger states, schedule summary, and next/previous fire times (GetSchedulerAdminJobs), and control background work throughPauseScheduler,ResumeScheduler,PauseSchedulerJob,ResumeSchedulerJob, andTriggerSchedulerJob. Reads are MediatR-authorized asResourceKinds.InstanceSetting/AuthorizationActions.InstanceSettings.Viewand controls asInstanceSettings.Update, all under the setting keyscheduler.admin. The whole surface is opt-in viaScheduler:Quartz:AdminApiEnabledand answers typed404ProblemDetails when the host has not enabled it, so HAL-driven clients omit the section rather than render controls that fail.Scheduler:Quartz:AdminApiReadOnly(defaulttrue) withholds every control link and refuses mutating requests before the scheduler is touched; refusals return409 Conflictwithscheduler_read_onlyorscheduler_unavailable, and an unknown job returns404. Jobs are a HAL collection so each row carries its own affordances:triggeris offered for every job,pauseonly for a job with active triggers,resumeonly for a paused one, and an on-demand durable job with no trigger of its own advertisestriggeralone. The overview advertises only the meaningful lifecycle transition —pausewhile running,resumewhile in standby. Responses expose scheduling metadata only; trigger data maps, job payloads, recipients, and secrets are never read or projected. Regenerate clients fromschemas/openapi_islamu-event.json. -
Resolver bootstrap contract hardening:
GET /api/instance/settings/resolver-configis now an anonymous, tenant-resolution-exempt read of non-sensitive routing flags, path prefix, and public domain settings. The Blazor BFF uses the existing generatedGetInstanceResolverConfigurationAsyncclient operation before authentication or tenant resolution instead of loading API persistence services directly. Resolver updates remain authenticated instance-administrator operations. -
Storage upload client contract hardening:
PUT /api/storageobject/upload-sessions/{uploadSessionId}/contentnow declares a requiredapplication/octet-streamOpenAPI request body withstring/binaryschema. The generatedUploadStorageUploadSessionContentAsyncoperation accepts aStream, allowing the Blazor BFF to proxy reserved-session bytes exclusively throughIEventApiClient; the API continues to enforce the reserved MIME type, expected byte count, and content signature instead of trusting the neutral transport media type. -
Instance bootstrap probe typing: setup-secret validation, SMTP connection testing, and auth/authz provider configuration-status operations now return concrete
SetupSecretValidationResultDto,SmtpConnectionTestResultDto, andProviderConfigurationStatusDtosuccess schemas. Their generated client methods no longer returnobject, so callers consume explicitValid,Success/Message, andConfiguredproperties without JSON shape parsing. -
Breaking onboarding-status HAL contract:
GET /api/instanceonboarding/status/GetInstanceOnboardingStatusandGET /api/tenantonboarding/status/GetTenantOnboardingStatusnow return HAL resources instead of bareInstanceOnboardingStatusDtoandTenantOnboardingStatusDtobodies. The existing scalar status fields remain present and_linksnow carries the server-authoritativecompleteand management affordances; generated clients therefore returnHalResourceOfInstanceOnboardingStatusDtoandHalResourceOfTenantOnboardingStatusDto, while the Blazor onboarding services map those wrappers for existing scalar consumers without discarding link metadata. Clients must regenerate and gate onboarding actions from the returned relations rather than roles, claims, or configured booleans. A configured Keycloak provider remains manageable for realm creation, repair, and reconciliation: before launch through/onboarding/auth-provider, and after launch through/settings/instance?section=auth-providerswhen themanage-authenticationrelation is present. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 generated clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.csare regenerated through the documented API/NSwag workflow. -
Footer management endpoints (11 endpoints: link groups CRUD, links CRUD, settings, reorder, governance).
-
Breaking footer management write authorization hardening: footer link-group, link, reorder, and settings write endpoints now require the authenticated actor plus MediatR resource authorization as
ResourceKinds.Tenant/AuthorizationActions.Updatewith the resolved tenant id. Regular authenticated users that previously reached handlers with authentication alone now receive403 Forbidden; missing actor identity remains401 Unauthorized. Tenant admins are limited to their resolved tenant, and instance administrators retain administrative authority through the provider path. Route templates and DTO schemas are unchanged, so no generated-client regeneration is required for this behavior-only hardening. Contract evidence:FooterCommandAuthorizationMetadataTests,FallbackAuthorizationServiceTests, andFooterAuthorizationTests. -
Breaking EmailDispatch admin authorization hardening: status, tenant pause/resume, outbox park/replay, and
POST .../resolve-without-replayrequire MediatR resource authorization asResourceKinds.EmailDispatchwith actionsview,manage_tenant,park,replay, andresolve. The resolve operation accepts a bounded audit reason and changes onlyDeadLettered,Parked, orUnknownwork to terminalSkipped. HAL controls use the same split actions and permanently omit mutation affordances after content redaction. Regular users receive403; tenant admins remain tenant-bound; instance admins retain operator authority. -
Setup-secret onboarding contract hardening: setup-secret-gated
InstanceOnboardingendpoints now advertise the namedSetupSecretrate-limit policy and typed RFC 7807403,410, and429responses in OpenAPI. The setup-secret filter returnsProblemDetailsinstead of ad hoc JSON for invalid setup secrets and completed setup mode; generated clients now deserialize those failures asProblemDetails.POST /api/instanceonboarding/auth-provider-configuration/keycloak-bootstrapalso advertises typed502and503ProblemDetails for Keycloak Admin API upstream/provider failures, so clients can distinguish operator validation failures from provider invalid-response/unavailable outcomes without receiving secrets or raw provider payloads. The authority model is unchanged: the setup secret is bootstrap-only andcompletestill requires an authenticated principal plus the setup secret. Contract evidence:SetupSecretRateLimitMetadataTests,SetupSecretRequiredFilterTests,SetupSecretFlowTests,InstanceOnboardingControllerTests, and generated OpenAPI/inventory/client workflow. -
Actor appearance fields (BackgroundColor, BackgroundEffect, BannerColor, BannerPictureId, BackgroundImageId) on actor update endpoints.
-
OutboxProcessor background service for reliable event dispatching with retry and dead-letter.
-
Analytics relay rate limit policy (
AnalyticsRelay) forPOST /api/a/t. -
Event and event-session template sync endpoints for diff/apply/history workflows, including 409 ProblemDetails types
/problems/stale_sync_baseand/problems/concurrent_update. -
Event create/update/detail APIs split card summaries from long-form copy:
descriptionis capped at 150 characters for list/card UI, whilecontentcarries the long event body up to 5000 characters. -
Additive event reporting endpoints are available under
/api/event-reports.GET /api/event-reports/events/{eventId}/options/GetEventReportOptionsexposes public reportability metadata and reason options for reportable events,POST /api/event-reports/SubmitEventReportaccepts authenticated reports,GET /api/event-reports/my/GetMyEventReportsreturns the authenticated reporter's paged HAL status list,GET /api/event-reports/my/{reportId}/GetMyEventReportreturns one reporter-owned status resource, andPUT /api/event-reports/my/{reportId}/communication-consent/UpdateMyEventReportCommunicationConsentchanges both explicit communication purposes for the report owner. Reporter-facing DTOs expose only status, reason, event id, timestamps, subcategory, and consent metadata; evidence text, reporter hashes, moderator identity, case internals, provider links, decisions, signals, and internal notes are excluded. Clients should gate report submission from event HAL_links.report-event, gate consent controls from My Reports_links.update-communication-consent, and use generated client operations rather than constructing routes locally. -
Non-breaking generated-client schema correction:
HalResourceOfReportingRoutingStateDtois now cataloged in the HAL OpenAPI schema map, so OpenAPI and NSwag expose the reporting routing state fields plus_linksinstead of an empty wrapper type. Route behavior and authorization are unchanged; regenerated clients can read the routing-state booleans, modes, provider state, and HAL affordances from the existing endpoint. -
Breaking event-registration read hardening: generic registration reads are now authenticated self-service contracts.
GET /api/eventregistrationreturns the authenticated user's paged registrations,GET /api/eventregistration/by-session/{eventSessionId}returns only the authenticated user's registration for that session,GET /api/eventregistration/by-user/{userId}returns403 Forbiddenwhen the route user differs from the authenticated user, andGET /api/eventregistration/{id}fails closed for registrations owned by another user.EventRegistrationDtoandEventRegistrationListDtono longer expose serializeduserId,userFullName, oruserEmail; generated clients should use event/session/status fields and server-owned route context instead of reading registrant identity from these DTOs. MCP event-registration context no longer includes registrant names. Organizer attendee management must use a future resource-authorized organizer projection rather than these generic self-service routes. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 generated clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking user-authentication-token contract cleanup: generic
POST /api/userauthenticationtokenandPUT /api/userauthenticationtoken/{id}operations,CreateUserAuthenticationTokenDto,UpdateUserAuthenticationTokenDto, their commands/handlers/validators, serializer roots, and generated client methods are removed. AuthenticatedGETlist/detail operations expose only self-scopedid,provider,pdsHost, andexpiresAt; authenticatedDELETE /api/userauthenticationtoken/{id}remains idempotent self-service revocation. No compatibility route or DTO alias is retained before v1.0. The CarpaNet bootstrap/current-session/delete bridge under/api/auth/atproto/sessionis server-private, no-store, purpose-authenticated, and excluded from API discovery, OpenAPI, the NSwag browser client, and the WASM serializer context. BFF refresh and signout remain cookie-bound operations; signout clears the local session even when best-effort remote revocation is unavailable. Contract evidence:UserAuthenticationTokenControllerMetadataTests,UserAuthenticationTokenDtoPrivacyTests,AtprotoCredentialIsolationTests,schemas/openapi_islamu-event.json, and the regeneratedExplore.Blazor.Client/Clients/EventApiClient.g.cs. -
Breaking external API-key management hardening:
/api/externalapikeylist/detail/usage-report reads no longer use shared output-cache policies and now advertise no-store response metadata plus theAuthenticatedrate-limit policy. Create/update/delete actions advertise theWriterate-limit policy and typed401/403ProblemDetails metadata. Owner-authority failures during create and usage-report authorization now return403 Forbiddeninstead of validation400or an empty successful report, and deleting a missing or non-owned key now returns404 Not Foundinstead of false204 No Content. Platform-scoped key-name uniqueness now uses the explicit tenant-filter bypass path, and repository reads/exists checks propagate cancellation tokens through the changed flows. Generated clients should handle the hardened 401/403/404 outcomes and continue treating newly created key material as a one-time secret response. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 generated clients. Contract evidence:ExternalApiKeyControllerMetadataTests,ExternalApiKeyIntegrationTests,ExternalApiKeyObservabilityTests,schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cs. -
Breaking tenant role grant read authorization hardening:
GET /api/tenant-user-role-grantsandGET /api/tenant-user-role-grants/{id}are authenticated administrative contracts that require actionviewonislamuevent_tenant_user_role_grantthrough MediatR resource authorization. Regular authenticated users are denied; tenant admins are limited to the resolved tenant; instance admins retain administrative access.TenantUserRoleGrantDtoandTenantUserRoleGrantListDtoremain identity-bearing and should not be used for public tenant-member/profile display. Clients should handle401/403on these read routes and continue gating create/revoke affordances from HAL_links. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 generated clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking organization-member read authorization hardening:
GET /api/organizationmember/{organizationId}andGET /api/organizationmember/member/{id}are authenticated administrative contracts that require actionviewonislamuevent_organization_memberthrough MediatR resource authorization. Regular authenticated users are denied; tenant admins and organization admins are evaluated against tenant, organization, and member attributes in Cerbos/local fallback.OrganizationMemberDtoremains identity-bearing and now includes additivetenantIdcontract metadata so authorization scopes and generated clients can preserve tenant context. Clients should handle401/403on these read routes and continue gating member-management affordances from HAL_links. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 generated clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Additive moderation report management endpoints are available under
/api/events/{eventId}/moderation/reports. Authenticated management callers can read the queue/detail resources throughGetModerationReportQueueandGetModerationReportDetail, then useTriageModerationReport,AssignModerationReport,DecideModerationReport, andExecuteModerationReportDecisionPOST routes when the detail resource advertisestriage-report,assign-report,decide-report, orexecute-report-decision. These routes require event moderation authority, optimistic case concurrency stamps, and bounded request metadata; they do not expose reporter hashes, stable reporter user/actor IDs, evidence creator IDs, decision moderator IDs, raw provider case/signal IDs, provider URLs, provider correlation IDs, provider payloads, raw errors, or arbitrary moderator notes in HAL action discovery. -
Breaking moderation report projection hardening:
ModerationReportQueueItemDtono longer exposesreporterUserIdorreporterActorId;ModerationReportDetailDtono longer exposesreporterUserIdorreporterActorId; moderation evidence no longer exposescreatedByUserId; moderation decisions no longer exposemoderatorUserId; moderation signals no longer exposeexternalSignalIdorcorrelationId; and moderation external-link projections no longer exposeproviderCaseId,providerSignalId,providerUrl, orcorrelationId. Clients should use state, reason, priority, case status, sync state, counts, and HAL action rels instead of stable user/provider pointers. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 generated clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking event moderation contract split: the old generic
POST /api/event/{id}/moderateroute and HAL relationmoderateare replaced by explicit moderation contracts:POST /api/event/{id}/moderation/light/ModerateEventLight/ HALmoderate-light,POST /api/event/{id}/moderation/heavy/ModerateEventHeavy/ HALmoderate-heavy, andPOST /api/event/{id}/moderation/unmoderate/UnmoderateEvent/ HALunmoderate. These moderation POSTs consume anEventModerationRequestDtoJSON body with optional safereasonCodeandcorrelationId; clients should send{}or a selected structured reason code instead of relying on empty-body POSTs. Instance/tenant administrators can receive moderation actions without event edit/update/delete authority. Heavy redaction returns503 ProblemDetailswhen redaction is committed but physical storage-object deletion remains pending. PublicGET /api/event/{id}still hides moderated events, while authorized management reads use authenticatedGET /api/event/{id}/management-detail/GetEventManagementDetailsandGET /api/event/management/by-actor/{actorId}/GetManagedEventsByActor, both protected by the eventview-managementaction rather than the publicviewaction. Safe moderation audit metadata is exposed through authenticatedGET /api/event/{id}/moderation/history/GetEventModerationHistoryand HALmoderation-history; it excludes original event text, slugs, URLs, image identifiers, storage object keys/paths, raw provider errors, and arbitrary moderator free text. -
Additive moderation integration endpoint:
POST /api/integrations/moderation/osprey/callback/ModerationIntegrationOspreyCallbackaccepts asynchronous Osprey-compatible signal callbacks from machine clients authenticated withX-API-Key. The endpoint requires theModerationIntegration.OspreyCallbackpolicy, which only admits API-key principals whose scopes satisfy event moderation authority. Payloads identify tenant/report/event/case plus bounded signal metadata; the Application handler rechecks tenant/report/event ownership, records Osprey signals idempotently, updates external sync state, and may raise local review priority, but it does not execute moderation actions or store raw provider payloads. -
Additive Coop moderation integration execution:
POST /api/integrations/moderation/coop/callback/ModerationIntegrationCoopCallbackaccepts signed Coop-compatible decision callbacks from API-key machine clients. The endpoint requires theModerationIntegration.CoopCallbackpolicy plus a valid timestamped HMAC-SHA256 signature over the raw body before JSON parsing. Verified callbacks and one uniqueIncomingWebhookEffectOutboxpointer commit together; duplicate intake is acknowledged idempotently. A fenced background worker revalidates the retained callback, dispatches authoritative decision execution, and completes the pointer with an applied-effect receipt only after command success. Authenticated operators can inspect safe tenant-scoped lifecycle state atGET /api/admin/incoming-webhook-effects/statusand follow the HALredriveaction for an eligible dead-lettered pointer. Callback bytes, hashes, signed provider decision IDs, and raw failures are excluded from this operator surface. -
Additive incoming Svix operational endpoint:
POST /api/integrations/svix/operational/IntegrationSvixOperationalCallbackaccepts Svix-compatible signed operational callbacks through[AllowAnonymous]where the Svix signature is the authentication mechanism. The endpoint reads the raw body before parsing, verifiessvix-id,svix-timestamp, andsvix-signaturewith the configuredwebhooks.svix.operational_webhook_secret, acknowledges verified instance-level operational callbacks with202 Accepted, and captures tenant-addressed payloads inincoming_webhook_messageswhen atenantId/tenant_idis present. This route is independent of the outgoing webhook provider mode. -
Additive webhook event catalog endpoint:
GET /api/webhooks/event-types/GetWebhookEventTypesis an anonymous, output-cached lookup route returning the authoritative outgoing webhook event catalog. Each item includes the event type name, group, description, schema version, public/enabled flags, payload retention days, JSON Schema, example envelope payload, and data-field metadata. This provider-neutral catalog is the same source used by LocalProvider payload building and Svix event-type synchronization. -
Breaking webhook portal authority containment:
POST /api/webhooks/svix/app-portal/OpenSvixAppPortalaccepts onlyconsumerIdplus optionalexpiresInSeconds; caller-selected read-only mode, feature flags, provider application identity, tenant identity, binding identity, and capability flags are not accepted. Portal issuance and theopen-provider-portalHAL affordance now require the same active, tenant-scoped, verified persisted binding whose provider version and capability-policy version exactly match the configured Svix profile and whose provider/governance capability intersection includes App Portal. Local/Cerbos authorization remains independently required. Responses areno-store; successful and provider-failed attempts write secret-safe audit metadata before returning, and audit failure returns no portal URL.WebhookConsumerDtoexposes no portal permission boolean, so Blazor clients must gate the action solely from the HAL relation. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 request shapes or generated clients. -
Additive webhook provider endpoint:
POST /api/webhooks/svix/app-portal/OpenSvixAppPortalcreates a backend-generated, short-lived Svix App Portal access URL for one verified webhook consumer binding. The endpoint is authenticated, rate-limited with the write policy, resource-authorized asislamuevent_webhookactionwebhook:open-provider-portal, bypasses HTTP idempotency replay for the short-lived token response, and returns only{ url, token, expiresAt }; the Svix API token remains server-side through secret resolution. Provider-disabled, missing-consumer, binding/capability mismatch, and Svix gateway failures return bounded RFC 7807 ProblemDetails codes. -
Additive webhook endpoint management routes:
GET /api/webhooks/endpoints/GetWebhookEndpoints,GET /api/webhooks/endpoints/{endpointId}/GetWebhookEndpointById,POST /api/webhooks/endpoints/CreateWebhookEndpoint,PUT /api/webhooks/endpoints/{endpointId}/UpdateWebhookEndpoint,DELETE /api/webhooks/endpoints/{endpointId}/DeleteWebhookEndpoint,POST /api/webhooks/endpoints/{endpointId}/rotate-secret/RotateWebhookEndpointSecret, andPOST /api/webhooks/endpoints/{endpointId}/test/TestWebhookEndpointexpose tenant-scoped endpoint management flows for outgoing webhook consumers. Reads return HAL resources with status and subscription metadata but no endpoint secret references; create requires an active tenant-local consumer, absolute HTTP(S) URL, secret reference, and enabled event type subscriptions; update replaces URL/delivery controls/subscriptions without rotating secrets; delete archives the endpoint while retaining delivery history; rotate-secret accepts a new secret reference plus an optional previous-secret overlap window, rejects unchanged references, incrementssecretVersion, and never accepts or returns raw signing material; test creates an authoritativewebhook.testmessage and one scheduled LocalProvider attempt for active Local/Composite endpoints while directing Svix-managed endpoint testing to the Svix App Portal. Active endpoint details may advertise HALupdate,rotate-secret,test, anddeleteaffordances, and duplicate URLs, invalid event subscriptions, invalid rotation requests, or unsupported provider-managed endpoint tests return bounded RFC 7807 ProblemDetails. -
Managed-provider provisioning endpoint
POST /api/managed-provider-provisioning/clients:ensurefor instance-admin/provider automation. It creates or rehydrates provider-customer tenants usingExternalBinding, creates tenant-localTenantUser/TenantUserProfilestate, creates a tenant admin role grant, and keeps ERP customer/admin identities out of instance-admin authority. -
Breaking tenant authority contract replacement: the former tenant-member public API surface is replaced by
/api/tenant-user-role-grants,TenantUserRoleGrantDto,TenantUserRoleGrantListDto,CreateTenantUserRoleGrantDto, create/revoke route names, and Cerbos resource kindislamuevent_tenant_user_role_grant. Role-grant updates are create/revoke flows; clients should gate create/revoke affordances from HAL_links. -
Idempotency keys are now bound to the original request identity. A repeated key replays only the same method/target/content/body/principal fingerprint; same-key reuse for a different write request returns
409 Conflict. -
Breaking storage download contract change: public arbitrary-key storage routes
GET /api/storageobject/file/{fileKey}andGET /api/storageobject/presigned-url-by-key/{objectKey}are removed. Clients must resolve content through metadata-backed IDs such asGET /api/storageobject/{id}/content, public images throughGET /api/storageobject/{id}/public, or S3-compatible presigned reads through the ID-based compatibility endpoint. -
Breaking storage authorization hardening: storage object metadata/list/detail, general content streaming, and ID-based presigned download routes now require authentication plus storage-object resource actions. Metadata uses
view, content usesdownload, presigned URL generation usespresigned_download, andGET /api/storageobject/{id}/publicremains the only anonymous storage content route for active public-image objects. The presigned URL endpoint is no-store and is no longer output-cacheable. -
Blazor BFF browser uploads now use provider-neutral API upload sessions.
/bff/storage/upload-sessionno longer returns or stores provider upload URLs/object keys;/bff/storage/upload-proxyrejects raw destinations and streams bytes to the API session content endpoint. -
Instance storage administration is now provider-neutral.
GET/PUT /api/instance/settings/storageexposes provider policy, max upload ceilings, default tenant quota, storage delegation lock, usage, provider health, and redacted optional S3 configuration.POST /api/instance/settings/storage/testtests the selected provider, andPOST /api/instance/settings/storage/usage/recalculatereconciles usage counters from storage metadata. -
Tenant storage administration is exposed through
GET/PATCH /api/tenant/settings/storage. The read returns effective tenant policy, usage, delegation lock/read-only state, and redacted optional S3 override fields. The grouped PATCH preserves omittedpolicyands3leaves, requires tenant-admin or instance-admin authority, and is rejected while instance policy locks tenant delegation or when the merged provider, quota, route, max-upload, or S3 configuration fails validation. -
/healthnow includes astoragereadiness check for the selected storage provider. Local-first deployments stay healthy without S3; S3-compatible storage is probed only when selected, and failure payloads use bounded provider/status/failure-code fields without exposing paths, endpoints, bucket names, object keys, access keys, or secrets. -
/healthalso includesstorage-reconciliationfor the API hosted drift worker. This is an operational health signal only, not a new public OpenAPI operation. The worker defaults to dry-run reporting and requires explicit mutation flags for quarantine or delete actions. -
Actor subscription endpoints added under
/api/actor-subscriptionsfor authenticated current-user subscription state, subscribe, notification-level update, and unsubscribe flows. HAL affordances now expose actor and event organizer subscription actions for organization/group actors only; clients must continue gating UI actions from_linksrather than local role or claim checks. -
Event-published organization/group actor subscriptions now produce durable in-app notifications through an internal outbox fanout path. This is not an SMTP or push delivery contract.
-
Authenticated notification refresh hints are available through
GET /api/notification/streamastext/event-stream. The SSE payload is minimal unread-count state only; notification rows and existing notification APIs remain the delivery source of truth. -
Authenticated AI assistant API foundation is available under
/api/ai/assistant. It exposes bootstrap, private conversation list/detail/create, send-message withIdempotency-Key, run-status polling, queued/in-progress run cancellation, proposed-action confirm/reject routes, andGET /api/ai/assistant/referencesfor bounded event, actor, and organization reference search. Conversation, run, proposed-action, and reference affordances are HAL-driven (self,collection, active-statesend-message, collectioncreate, cancellable-statecancel-run, proposed-stateconfirm-action/reject-action, and referenceevent/actor/organizationlinks); send-message accepts selected lightweight references for prompt context and returns the run-status polling route, confirm propagatesIdempotency-Key, provider/proposed-action/run failures map to safe ProblemDetails, reference results omit full resource content, and tool calls mutate state only after explicit confirmation. Streaming is intentionally deferred; the non-streaming polling path remains the supported fallback contract. -
Breaking error-shape cleanup:
EventAgendaItemController,EventSessionAgendaItemController, andLocationRoomControllercreate/update 400 responses now return RFC 7807ValidationProblemDetails(application/problem+json) withcode,traceId,timestamp, and optionalcorrelationIdinstead of rawBaseCommandResponse<Guid>envelopes or anonymous{ error }payloads. Clients should read validation details fromerrorsand machine-readable status fromcode. -
Breaking error-shape cleanup:
ActorKeyStoreControllerandUserAuthenticationTokenControllercreate/update 400 responses now return RFC 7807ValidationProblemDetails(application/problem+json) withcode,traceId,timestamp, and optionalcorrelationIdinstead of raw command envelopes, anonymous{ error }, or ad hocProblem(...)payloads.ActorKeyStoreControllerdelete not-found now returns RFC 7807ProblemDetailswith aresource_not_foundfallback code. Clients should read validation details fromerrorsand machine-readable status fromcode. -
Breaking appearance update contracts:
PATCH /api/user/appearancenow accepts only an optionallocalizationgroup containing nullabledirectionandlanguageleaves; omitted leaves remain unchanged.PATCH /api/user/appearance/profiles/{profileId}splits editable profile metadata from light/dark palette snapshots, andPATCH /api/admin/ui-themes/{id}uses route-owned identity plus requiredrowVersionand nullablemetadata,state, andpalettesgroups. Active-profile selection, current theme mode replacement, and profile archive remain focusedPUTactions. Old broad PUT bodies and body-owned UI-theme IDs are removed from OpenAPI and generated clients without a compatibility window. -
Breaking User profile update contract: the old authenticated
PUT /api/userpartial-update route with bodyidis replaced byPATCH /api/user/{id}/ route nameUpdateCurrentUser. The route ID is authoritative andUpdateUserDtono longer includesid; request bodies contain only nullable update groups such asnamesandprofileImage. PATCH now requires anIf-Matchheader containing the currentUserDto.concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateCurrentUserAsync(Guid id, UpdateUserDto body, string? if_Match = null, ...), and Blazor callers pass the quoted stamp from the last loaded user detail. Existing users receive a non-emptyusers.concurrency_stampthrough migrationAddUserConcurrencyStamp. OldPUT /api/userclients receive405 Method Not Allowed, and old JSON bodies that still includeidare rejected by the API's unmapped-member validation. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking Actor update contract: the old authenticated
PUT /api/actor/{id}request wrapper{ actorDto, appearanceDto }is replaced byPATCH /api/actor/{id}/ route nameUpdateActor. The route ID is authoritative andUpdateActorDtono longer includesidortenantId; request bodies contain nullable groups such asprofile,profileImage,appearance,federationIdentifiers, andfederationMetadata. Clearable nullable fields useOptionalUpdate<T>field operations so clients can distinguish omitted fields from explicit clears. PATCH requiresIf-Matchwith the currentActorDto.concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateActorAsync(Guid id, UpdateActorDto body, string? if_Match = null, ...). Existing actors receive a non-emptyactors.concurrency_stampthrough migrationAddActorConcurrencyStamp. OldPUT /api/actor/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking Category update contract: the old authenticated
PUT /api/category/{id}broad request body withid,masterCode,fullName, andparentIdis replaced byPATCH /api/category/{id}/ route nameUpdateCategory. The route ID is authoritative andUpdateCategoryDtono longer includesid; request bodies contain nullable property groupsmasterCode,fullName, andparent.parent.parentIdusesOptionalUpdate<Guid?>, so clients can explicitly clear a parent with{ "hasValue": true, "value": null }. PATCH requiresIf-Matchwith the currentCategoryDtoorCategoryListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateCategoryAsync(Guid id, UpdateCategoryDto body, string? if_Match = null, ...). Existing categories receive a non-emptycategories.concurrency_stampthrough migrationAddCategoryConcurrencyStamp. OldPUT /api/category/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking Location update contract: the old authenticated
PUT /api/location/{id}broad request body withid, address fields, coordinates, and timezone is replaced byPATCH /api/location/{id}/ route nameUpdateLocation. The route ID is authoritative andUpdateLocationDtono longer includesid; request bodies contain nullable property groupsfullName,address,postcode,country,city,latitude,longitude, andtimezone. Clearablelatitude,longitude, andtimezonevalues useOptionalUpdate<T>, so clients can explicitly clear a value with{ "hasValue": true, "value": null }. PATCH requiresIf-Matchwith the currentLocationDtoorLocationListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateLocationAsync(Guid id, UpdateLocationDto body, string? if_Match = null, ...). Existing locations receive a non-emptylocations.concurrency_stampand audit timestamp/user columns through migrationAddLocationAuditAndConcurrencyStamp. OldPUT /api/location/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking LocationRoom update contract: the old authenticated
PUT /api/locationroom/{id}broad request body withid,locationId, room name, slug, description, capacity, and sort order is replaced byPATCH /api/locationroom/{id}/ route nameUpdateLocationRoom. The route ID is authoritative andUpdateLocationRoomDtono longer includesid; request bodies contain nullable property groupslocation,name,slug,description,capacity, andsortOrder. Clearableslug,description, andcapacityvalues useOptionalUpdate<T>, and parent location changes are rejected when the target location is not in the same tenant as the room. PATCH requiresIf-Matchwith the currentLocationRoomDtoorLocationRoomListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateLocationRoomAsync(Guid id, UpdateLocationRoomDto body, string? if_Match = null, ...). OldPUT /api/locationroom/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking Organization profile update contract: the old authenticated
PUT /api/organization/{id}broad request body with full profile/contact/address fields is replaced byPATCH /api/organization/{id}/ route nameUpdateOrganization. The route ID is authoritative andUpdateOrganizationDtocontains nullable property groupsfullName,websiteUrl,email,country,city,postcode, andaddress. ClearablewebsiteUrl.valueusesOptionalUpdate<string?>, so clients can explicitly clear the URL with{ "hasValue": true, "value": null }. PATCH requiresIf-Matchwith the currentOrganizationDtoorOrganizationListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateOrganizationAsync(Guid id, UpdateOrganizationDto body, string? if_Match = null, ...). Approval status is not part of the profile update DTO and remains a separate admin lifecycle action atPUT /api/organization/{id}/approval-status. OldPUT /api/organization/{id}profile clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking Group profile/hierarchy update contract: the old authenticated
PUT /api/group/{id}broad request body withfullName,description,parentOrganizationId, andparentGroupIdis replaced byPATCH /api/group/{id}/ route nameUpdateGroup. The route ID is authoritative andUpdateGroupDtocontains nullable property groupsfullName,description,parentOrganization, andparentGroup. Clearabledescription.value,parentOrganization.value, andparentGroup.valueuseOptionalUpdate<T>, so clients can explicitly clear text or parent relationships with{ "hasValue": true, "value": null }. PATCH requiresIf-Matchwith the currentGroupDtoorGroupListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateGroupAsync(Guid id, UpdateGroupDto body, string? if_Match = null, ...). Approval status is not part of the profile/hierarchy update DTO and remains a separate lifecycle action atPUT /api/group/{id}/approval-status. OldPUT /api/group/{id}profile clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking Event update contract: the old authenticated
PUT /api/event/{id}draft update body is replaced byPATCH /api/event/{id}/ route nameUpdateEvent. The route ID is authoritative andUpdateEventDtonow contains nullable property groups for title, subtitle, description, content, slug, event type, audience gender/age, price, currency, featured image, registration requirement, external registration URL, visibility, format, madhab, timezone, event time zone, event URL, background color/effect/image, source template, series membership/order, and registration policy. Clearable nullable values useOptionalUpdate<T>, so clients can distinguish omitted fields from explicit clears. PATCH requiresIf-Matchwith the current eventconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateEventAsync(Guid id, UpdateEventDto body, string? if_Match = null, ...). Event publish/archive/cancel, moderation, template sync, role/member/ownership, and session lifecycle flows remain separate domain commands. OldPUT /api/event/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking EventSession update contract: the old authenticated
PUT /api/eventsession/{id}broad body is replaced byPATCH /api/eventsession/{id}/ route nameUpdateEventSession. The route ID is authoritative andUpdateEventSessionDtonow contains nullable property groups for parent event, schedule, location, featured image, room, sort order, title, kind, description, slug, max audience attendees, registration mode, price, currency code, and Islamic scheduling aspect. Clearable nullable values useOptionalUpdate<T>, including explicit schedule clears and Islamic-aspect clears. PATCH requiresIf-Matchwith the current sessionconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateEventSessionAsync(Guid id, UpdateEventSessionDto body, string? if_Match = null, ...). Session create-draft, schedule, publish, archive, cancel, and complete remain separate lifecycle commands. OldPUT /api/eventsession/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking EventAgendaItem update contract: the old authenticated
PUT /api/eventagendaitem/{id}broad body with route/body identifiers and flat event, title, description, schedule, location, room, kind, and sort-order fields is replaced byPATCH /api/eventagendaitem/{id}/ route nameUpdateEventAgendaItem. The route ID is authoritative andUpdateEventAgendaItemDtonow contains nullable property groupsevent,title,description,schedule,location,room,kind, andsortOrder. Clearabledescription,location,room, andkindvalues useOptionalUpdate<T>, so clients can explicitly clear a value without relying on ambiguous JSON nulls. PATCH requiresIf-Matchwith the currentEventAgendaItemDtoorEventAgendaItemListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateEventAgendaItemAsync(Guid id, UpdateEventAgendaItemDto body, string? if_Match = null, ...). The handler validates parent-event, location, and room tenancy, derives location from room where appropriate, saves once, and invalidates affected parent event detail/list caches. OldPUT /api/eventagendaitem/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking EventDay update contract: the old authenticated
PUT /api/eventday/{id}broad body withid,eventId, local date, label, description, banner text/image, publication state, sort order, and day-scope registration fields is replaced byPATCH /api/eventday/{id}/ route nameUpdateEventDay. The route ID is authoritative andUpdateEventDayDtonow contains nullable property groupsevent,localDate,label,description,bannerText,bannerImage,publication,sortOrder, andregistration. Clearablelabel,description,bannerText, andbannerImagevalues useOptionalUpdate<T>, so clients can explicitly clear values without ambiguous JSON nulls. PATCH requiresIf-Matchwith the currentEventDayDtoorEventDayListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateEventDayAsync(Guid id, UpdateEventDayDto body, string? if_Match = null, ...). The handler validates parent-event tenancy, enforces local-date uniqueness inside the effective parent event, saves once, and invalidates affected parent event detail/list caches. OldPUT /api/eventday/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking EventSeries update contract: the old authenticated
PUT /api/eventseries/{id}broad body with bodyid, title, description, slug, featured image, and publication fields is replaced byPATCH /api/eventseries/{id}/ route nameUpdateEventSeries. The route ID is authoritative andUpdateEventSeriesDtono longer includesid; request bodies contain nullable property groupstitle,description,slug,featuredImage, andpublication. Clearabledescription,slug, andfeaturedImagevalues useOptionalUpdate<T>, so clients can explicitly clear text or image references without ambiguous JSON nulls. PATCH requiresIf-Matchwith the currentEventSeriesDtoorEventSeriesListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateEventSeriesAsync(Guid id, UpdateEventSeriesDto body, string? if_Match = null, ...). The handler loads the series with child events, validates manually, checks concurrency, saves once, and invalidates affected child event detail caches plus the tenant event-list cache tag. OldPUT /api/eventseries/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking EventRegistration update contract: the old authenticated
PUT /api/eventregistration/{id}broad body with route/body identifiers, user/session/tenant IDs, approval status, intent, and ATProto record fields is replaced byPATCH /api/eventregistration/{id}/ route nameUpdateEventRegistration. The route ID is authoritative andUpdateEventRegistrationDtono longer includesid; request bodies contain nullable property groupsuser,session,intent,approvalStatus, andatprotoRecord. Clearableintent,approvalStatus, andatprotoRecordvalues useOptionalUpdate<T>, so clients can explicitly clear relationship/status fields without ambiguous JSON nulls. PATCH requiresIf-Matchwith the currentEventRegistrationDtoorEventRegistrationListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateEventRegistrationAsync(Guid id, UpdateEventRegistrationDto body, string? if_Match = null, ...). The handler validates referenced user/session/intent/status/record entities before mutation, rejects cross-tenant session moves and wrong-event intent assignments, saves once, and invalidates affected parent event detail/list caches. Existing registrations receive a non-emptyevent_registrations.concurrency_stampthrough migrationAddEventRegistrationConcurrencyStamp. OldPUT /api/eventregistration/{id}clients receive405 Method Not Allowed. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking EventSessionLanguage update contract: session-language relationship updates are now exposed as
PATCH /api/eventsessionlanguage/{id}/ route nameUpdateEventSessionLanguage. The route ID is authoritative andUpdateEventSessionLanguageDtocontains nullable relationship groupssessionandlanguage; each present group must include its required non-nullable ID. PATCH requiresIf-Matchwith the currentEventSessionLanguageDtoorEventSessionLanguageListDtoconcurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must callUpdateEventSessionLanguageAsync(int id, UpdateEventSessionLanguageDto body, string? if_Match = null, ...). The handler validates the target session and language before mutation, rejects cross-tenant session moves, prevents duplicate session/language assignments, saves once, and invalidates affected parent event detail/list caches. Existing session-language rows receive a non-emptyevent_session_languages.concurrency_stampthrough migrationAddEventSessionLanguageConcurrencyStamp. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Breaking event/session lifecycle contract: event imports are now first-class authenticated writes at
POST /api/event/import/ generatedImportEventAsync; event publish-readiness is resource-authorized; event publish/archive/cancel and session create-draft/schedule/publish/cancel/complete/archive transitions use explicit named routes and HAL relations. New generated session lifecycle methods areCreateDraftEventSessionAsync,ScheduleEventSessionAsync,PublishEventSessionAsync,CancelEventSessionAsync,CompleteEventSessionAsync, andArchiveEventSessionAsync.EventSessionDtoandEventSessionListDtonow expose nullable schedule/local projection fields plusisScheduled,concurrencyStamp,eventSessionStatusId,eventSessionStatusMasterCode, andeventSessionStatusFullName. Old clients that treatedstartTime/endTimeas always present must handlenullfor drafts and gate lifecycle UI from_links(create-session-draft,schedule,publish,cancel,complete,archive) rather than local role/status checks. Session publish is rejected unless the parent event is published; event moderation cascades to sessions, and heavy event redaction clears/redacts session-owned content and image references. Target milestone: API version0.1development mainline; no compatibility window for pre-v1 generated clients. Contract evidence:schemas/openapi.json,docs/API_CONTRACT_INVENTORY.md, andExplore.Blazor.Client/Clients/EventApiClient.g.cswere regenerated through the documented API/NSwag workflow. -
Phase 19 refund protection contract: account and Studio payment resources project bounded refund, dispute deadline, and payment-specific material-change-choice truth; account buyers receive HAL-authorized event-cancellation refund and material-change response commands; Studio receives HAL-authorized refund creation, same-attempt
retry-refundrecovery for definitive provider blocks, plus event-level refund/material-change campaign progress and resume recovery. Buyer-refunded totals follow main provider proof whileRefundedrequires exact buyer and application-fee legs. Campaign free-text decision reasons are server-only. All writes are authenticated, server-authorized, rate-limited, idempotent, private/no-store, and provider-neutral. OpenAPI, inventory, and the NSwag client are generated from the updated contract.
- Clean Architecture + CQRS API with MediatR.
- Keycloak JWT authentication.
- Tenant-aware data access with global query filters.
- OpenAPI + Swagger + Scalar documentation endpoints.
- Graceful shutdown behavior for rolling deployments.