Skip to content

Latest commit

 

History

History
1097 lines (968 loc) · 193 KB

API_CHANGELOG.md

File metadata and controls

1097 lines (968 loc) · 193 KB

Scope: externally observable API behavior and source-defined versioning. Source: API routes and generated OpenAPI contracts.

API Changelog

2026-10-18

  • 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 return apiKeyConfigured. Authentication settings and onboarding reads no longer return googleClientSecret or keycloakClientSecret, and authentication provider patches no longer accept googleClientSecret. Authorization-provider reads no longer expose cerbosAdminCredentialsOwnership, cerbosAdminPasswordConfigured, or cerbosAdminUsernameConfigured. Tenant onboarding settings and completion no longer carry aiAssistantApiKey. 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/internal is 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-orders requires Idempotency-Key. Preserve the same key for retries of the same order intent.
  • Breaking: publish localization import bounds. POST /api/admin/localization/bundle requires languageCode (1..35 characters) and translations; 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.

2026-10-12

  • Breaking: native draft program schedule nullability. Nested event-creation sessions accept absent StartTime and EndTime for 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.

2026-10-06

  • Tenant-owned external API-key creation without a resolved trusted tenant now returns HTTP 400 validation 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 200 now publishes ExternalApiKeyIssuanceDto, not a generic command-response envelope. Required fields are id, keyId, disclosureStatus, and apiKey. Disclosure is exactly Issued or PreviouslyIssued, never null; recovery requires explicit null apiKey. 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 success Boolean. 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.

2026-10-05

  • Breaking: bounded external API key issuance. POST /api/ExternalApiKey now requires exactly one case-sensitive Idempotency-Key containing 1..128 ASCII [A-Za-z0-9._:-] characters. Native creation requires OperationKey; 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" (raw apiKey returned) from "PreviouslyIssued" (apiKey = null, metadata only). Authorized recovery of committed operations does not mint credentials. Changed normalized policy returns 409, revoked owner authority returns 403, an unbound identity returns 401, and an unavailable issued key returns 404. 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.

2026-10-04

  • 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.

2026-10-02

  • Breaking public discovery traversal. GetEvents accepts opaque cursor and pageSize instead of public pageNumber. The HAL resource exposes snapshotCount, truncated, expiresAt, hasMore and forward next, 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-store 400, 410 and 409 ProblemDetails; unavailable authority or capacity returns 503. 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 409 ProblemDetails, and denied authority returns 403. Candidate fields remain flattened alongside _links.

  • Occurrence-correct discovery. Public date, temporal and governed-area filters match the same published session. Cards expose matchingSession and nullable additionalSessionCount, with permitted regional city/country. Unknown or inactive areaId values 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 503 with discovery_unavailable.

  • Breaking: storage provenance versus delivery. Persisted StorageObject.Uri is replaced by nullable SourceUri foreign-origin provenance. Ordinary storage uri is 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_external records 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 profilePictureStorageObjectId from externalProfilePictureUri. Display URLs are derived independently; User profileImageKey is removed. The user profileImage PATCH group selects a managed profilePictureId or 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.

2026-10-01

  • 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.

2026-09-29

  • 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, explicit configuration.import scope 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/manifest and import POST /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.

2026-09-23

  • 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 HAL edit relation. Anonymous callers receive 401; signed-in non-administrators receive 403. The existing tenant EventResources category now advertises edit only 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}/destination accepts 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}/access returns an empty 302 with the destination in Location, private/no-store caching and no-referrer policy. Audience and management DTOs gain only the safe origin; eligible HAL has a same-origin access action. 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 export authority 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 at POST /api/event-resource-provider-activation/begin, and attest convergence at POST /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 explicit frozenParentPolicyContractConfirmed. 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.

2026-09-22

  • Breaking: retired Keycloak startup reconciliation. AppHost and Docker Compose no longer launch keycloak-init or 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/plans now requires a closed RepairClient, CreateClients or CreateRealm intent. 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}, and POST /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.

2026-09-21

  • 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 explicit Unavailable, with operator_identity_countries_unavailable; no partial mandatory selector is supplied. Registration identifiers remain optional; authority options are empty with registrationAuthorityState: NotSupported because no registry exists. The tenant-independent resource is private/no-store and exposes only self and refresh, never edit authority. Normal authenticated instance identity readers discover it through form-options; setup-secret authority is not extended. No persisted identity values, credentials, migration or new dependency is added.

2026-09-20

  • Breaking: setup finishes before publication. Completion requests must submit the current journey generation as expectedJourneyGeneration (nested under settings for 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/journey for 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 publicDisclosure and paidCommerce assessments. 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-store and 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 also private, 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.

2026-09-13

  • 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.

2026-09-08

  • SQLite directory temporal queries. The existing event directory keeps filtering, counts and pagination in SQLite instead of failing on unsupported DateTimeOffset comparisons. 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 BrandingSettingsDto adds nullable supportEmail, sourced from instance-scoped branding.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 SMTP email.from_address or email.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 409 guest_registration_cancellation_ineligible. The server-authored cancel-registration POST 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-status only 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 intended Idempotency-Key, returning a private HAL challenge without allocating inventory. StartGuestRegistrationOrder keeps its route/body and now requires X-Registration-Challenge and X-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 emits signup:atproto, signup:keycloak or signup:google with the exact shared destination; there is no signup: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. GetInstanceOnboardingAuthProviderConfiguration retains its flattened configuration fields and adds server-authored lifecycle links. The generated response is HalResourceOfAuthProviderConfigurationDto; 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.

2026-09-07

  • Guarded SMTP delivery disable. Instance administration exposes POST /api/instance/settings/smtp/disable-preview and /disable; current-tenant administration exposes POST /api/settings/email-delivery/disable-preview and /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 acknowledgement DISABLE 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 deliveryEnabled and server-authored action links. Clients discover disable-preview and disable rather 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 uses HalResourceOfInstanceSmtpSettingsDto instead of its former plain response type.

  • Breaking: Local sign-in accepts an identifier. API and BFF Local login requests use identifier instead of email, 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 exposes pendingOperationId only to active setup authority, and advertises complete-local through HAL for eligible setup.

  • Breaking: managed Local administrators use existing identity references. Managed provisioning accepts localIdentity.localSubjectId instead 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-identities ListLocalIdentities
    POST /api/instance/local-identities CreateLocalIdentity
    POST /api/instance/local-identities/{userId}/temporary-credential ResetLocalCredential
    GET /api/instance/local-identity-operations/{operationId} GetLocalCredentialOperation
    POST /api/instance/local-identity-operations/{operationId}/reconcile ReconcileLocalCredentialOperation

    Create 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: null for 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_stamp and 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.

2026-09-06

  • Additive: first-use Local credentials yield restricted replacement authority. Local login can return replacementChallenge instead of an ordinary token. POST /api/auth/local/credential-replacement accepts 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 from schemas/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-replacement requires antiforgery and accepts only the new password; the BFF supplies its protected challenge server-side. Completion clears the challenge and returns fixed /login navigation 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) and POST /bff/auth/local/register no 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 returns authentication_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.

Email-dispatch operator hold ownership

  • Status resources expose nullable parkReason with CapabilityUnavailable or Operator. deliveryStatus is now a typed enum in the contract; JSON continues to use the existing named status values.
  • Permission-qualified park links 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.

2026-09-01

  • Additive: instance bootstrap status reports typed onboarding state. The status response gains value-free state, mode, provider, and generation fields 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 from schemas/openapi_islamu-event.json to 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-Capability response header, mutations require fresh UUIDv7 Idempotency-Key values, 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 from schemas/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.

2026-08-30

  • 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 from schemas/openapi_islamu-event.json.

  • Additive: bounded configuration import sessions now have separate instance and tenant APIs. Instance administrators upload strict v1alpha2 ConfigurationManifest bytes through POST /api/control-plane/configuration-import/sessions; tenant administrators upload strict TenantConfigurationPackage bytes through POST /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 through X-Configuration-Import-Token for 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 from schemas/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 TenantConfigurationPackage from GET /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-transfers bind 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 and Accept-Language; the generated client operation is GetPublicLegalDocument.

  • Breaking (pre-v1): /terms and /privacy no 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.

2026-08-29

  • Added: machine-readable ticketing deployment capability status. GET /api/deployment/ticketing-capabilities returns the authoritative production-approved, test-only, or disabled status, 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.

2026-08-28

  • Breaking: legal identity is structured and responsibility-specific. Tenant admins now use the HAL-gated GET/PATCH /api/tenant/settings/documents/directory-operator-identity resource with grouped PATCH presence and optimistic concurrency. Anonymous public settings/shell expose separate directoryOperator and instanceOperator objects and return non-cacheable RFC 7807 503 with tenant_identity_unavailable when 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 groups organizerMerchant, tenantDirectoryOperator, instanceOperator, and paymentOperations and 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/complete accepts CompleteTenantOnboardingRequest, 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.

2026-08-26

  • 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/query targetId, use closed reason codes, and expose permission-matched HAL relations. Scanner issuance rejects missing, cross-event, stopped, or non-PlatformManaged targets before secret material is created.
  • Additive: admission check-in reporting is target-exact and export-safe. GET /api/events/{eventId}/admission/check-ins/summary requires targetId and returns long CheckedIn/Undone and Active/Inactive counts, target type, and an hourly last-activity bucket; rejected attempts are not reported as persisted facts. Authenticated GET /api/events/{eventId}/admission/check-ins/audit uses 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 require EventCheckInView, 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 reasonCode with one of OperatorCorrection, DuplicateScan, WrongTarget, or ExceptionalReconciliation; the former free-form reason property is removed. Append-only facts persist only the enum identity and the compensated check-in UUIDv7.

2026-08-25

  • 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 _links round 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 or with; 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. AdmissionTicketDto now includes registrationOrderId, holderDisplayName, ticketTypeName, issuedAtUtc, and typed entitlements with human event/day/session presentation; recovery, QR, and print delivery documents carry the same presentation facts. Its HAL resource includes registration-order so clients follow authoritative order affordances rather than infer refund/support authority. /api/tickets/{ticketId}/qr and /api/tickets/{ticketId}/print changed from GET to POST and operation IDs are now ReissueCurrentAdmissionTicketQr and ReissueCurrentAdmissionTicketPrint. 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 relations qr-code and print advertise 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-suggestions as a private, no-store HAL collection with bounded local search, governed source/visibility metadata, dedicated throttling, and no client-owned provider or coordinate authority. Added POST /api/location/{id}/address-approval with If-Match optimistic concurrency; clients must render approval only from the approve-tenant-address HAL 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-lived addressSelectionToken; 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.items and AddressProviderOutcome, 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 from schemas/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's CreateEventLocationDto remove latitude and longitude without 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 through LocationDto and the existing purpose-specific event-location disclosure contracts; routes, operation IDs, HAL capabilities, GET/write authorization, private-home classification/ownership consent, ETag/If-Match concurrency, and provider-selection seams are unchanged. Regenerate in order with dotnet build src/Explore.API/Explore.API.csproj --configuration Release --no-restore, the ApiContractInventory_Generate_WritesMarkdownToDocs integration-test filter, and dotnet 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 defaults view=Overrides, accepts explicit view=Portable, returns application/vnd.islamu.configuration-manifest.v1alpha2+json attachments, and exposes permission-filtered export-configuration-overrides / export-configuration-portable HAL 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 from schemas/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-intake through stable GetTenantReportingIntakePolicy and UpdateTenantReportingIntakePolicy operations. The response owns effective enabled, source, instance-lock, canDisable, and reason metadata; the update body accepts only enabled, with tenant and actor identity derived by the server. Clients must gate mutation through the HAL edit relation 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, and CreateTagDto omit tenantId. Controllers derive tenant identity from ITenantContext or 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.

2026-08-24

  • 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-store GET .../payment/acceptance operations return exact merchant, independent instance operator/official status, delivery, line/money, refund language/version/text, support/complaint, provider/charge, and statement facts. HAL adds payment-acceptance; clients must render and acknowledge those server facts before using start-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.

2026-08-23

  • Breaking (pre-v1) contract correction: EventLocationDisclosureState is 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 from schemas/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 as integer (for example entityTypeName, propertyType, exposureLevel on 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 of int?/object. EventLocationManagementDto.fields and .policy likewise reference EventLocationManagementFieldsDto and EventLocationDisclosurePolicyDto instead of anonymous generated shapes.
  • Breaking (pre-v1): GET /api/location/by-city/{city} and GET /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/management returns every EventLocation on the event as HalCollectionResource<EventLocationManagementDto>. The existing /locations/review route is now the remediation-filtered specialization of the same read; both are authenticated, private, no-store, and carry per-row edit / remediate-location affordances.
  • Additive: consent-backed private home ownership. POST /api/location/{id}/private-home and POST /api/location/{id}/private-home/ownership take PrivateHomeOwnershipConsentDto (consentAcknowledged, consentVersion) plus the current stamp in If-Match. Ownership is always claimed by the authenticated actor; the aggregate rejects any transfer where the consenting user is not the new owner. LocationDto adds the descriptive locationKindId so management surfaces can offer the right consent flow.
  • Additive: event-location-privacy-review readiness check and the Explore.EventLocationPrivacy meter. The check reports aggregate remediation backlog depth and its operator threshold and degrades above it. The meter exposes event_location_privacy_disclosures_total (purpose, state), event_location_privacy_corrections_total (event_type, status), and the event_location_privacy_review_queue_depth gauge. All dimensions are bounded and identifier-free.

2026-08-20

  • 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-commerce status read. HAL adds start-payment, payment-status, checkout-redirect, retry-payment, and studio-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 RequireIdempotencyKey operation 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 archive for a Published Event. Published Events retain authorized cancel, 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 _links and remove any local status/claim fallback. Routes and operation IDs are unchanged, with no compatibility affordance before v1.0.
  • Breaking (pre-v1): EventSessionDto and EventSessionListDto add parentEventStatusId. Session detail and collection lifecycle links now evaluate the current session status, parent Event status, startTime, endTime, and endTimeType through the same Domain predicates as command execution. In particular, publish and complete require a Published parent Event. Generated clients must regenerate from schemas/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-Key response replay.

2026-08-19

  • Additive: GET /api/instance/settings/authz-provider/package/status returns AuthorizationPolicyPackageStatusDto — 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 anonymous authz-provider/status readiness probe, which must not disclose whether authorization is currently degraded.
  • Breaking (pre-v1): the cerbos.failure_mode governance setting was deleted. It was inert in two ways at once: its definition documented the values deny/allow while its parser only recognised open, and open was 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.

2026-08-16

  • Breaking (pre-v1): control-plane command failures now return RFC 7807 ProblemDetails instead of a bare BaseCommandResponse body. The shared MapCommandResponse helper — 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 → 400 ValidationProblemDetails. Success responses are unchanged. ProducesResponseType metadata 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. InstanceSettingsController was deleted after all 47 of its actions moved; it had become an empty shell.
  • The only externally visible difference is the OpenAPI tags array, which is derived from the controller class name. Tags now name the real capability — for example ControlPlaneTenantPlan, WebhookEndpoints, InstanceStorageSettings — instead of grouping every operation of a former mega-controller under one label. This affects documentation grouping only; the NSwag client uses SingleClientFromOperationId, so no generated method name, namespace, or signature changed. No client regeneration is required beyond the checked-in artifact already updated here.
  • Contract fix: HalCollectionEmbeddedOfSchedulerAdminJobDto now references HalResourceOfSchedulerAdminJobDto instead of emitting an untyped item schema, so generated clients type scheduler job collections rather than falling back to ICollection<object>.
  • Contract fix: HalResourceOfRegistrationAnswerAnalyticsDto previously emitted an empty wrapper schema because it was never registered in HalOpenApiSchemaCatalog. 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): RegistrationFormTemplateInput and InstantiateRegistrationFormTemplateInput were renamed to RegistrationFormTemplateInputDto and InstantiateRegistrationFormTemplateInputDto to satisfy the repository's DTO naming convention. Clients must regenerate.

2026-08-15

  • Additive provider-neutral promotion contract for API version 0.1: authenticated organizer management under /api/events/{eventId}/promotions adds private/no-store list/detail plus idempotent draft, revise, publish, immediate server-timed revoke, and code-rotate operations authorized exclusively by manage-paid-event-commerce for the persisted organizer actor. Draft HAL permits publish; published HAL permits revise-promotion, revoke, and rotate-promotion-code; the collection permits create-promotion. Safe DTOs expose versioned definition, eligibility, discount, window, limits, and masked-code facts only; raw codes are returned as issuedCode only 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 and X-Registration-Order-Capability guest orders add idempotent apply/remove routes beneath /api/events/{eventId}/registration-orders. Guest mutations use PublicTransactional rate 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-order 404; syntactically malformed JSON retains the global safe 400 contract. Order reads add pre-discount, discount, post-discount, fee, organizer-earnings, contribution, and final totals plus a masked applied label; HAL emits exactly one of apply-promotion or remove-promotion while 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-Key as 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.

2026-08-14

  • 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 adds publication-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 require manage-paid-event-commerce for the event's persisted organizer actor; no administrator fallback merchant exists.

2026-08-12

  • Additive governed contact-sharing contract for API version 0.1: EventContactShareConsent now supports User, RegistrationPurchaser, RegistrationParticipant, and GuestContact subjects 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 advertise export-attendees only for a verified organization organizer and an authorized ExportSharedContacts decision. 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 RetentionUntil deadlines 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.

2026-08-10

  • Additive registration-provider framework contract for API version 0.1: anonymous POST /api/integrations/registration/{provider}/{bindingId}/callback records bounded provider-submission callbacks through the shared incoming-webhook ledger and returns 202 Accepted for verified, duplicate, malformed, unknown, stale, or parked evidence, with 413 reserved 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-providers manage 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 relations manage-registration-channels and view-registration-provider-health are 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-route Content-Security-Policy with exact frame-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.

2026-08-01

  • Additive native registration-submission contract for API version 0.1: authenticated order owners and capability-scoped guests can read typed requirement-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 both X-Registration-Attempt-Capability and 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 conditional skip HAL 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 declare Idempotency-Key explicitly, guest writes also validate the order capability, and validation ProblemDetails expose stable issue codes and form field keys without answer values. Order HAL advertises requirement-progress only while requirements await completion, while the separate walk-in flow retains optional-questionnaire as 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 quoted If-Match; stale writes return 409, 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, including manage-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 strong If-Match concurrency. Workflow requirement resources expose persisted isAttached state and advertise exactly one permission-checked attach or detach HAL relation. Walk-in events may expose one published standalone form descriptor at the anonymous /api/events/{eventId}/participation/optional-questionnaire route, advertised only through the optional-questionnaire HAL relation; invalid or absent descriptors return non-leaking 404, and attachment changes create no registration-order or participant state.

2026-07-31

  • 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-gated view-participants HAL 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: RegistrationOrderLifecycleResponse and GuestRegistrationOrderLifecycleResponse are renamed to RegistrationOrderLifecycleResponseDto and GuestRegistrationOrderLifecycleResponseDto across Application, API, and generated Blazor contracts. The pre-1.0 contract has no aliases or compatibility shims; regenerate clients from schemas/openapi_islamu-event.json.

2026-07-30

  • Additive deletion-conflict contract for API version 0.1: DELETE /api/eventday/{id} and DELETE /api/eventsession/{id} now return typed 409 Conflict ProblemDetails when a published ticket entitlement references the schedule target. Successful deletion remains 204 No Content, missing targets remain 404, 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-gated PATCH /api/instanceonboarding/profile and POST /api/instanceonboarding/complete requests 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: concurrent POST /api/events/{eventId}/ticketing/draft and POST /api/events/{eventId}/ticketing/draft:clone attempts that collide on catalog-version uniqueness, plus duplicate capacity-pool name races, now return retryable 409 Conflict with event_ticketing_concurrency_conflict instead of 500; 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 scalar Event.Price or Event.CurrencyCode, and EventSession contracts no longer expose EventSession.Price. The persistence model removes the matching Event and EventSession price and currency_code columns without a compatibility field or decimal fallback. Published Event reads instead expose nullable ticketPriceSummary, derived from the published EventTicketCatalogVersion, with summaryCode, nullable currencyCode, currencyMinorUnitDigits, and fromAmountMinor. Clients must regenerate from schemas/openapi_islamu-event.json and render the summary rather than treating an Event or session scalar as pricing authority.

2026-07-29

  • Breaking storage-upload hardening for API version 0.1: POST /api/storageobject/generate-upload-url (GenerateStorageObjectUploadUrlAsync) and caller-authored POST /api/storageobject (CreateStorageObjectAsync) are removed with UploadRequestDto, UploadUrlResponseDto, and CreateStorageObjectDto. Browser uploads now converge on authenticated same-origin BFF sessions backed by provider-neutral CreateStorageUploadSessionAsync, UploadStorageUploadSessionContentAsync, and CancelStorageUploadSessionAsync; 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 provider objectKey values.
  • Additive authenticated setup contract for API version 0.1: PATCH /api/instanceonboarding/profile / SaveInstanceOnboardingProfile accepts SelfHostOnboardingProfileDto only 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 persist Purpose, TimeZone, secrets, route history, snapshots, or browser state. Instance onboarding status advertises the save-profile HAL relation only for authenticated callers with active setup-secret authority. The endpoint returns typed validation/auth/setup/rate-limit ProblemDetails and is represented by regenerated SaveInstanceOnboardingProfileAsync client code.
  • Breaking global/contextual Actor read correction for API version 0.1: public ActorDto and ActorListDto responses no longer serialize legacy tenantId, private userId, 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 emits subscribe and subscription only from a non-serialized request-local discoverability decision plus normal authorization; global collections expose no tenant-local subscription affordance. Regenerate clients from schemas/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 pending OrganizationTenant; 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: generic POST /api/actor, PATCH /api/actor/{id}, and DELETE /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 exact GET /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: authenticated GET /api/events/{eventId}/ticketing returns 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 integer long ...Minor values. Catalog relations use create-draft, clone-draft, create-type, create-pool, publish, and item-level edit or delete. The response does not embed instance monetization settings.
  • Additive instance platform-monetization contract for API version 0.1: authenticated Admin-class GET and PUT /api/instance/settings/platform-monetization read and replace the active versioned fee policy and separate contribution setting. Fixed charges use integer minor units and percentages use integer basis points, where 10_000 = 100%. The HAL response emits edit only 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.

2026-07-28

  • Additive global Actor moderation contract for API version 0.1: authenticated instance administrators can call POST /api/actor/{actorId}/moderation/suspend, POST /api/actor/{actorId}/moderation/reinstate, POST /api/actor/atproto-identities/{identityId}/moderation/suspend, and POST /api/actor/atproto-identities/{identityId}/moderation/reinstate. Each request body contains only reasonCode; the route selects the moderation action. Commands authorize the existing global-actor-moderation instance-setting update and handlers recheck instance-admin authority. Same-state retries are idempotent, identity reinstatement preserves IsActive, and successful moderation invalidates public Event caches. All four operations declare WritePolicy and typed 429 Too Many Requests metadata. 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-management reads remain available for ineligible Events through additive GET /api/eventday/management/by-event/{eventId}, GET /api/eventsessionlanguage/management/by-event/{eventId}/by-session/{eventSessionId}, GET /api/event/{id}/management-aspects/islamic, and GET /api/event/{id}/management-aspects/tech operations. 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.

2026-07-27

  • Breaking tenant-settings write cleanup for API version 0.1: PUT /api/tenantonboarding/settings and operation UpdateTenantOnboardingPolicySettings are 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/complete retains UpdateTenantPolicyRequest for initial onboarding completion, and GET /api/tenantonboarding/settings remains available for the management read model. Regenerate clients from schemas/openapi_islamu-event.json; there is no compatibility route or window before v1.0.
  • Breaking federation-state authority removal for API version 0.1: all GET, POST, PUT, and DELETE operations under /api/actorkeystore and /api/syncstate are 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 from schemas/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 flat PUT bodies to grouped PATCH at 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 from schemas/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 broad PUT bodies to grouped PATCH at 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 grouped PATCH at their existing routes. StorageObject bodies contain only optional metadata, access, and ownership groups; 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 broad PUT bodies to grouped PATCH at /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 in If-Match. Metadata leaves update independently; explicit wrappers clear nullable description/event-type values; supplying definitions.items atomically replaces definitions and nested options, while omitting the definitions group preserves them. Template detail reads now expose concurrencyStamp; sync diff/apply/history operations remain separate. Regenerate clients from schemas/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 flat PUT bodies to grouped PATCH at /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 GUID If-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 remain PUT. Regenerate clients from schemas/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 expose isRegistrationRequired or externalRegistrationUrl. They now use typed participationConfiguration lookup facts whose mode and obligation are required. Organizers configure the isolated participation resource through authenticated PATCH /api/events/{eventId}/participation (ConfigureEventParticipation) with one required strong quoted GUID If-Match; event HAL exposes configure-participation only through organizer/assignment manage-registrations authority. Public clients render participation actions only from start-registration, sign-in-to-register, or external-registration on 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 from schemas/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 from PUT to PATCH; EventSessionAgendaItem and EventSessionGroup changed from flat PUT bodies to route-ID grouped PATCH; EventSessionSpeaker update moved from PATCH /api/eventsessionspeaker/management/by-session/{eventSessionId}/{id} to PATCH /api/eventsessionspeaker/management/{id} and now takes only the authoritative assignment ID from the route. Session-group and speaker updates require strong If-Match; group list/detail responses expose concurrencyStamp. Islamic aspects now use POST /api/event/{id}/aspects/islamic (CreateEventIslamicAspect) and grouped PATCH (UpdateEventIslamicAspect); Tech aspects use the matching POST (CreateEventTechAspect) and grouped PATCH (UpdateEventTechAspect) operations at /api/event/{id}/aspects/tech. TagTypeTags and CategoryTypeCategories remain Application-only grouped relationship commands. Regenerate clients from schemas/openapi_islamu-event.json; there is no compatibility route, flat DTO, or overload before v1.0.
  • Additive event-list provenance contract: EventListDto now exposes nullable provenanceTypeCode, 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 from PUT to PATCH at their existing paths and operation IDs. Their request cells collection 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 accept targetActorId; route identity is authoritative, and the nullable notificationLevel group plus expectedConcurrencyStamp form the update contract. Missing subscriptions return typed 404 ProblemDetails. The three matrix mute endpoints remain focused PUT actions, and HAL save now advertises PATCH. Regenerate clients from schemas/openapi_islamu-event.json; there is no compatibility DTO, property, route, or overload before v1.0.

2026-07-26

  • 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 from PUT to route-ID PATCH at 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 include isActive so editing an inactive link preserves its state. Tenant/footer external-link writes accept relative paths and HTTPS URLs by default; instance setting security.require_https_external_urls=false additionally permits HTTP only for an explicitly trusted HTTP-only private network. Regenerate clients from schemas/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/EventListDto no longer expose the legacy isUserReported or external eventUrl fields. 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-actions and /api/events/{eventId}/organizer-claims, with claimant reads at /api/actors/{claimantActorId}/organizer-claims. Public-action responses add fixed openInNewTab: true and rel: "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 uses withdraw-organizer-claim and 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 from schemas/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: all GET, POST, PUT, and DELETE operations under /api/userexternallogin and /api/indexeddid are removed, along with their HAL resources and generated client methods. Browsers can no longer submit a UserId, tenant, provider key, DID, handle, PDS host, or signing-key metadata to claim identity ownership. Internal UserExternalLogin and IndexedDid persistence remains available only to verified authentication and federation flows until the global actor migration replaces the DID cache model. Regenerate clients from schemas/openapi_islamu-event.json; there is no compatibility route or window before v1.0.
  • The exact designated provider routes GET and PATCH /api/instance/settings/auth-provider plus GET and PATCH /api/instance/settings/authz-provider now 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 typed 429 Too Many Requests OpenAPI responses; PATCH operations also declare x-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-user Write window.
  • Task 1.3 persistence hardening synchronizes default-tenant module capabilities only in SingleTenant mode 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 correct SystemLocked or SystemDefault source.

2026-07-25

  • Privacy-erasure contract finalization: DELETE /api/user now returns 202 Accepted, Location, Retry-After, and a one-time receipt after local commit. The receipt-status route is GET /api/privacy-erasure/status, documented in OpenAPI with the custom PrivacyErasureReceipt apiKey scheme on the Authorization header (Authorization: ErasureReceipt <receipt>), marked private, no-store, and bounded to fenced, provider_pending, or completed plus aggregate provider-work counts/timestamps. Missing, invalid, wrong, and expired receipts all collapse to the same 401 challenge. 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.items as HalResourceOfEventLocationManagementDto[] instead of an untyped array. The wire payload and route behavior are unchanged; regenerated NSwag clients use ICollection<HalResourceOfEventLocationManagementDto> rather than ICollection<object>, so source consumers may need to recompile or update assignments.

2026-07-24

  • Breaking instance-secret read hardening for API version 0.1: GET /api/instance/settings/smtp no longer exposes username or password in InstanceSmtpSettingsDto; clients use usernameConfigured and passwordConfigured, then send replacement credentials only through PatchInstanceSmtpSettingsDto.configuration. GET /api/instance/settings/ai-assistant replaces readable apiKey with apiKeyConfigured; model discovery may carry a transient key, while persisted replacement keys are accepted only through PatchAiAssistantGovernanceSettingsDto.providerConfiguration. Instance governance reads never return persisted key material. Regenerate clients from schemas/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 ID UpdateTenantStorageSettings, and generated method UpdateTenantStorageSettingsAsync to PATCH /api/tenant/settings/storage, operation ID PatchTenantStorageSettings, and generated method PatchTenantStorageSettingsAsync. The request now carries optional policy and s3 groups 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 ID ReplaceTenantBrandingSettingsDocument, and generated method ReplaceTenantBrandingSettingsDocumentAsync to PATCH /api/tenant/settings/documents/branding, operation ID PatchTenantBrandingSettingsDocument, and generated method PatchTenantBrandingSettingsDocumentAsync. The request requires expectedConcurrencyStamp and at least one displayName or assets group. Stale stamps and database concurrency races return 409 Conflict with /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 ID UpdateTenantFooterSettings, and generated method UpdateTenantFooterSettingsAsync to PATCH /api/footer/settings, operation ID PatchTenantFooterSettings, and generated method PatchTenantFooterSettingsAsync. The request uses optional general, template, description, socialLinks, and copyright groups. The authenticated GET /api/footer/settings operation ID GetTenantFooterSettings and generated method GetTenantFooterSettingsAsync return the scalar settings, typed social links, lock state, and HAL capabilities used by the admin client.
    • The three former PUT operations and generated update/replace methods are removed, with no compatibility alias or window before v1.0. Regenerate from schemas/openapi_islamu-event.json, send only the group leaves being changed, and drive editability from returned HAL relations. Exact-key setting routes that still use PUT, such as instance ATProto key updates, are unchanged and are not part of this grouped-resource migration.
  • Breaking reporter-communication contract for API version 0.1: SubmitEventReportDto, MyEventReportDto, ModerationReportQueueItemDto, ModerationReportDetailDto, and their HAL wrappers replace ReporterContactConsent with required booleans ReportCaseUpdatesConsent and ReportFollowUpContactConsent. POST /api/event-reports now requires clients to choose each purpose independently; reporter-owned and moderation reads return both values. Anonymous submissions force both values to false, and there is no legacy alias or compatibility window before v1.0. The additive authenticated PUT /api/event-reports/my/{reportId}/communication-consent operation accepts both required booleans, updates only a reporter-owned report, and returns its refreshed HAL resource. My Reports detail and collection items expose update-communication-consent only through the server authorization pipeline. Clients must regenerate from schemas/openapi_islamu-event.json and gate withdrawal UI from that relation.

  • Breaking organization/group membership projection normalization: OrganizationListDto.currentUserRole and GroupListDto.currentUserRole are replaced by numeric currentUserRoleId lookup 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 stable roles.id value through currentUserRoleId; 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: the settings items returned by GET /api/settings/instance/atproto-federation and the tenant setting-group route now reference EffectiveSettingDto in OpenAPI instead of an anonymous inline schema. Generated clients therefore expose ICollection<EffectiveSettingDto> and deserialize the runtime string-valued source property as SettingSource, replacing the invalid anonymous int? projection that caused successful 200 OK responses to fail in Blazor. Clients must regenerate and replace uses of the former generated Settings type; the server JSON payload is unchanged, so there is no server-side data migration. Contract evidence is retained in schemas/openapi_islamu-event.json, EventApiClient.g.cs, and the OpenAPI plus generated-client deserialization regression tests.

Breaking Change Evidence

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-guard when 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.

Current Mainline (API version 0.1)

Key behavior in current code:

  • Additive authenticated workspace-shell context: GET /api/ui-shell/context / GetUiShellContext returns 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 call GetUiShellContextAsync.

  • Additive purpose-specific EventLocation routes: anonymous public disclosure is available at GET /api/events/{eventId}/locations; authenticated attendee disclosure uses GET /api/events/{eventId}/locations/my-access; authenticated management reads and policy updates use GET /api/events/{eventId}/locations/{eventLocationId}/management and PATCH /api/events/{eventId}/locations/{eventLocationId}/disclosure. Public responses always use the public disclosure purpose and have no shared output cache; private routes send private, no-store. Physical LocationId values 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 an Unknown row as Delivered or NotDelivered through the row HAL reconcile action. Generic replay no longer accepts Unknown, 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 through PUT /api/settings/instance/atproto-federation/{key}, and lock or unlock it through POST/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 server CanEdit, IsLockable, and IsLocked metadata.

  • Breaking AT Protocol federation boundary: every raw /api/atprotorecord read 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 at GET /api/event; a federated item may expose GET /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 version 0.1 development mainline; contract evidence is the runtime OpenAPI/HAL boundary suite and regenerated NSwag client.

  • Additive anonymous home-discovery contract: GET /api/public-experience/home / GetHomeDiscovery accepts optional coarse areaId and mode=area|online|all query context and returns one bounded HomeDiscoveryDto containing 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, and mode; generated clients call GetHomeDiscoveryAsync(...).

  • Media-type API versioning (Accept: application/json;v=0.1 or application/hal+json;v=0.1).

  • HAL responses with Prefer: return=minimal support.

  • Output caching policies (LookupData, ListData, DetailData) and HybridCache usage.

  • ETag middleware for successful JSON/HAL GET responses with 304 Not Modified support.

  • 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:read machine scope can send bounded JSON bodies to POST /api/management/upgrade/preflight / EvaluateManagementUpgradePreflight and POST /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 use EvaluateManagementUpgradePreflightAsync(ManagementUpgradePreflightRequest, ...) and VerifyManagementUpgradePostflightAsync(ManagementUpgradePostflightRequest, ...).

  • Additive managed tenant preflight contract: registered Control Plane callers with the dedicated event-management:read machine scope can send the strict versioned provisioning request to POST /api/management/tenants/preflight / EvaluateManagedTenantProvisioningPreflight. The side-effect-free 200 OK assessment 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 return 400. 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 use EvaluateManagedTenantProvisioningPreflightAsync(ManagementTenantProvisioningRequest, ...).

  • Additive managed tenant provisioning contract: a registered managed instance authenticates with the dedicated X-Control-Plane-Key scheme and can schedule Event-owned tenant creation through POST /api/management/tenants/provision / ScheduleManagedTenantProvisioning (202 Accepted), read safe operation state through GET /api/management/tenant-provisioning/{operationId} / GetManagedTenantProvisioningOperation, and cancel a still-pending operation through POST /api/management/tenant-provisioning/{operationId}/cancel / CancelManagedTenantProvisioningOperation. Scheduling is available only for persisted MultiTenant instances 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-push expose public VAPID configuration and authenticated current-device subscribe/status/unsubscribe flows. GET /vapid-public-key additionally exposes only the public key as anonymous text/plain for 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/listmonk expose sanitized settings reads, non-secret settings updates, write-only credential rotation, and server-side test connection. registration.created outgoing webhook payloads now use schema version 2 with required consentToEmailShare; attendee PII fields are optional and present only when ShareEmailWithOrganizer == true, so webhook consumers must tolerate missing PII when consent is false.

  • Additive public event URL endpoint: GET /api/event/public/{slugCode} / GetEventByPublicCode resolves clean public URL tokens such as {slug}-{publicCode} while existing GUID-based API routes remain available for API and management flows. EventDto and EventListDto now include server-owned publicCode; create/update requests still do not accept publicCode, and clients should build public event links from slug + publicCode instead of raw event GUIDs.

  • Additive public event Open Graph image endpoint: anonymous GET /api/event/public/{slugCode}/og-image returns the authoritative 1200x630 image/png for eligible published/public events, with a strong quoted ETag, Cache-Control: public, max-age=0, must-revalidate, Vary: Host, X-Tenant-Slug, and 304 Not Modified support; 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 return 429 Too Many Requests.

  • ProblemDetails behavior hardening: authorization middleware challenge/forbid results now return shared RFC 7807 bodies with code=authentication_required or code=forbidden, and rate-limit 429 Too Many Requests responses include code=rate_limited plus retry/rate-limit headers when available. This aligns runtime behavior with existing typed ProblemDetails metadata; 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=open is configured.

  • Additive authorization onboarding fields on AuthorizationProviderConfigurationDto: authorizationProviderManagedByDeployment, authorizationProviderBootstrapStatus, cerbosPoliciesSynchronized, and authorizationProviderBootstrapMessage. AUTHORIZATION_PROVIDER maps to the validated blank/local/cerbos selector. 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 / GetControlPlaneOverview returns 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 as ResourceKinds.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 _links for related instance-setting surfaces such as domains, plans, storage, authentication, and authorization. The plans relation 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 / GetControlPlaneDomains returns 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 as ResourceKinds.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 advertise update-version-draft and publish, published versions may advertise archive and clone, and archived versions advertise no lifecycle mutation. Clients must read the selected version's links rather than root relations. ControlPlaneTenantEffectiveConfigurationDto also adds rollbackAssignment; rollback is 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 one IUnitOfWork transaction. Regenerated clients expose ControlPlaneTenantPlanVersionDto._links and HalResourceOfControlPlaneTenantEffectiveConfigurationDto.RollbackAssignment. Target milestone: API version 0.1 development 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 / GetControlPlaneOperations returns 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 as ResourceKinds.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 POSTs activate, suspend, archive, reactivate, and schedule-purge expose bounded tenant fleet metadata for instance operators. Reads are MediatR-authorized as ResourceKinds.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, write TenantLifecycleLog audit 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 7807 409 Conflict with type=/problems/concurrent_update and code=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 matching ResourceKinds.Tenant / AuthorizationActions.Create HAL permission. POST /api/admin/control-plane/tenants/{tenantId}/schedule-purge / ScheduleControlPlaneTenantPurge is exposed only from archived tenant resources as a schedule-purge HAL affordance; it records audited destructive intent by moving the tenant to the non-active Purged lifecycle 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 ControlPlaneTenantEffectiveSettingDto can now include its own _links dictionary for independently authorized override, lock, and unlock actions. 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 advertise configuration only when the effective-configuration read is authorized. Prefer: return=minimal and 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, and override validates 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 write IsLocked, lock/unlock use expected-state compare-and-swap, and tenant mutations recheck the system lock after acquiring the shared lock. Rejections return safe BaseCommandResponse.FailureCode values such as setting_not_found, setting_scope_not_supported, sensitive_setting_not_supported, setting_system_locked, setting_validation_failed, setting_override_not_found, and setting_state_conflict.

  • Additive event-session speaker management reads and creates are available under /api/eventsessionspeaker/management/by-session/{eventSessionId}; updates use the authoritative assignment route PATCH /api/eventsessionspeaker/management/{id}. Authenticated management callers can read HAL speaker assignment rows with GetEventSessionSpeakersBySession, assign actors with CreateEventSessionSpeaker, update actor/session assignment groups with UpdateEventSessionSpeaker, and remove assignments with DeleteEventSessionSpeaker. The contract is parent-session resource protected, derives update identity from route-owned id, requires strong If-Match on updates, emits EventSession speakers and assignment edit/delete HAL affordances only when authorized, and is represented by generated client methods GetEventSessionSpeakersBySessionAsync, CreateEventSessionSpeakerAsync, UpdateEventSessionSpeakerAsync, and DeleteEventSessionSpeakerAsync. 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 a SchedulerPauseRequestDto body whose confirmationText equals 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 returns 400 with scheduler_confirmation_required. ResumeScheduler and all per-job actions are unchanged and unguarded. The scheduler overview additionally exposes erroredJobCount and executingViewIsNodeLocal; 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 an explore.scheduler.admin_actions metric, and a scheduler readiness check reports scheduler posture on /health. Regenerate clients from schemas/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, and POST /api/admin/scheduler/jobs/{group}/{name}/interrupt (InterruptSchedulerJob) requests cooperative cancellation of its executing instances. Both are authorized as InstanceSettings.Update under the scheduler.admin setting key and honour Scheduler:Quartz:AdminApiReadOnly. HAL emits reset-error only for a job whose state is error and interrupt only 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 is 409 Conflict with the new failure code scheduler_action_not_applicable rather than a success that changed nothing. Regenerate clients from schemas/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 through PauseScheduler, ResumeScheduler, PauseSchedulerJob, ResumeSchedulerJob, and TriggerSchedulerJob. Reads are MediatR-authorized as ResourceKinds.InstanceSetting / AuthorizationActions.InstanceSettings.View and controls as InstanceSettings.Update, all under the setting key scheduler.admin. The whole surface is opt-in via Scheduler:Quartz:AdminApiEnabled and answers typed 404 ProblemDetails when the host has not enabled it, so HAL-driven clients omit the section rather than render controls that fail. Scheduler:Quartz:AdminApiReadOnly (default true) withholds every control link and refuses mutating requests before the scheduler is touched; refusals return 409 Conflict with scheduler_read_only or scheduler_unavailable, and an unknown job returns 404. Jobs are a HAL collection so each row carries its own affordances: trigger is offered for every job, pause only for a job with active triggers, resume only for a paused one, and an on-demand durable job with no trigger of its own advertises trigger alone. The overview advertises only the meaningful lifecycle transition — pause while running, resume while in standby. Responses expose scheduling metadata only; trigger data maps, job payloads, recipients, and secrets are never read or projected. Regenerate clients from schemas/openapi_islamu-event.json.

  • Resolver bootstrap contract hardening: GET /api/instance/settings/resolver-config is now an anonymous, tenant-resolution-exempt read of non-sensitive routing flags, path prefix, and public domain settings. The Blazor BFF uses the existing generated GetInstanceResolverConfigurationAsync client 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}/content now declares a required application/octet-stream OpenAPI request body with string/binary schema. The generated UploadStorageUploadSessionContentAsync operation accepts a Stream, allowing the Blazor BFF to proxy reserved-session bytes exclusively through IEventApiClient; 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, and ProviderConfigurationStatusDto success schemas. Their generated client methods no longer return object, so callers consume explicit Valid, Success/Message, and Configured properties without JSON shape parsing.

  • Breaking onboarding-status HAL contract: GET /api/instanceonboarding/status / GetInstanceOnboardingStatus and GET /api/tenantonboarding/status / GetTenantOnboardingStatus now return HAL resources instead of bare InstanceOnboardingStatusDto and TenantOnboardingStatusDto bodies. The existing scalar status fields remain present and _links now carries the server-authoritative complete and management affordances; generated clients therefore return HalResourceOfInstanceOnboardingStatusDto and HalResourceOfTenantOnboardingStatusDto, 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-providers when the manage-authentication relation is present. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 generated clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs are 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.Update with the resolved tenant id. Regular authenticated users that previously reached handlers with authentication alone now receive 403 Forbidden; missing actor identity remains 401 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, and FooterAuthorizationTests.

  • Breaking EmailDispatch admin authorization hardening: status, tenant pause/resume, outbox park/replay, and POST .../resolve-without-replay require MediatR resource authorization as ResourceKinds.EmailDispatch with actions view, manage_tenant, park, replay, and resolve. The resolve operation accepts a bounded audit reason and changes only DeadLettered, Parked, or Unknown work to terminal Skipped. HAL controls use the same split actions and permanently omit mutation affordances after content redaction. Regular users receive 403; tenant admins remain tenant-bound; instance admins retain operator authority.

  • Setup-secret onboarding contract hardening: setup-secret-gated InstanceOnboarding endpoints now advertise the named SetupSecret rate-limit policy and typed RFC 7807 403, 410, and 429 responses in OpenAPI. The setup-secret filter returns ProblemDetails instead of ad hoc JSON for invalid setup secrets and completed setup mode; generated clients now deserialize those failures as ProblemDetails. POST /api/instanceonboarding/auth-provider-configuration/keycloak-bootstrap also advertises typed 502 and 503 ProblemDetails 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 and complete still 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) for POST /api/a/t.

  • Event and event-session template sync endpoints for diff/apply/history workflows, including 409 ProblemDetails types /problems/stale_sync_base and /problems/concurrent_update.

  • Event create/update/detail APIs split card summaries from long-form copy: description is capped at 150 characters for list/card UI, while content carries 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 / GetEventReportOptions exposes public reportability metadata and reason options for reportable events, POST /api/event-reports / SubmitEventReport accepts authenticated reports, GET /api/event-reports/my / GetMyEventReports returns the authenticated reporter's paged HAL status list, GET /api/event-reports/my/{reportId} / GetMyEventReport returns one reporter-owned status resource, and PUT /api/event-reports/my/{reportId}/communication-consent / UpdateMyEventReportCommunicationConsent changes 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: HalResourceOfReportingRoutingStateDto is now cataloged in the HAL OpenAPI schema map, so OpenAPI and NSwag expose the reporting routing state fields plus _links instead 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/eventregistration returns 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} returns 403 Forbidden when the route user differs from the authenticated user, and GET /api/eventregistration/{id} fails closed for registrations owned by another user. EventRegistrationDto and EventRegistrationListDto no longer expose serialized userId, userFullName, or userEmail; 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 version 0.1 development mainline; no compatibility window for pre-v1 generated clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking user-authentication-token contract cleanup: generic POST /api/userauthenticationtoken and PUT /api/userauthenticationtoken/{id} operations, CreateUserAuthenticationTokenDto, UpdateUserAuthenticationTokenDto, their commands/handlers/validators, serializer roots, and generated client methods are removed. Authenticated GET list/detail operations expose only self-scoped id, provider, pdsHost, and expiresAt; authenticated DELETE /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/session is 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 regenerated Explore.Blazor.Client/Clients/EventApiClient.g.cs.

  • Breaking external API-key management hardening: /api/externalapikey list/detail/usage-report reads no longer use shared output-cache policies and now advertise no-store response metadata plus the Authenticated rate-limit policy. Create/update/delete actions advertise the Write rate-limit policy and typed 401/403 ProblemDetails metadata. Owner-authority failures during create and usage-report authorization now return 403 Forbidden instead of validation 400 or an empty successful report, and deleting a missing or non-owned key now returns 404 Not Found instead of false 204 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 version 0.1 development mainline; no compatibility window for pre-v1 generated clients. Contract evidence: ExternalApiKeyControllerMetadataTests, ExternalApiKeyIntegrationTests, ExternalApiKeyObservabilityTests, schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs.

  • Breaking tenant role grant read authorization hardening: GET /api/tenant-user-role-grants and GET /api/tenant-user-role-grants/{id} are authenticated administrative contracts that require action view on islamuevent_tenant_user_role_grant through MediatR resource authorization. Regular authenticated users are denied; tenant admins are limited to the resolved tenant; instance admins retain administrative access. TenantUserRoleGrantDto and TenantUserRoleGrantListDto remain identity-bearing and should not be used for public tenant-member/profile display. Clients should handle 401/403 on these read routes and continue gating create/revoke affordances from HAL _links. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 generated clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking organization-member read authorization hardening: GET /api/organizationmember/{organizationId} and GET /api/organizationmember/member/{id} are authenticated administrative contracts that require action view on islamuevent_organization_member through 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. OrganizationMemberDto remains identity-bearing and now includes additive tenantId contract metadata so authorization scopes and generated clients can preserve tenant context. Clients should handle 401/403 on these read routes and continue gating member-management affordances from HAL _links. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 generated clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were 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 through GetModerationReportQueue and GetModerationReportDetail, then use TriageModerationReport, AssignModerationReport, DecideModerationReport, and ExecuteModerationReportDecision POST routes when the detail resource advertises triage-report, assign-report, decide-report, or execute-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: ModerationReportQueueItemDto no longer exposes reporterUserId or reporterActorId; ModerationReportDetailDto no longer exposes reporterUserId or reporterActorId; moderation evidence no longer exposes createdByUserId; moderation decisions no longer expose moderatorUserId; moderation signals no longer expose externalSignalId or correlationId; and moderation external-link projections no longer expose providerCaseId, providerSignalId, providerUrl, or correlationId. Clients should use state, reason, priority, case status, sync state, counts, and HAL action rels instead of stable user/provider pointers. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 generated clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking event moderation contract split: the old generic POST /api/event/{id}/moderate route and HAL relation moderate are replaced by explicit moderation contracts: POST /api/event/{id}/moderation/light / ModerateEventLight / HAL moderate-light, POST /api/event/{id}/moderation/heavy / ModerateEventHeavy / HAL moderate-heavy, and POST /api/event/{id}/moderation/unmoderate / UnmoderateEvent / HAL unmoderate. These moderation POSTs consume an EventModerationRequestDto JSON body with optional safe reasonCode and correlationId; 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 returns 503 ProblemDetails when redaction is committed but physical storage-object deletion remains pending. Public GET /api/event/{id} still hides moderated events, while authorized management reads use authenticated GET /api/event/{id}/management-detail / GetEventManagementDetails and GET /api/event/management/by-actor/{actorId} / GetManagedEventsByActor, both protected by the event view-management action rather than the public view action. Safe moderation audit metadata is exposed through authenticated GET /api/event/{id}/moderation/history / GetEventModerationHistory and HAL moderation-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 / ModerationIntegrationOspreyCallback accepts asynchronous Osprey-compatible signal callbacks from machine clients authenticated with X-API-Key. The endpoint requires the ModerationIntegration.OspreyCallback policy, 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 / ModerationIntegrationCoopCallback accepts signed Coop-compatible decision callbacks from API-key machine clients. The endpoint requires the ModerationIntegration.CoopCallback policy plus a valid timestamped HMAC-SHA256 signature over the raw body before JSON parsing. Verified callbacks and one unique IncomingWebhookEffectOutbox pointer 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 at GET /api/admin/incoming-webhook-effects/status and follow the HAL redrive action 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 / IntegrationSvixOperationalCallback accepts Svix-compatible signed operational callbacks through [AllowAnonymous] where the Svix signature is the authentication mechanism. The endpoint reads the raw body before parsing, verifies svix-id, svix-timestamp, and svix-signature with the configured webhooks.svix.operational_webhook_secret, acknowledges verified instance-level operational callbacks with 202 Accepted, and captures tenant-addressed payloads in incoming_webhook_messages when a tenantId/tenant_id is present. This route is independent of the outgoing webhook provider mode.

  • Additive webhook event catalog endpoint: GET /api/webhooks/event-types / GetWebhookEventTypes is 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 / OpenSvixAppPortal accepts only consumerId plus optional expiresInSeconds; caller-selected read-only mode, feature flags, provider application identity, tenant identity, binding identity, and capability flags are not accepted. Portal issuance and the open-provider-portal HAL 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 are no-store; successful and provider-failed attempts write secret-safe audit metadata before returning, and audit failure returns no portal URL. WebhookConsumerDto exposes no portal permission boolean, so Blazor clients must gate the action solely from the HAL relation. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 request shapes or generated clients.

  • Additive webhook provider endpoint: POST /api/webhooks/svix/app-portal / OpenSvixAppPortal creates 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 as islamuevent_webhook action webhook: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, and POST /api/webhooks/endpoints/{endpointId}/test / TestWebhookEndpoint expose 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, increments secretVersion, and never accepts or returns raw signing material; test creates an authoritative webhook.test message 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 HAL update, rotate-secret, test, and delete affordances, 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:ensure for instance-admin/provider automation. It creates or rehydrates provider-customer tenants using ExternalBinding, creates tenant-local TenantUser/TenantUserProfile state, 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 kind islamuevent_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} and GET /api/storageobject/presigned-url-by-key/{objectKey} are removed. Clients must resolve content through metadata-backed IDs such as GET /api/storageobject/{id}/content, public images through GET /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 uses download, presigned URL generation uses presigned_download, and GET /api/storageobject/{id}/public remains 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-session no longer returns or stores provider upload URLs/object keys; /bff/storage/upload-proxy rejects raw destinations and streams bytes to the API session content endpoint.

  • Instance storage administration is now provider-neutral. GET/PUT /api/instance/settings/storage exposes provider policy, max upload ceilings, default tenant quota, storage delegation lock, usage, provider health, and redacted optional S3 configuration. POST /api/instance/settings/storage/test tests the selected provider, and POST /api/instance/settings/storage/usage/recalculate reconciles 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 omitted policy and s3 leaves, 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.

  • /health now includes a storage readiness 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.

  • /health also includes storage-reconciliation for 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-subscriptions for 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 _links rather 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/stream as text/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 with Idempotency-Key, run-status polling, queued/in-progress run cancellation, proposed-action confirm/reject routes, and GET /api/ai/assistant/references for bounded event, actor, and organization reference search. Conversation, run, proposed-action, and reference affordances are HAL-driven (self, collection, active-state send-message, collection create, cancellable-state cancel-run, proposed-state confirm-action/reject-action, and reference event/actor/organization links); send-message accepts selected lightweight references for prompt context and returns the run-status polling route, confirm propagates Idempotency-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, and LocationRoomController create/update 400 responses now return RFC 7807 ValidationProblemDetails (application/problem+json) with code, traceId, timestamp, and optional correlationId instead of raw BaseCommandResponse<Guid> envelopes or anonymous { error } payloads. Clients should read validation details from errors and machine-readable status from code.

  • Breaking error-shape cleanup: ActorKeyStoreController and UserAuthenticationTokenController create/update 400 responses now return RFC 7807 ValidationProblemDetails (application/problem+json) with code, traceId, timestamp, and optional correlationId instead of raw command envelopes, anonymous { error }, or ad hoc Problem(...) payloads. ActorKeyStoreController delete not-found now returns RFC 7807 ProblemDetails with a resource_not_found fallback code. Clients should read validation details from errors and machine-readable status from code.

  • Breaking appearance update contracts: PATCH /api/user/appearance now accepts only an optional localization group containing nullable direction and language leaves; omitted leaves remain unchanged. PATCH /api/user/appearance/profiles/{profileId} splits editable profile metadata from light/dark palette snapshots, and PATCH /api/admin/ui-themes/{id} uses route-owned identity plus required rowVersion and nullable metadata, state, and palettes groups. Active-profile selection, current theme mode replacement, and profile archive remain focused PUT actions. 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/user partial-update route with body id is replaced by PATCH /api/user/{id} / route name UpdateCurrentUser. The route ID is authoritative and UpdateUserDto no longer includes id; request bodies contain only nullable update groups such as names and profileImage. PATCH now requires an If-Match header containing the current UserDto.concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateCurrentUserAsync(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-empty users.concurrency_stamp through migration AddUserConcurrencyStamp. Old PUT /api/user clients receive 405 Method Not Allowed, and old JSON bodies that still include id are rejected by the API's unmapped-member validation. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking Actor update contract: the old authenticated PUT /api/actor/{id} request wrapper { actorDto, appearanceDto } is replaced by PATCH /api/actor/{id} / route name UpdateActor. The route ID is authoritative and UpdateActorDto no longer includes id or tenantId; request bodies contain nullable groups such as profile, profileImage, appearance, federationIdentifiers, and federationMetadata. Clearable nullable fields use OptionalUpdate<T> field operations so clients can distinguish omitted fields from explicit clears. PATCH requires If-Match with the current ActorDto.concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateActorAsync(Guid id, UpdateActorDto body, string? if_Match = null, ...). Existing actors receive a non-empty actors.concurrency_stamp through migration AddActorConcurrencyStamp. Old PUT /api/actor/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking Category update contract: the old authenticated PUT /api/category/{id} broad request body with id, masterCode, fullName, and parentId is replaced by PATCH /api/category/{id} / route name UpdateCategory. The route ID is authoritative and UpdateCategoryDto no longer includes id; request bodies contain nullable property groups masterCode, fullName, and parent. parent.parentId uses OptionalUpdate<Guid?>, so clients can explicitly clear a parent with { "hasValue": true, "value": null }. PATCH requires If-Match with the current CategoryDto or CategoryListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateCategoryAsync(Guid id, UpdateCategoryDto body, string? if_Match = null, ...). Existing categories receive a non-empty categories.concurrency_stamp through migration AddCategoryConcurrencyStamp. Old PUT /api/category/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking Location update contract: the old authenticated PUT /api/location/{id} broad request body with id, address fields, coordinates, and timezone is replaced by PATCH /api/location/{id} / route name UpdateLocation. The route ID is authoritative and UpdateLocationDto no longer includes id; request bodies contain nullable property groups fullName, address, postcode, country, city, latitude, longitude, and timezone. Clearable latitude, longitude, and timezone values use OptionalUpdate<T>, so clients can explicitly clear a value with { "hasValue": true, "value": null }. PATCH requires If-Match with the current LocationDto or LocationListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateLocationAsync(Guid id, UpdateLocationDto body, string? if_Match = null, ...). Existing locations receive a non-empty locations.concurrency_stamp and audit timestamp/user columns through migration AddLocationAuditAndConcurrencyStamp. Old PUT /api/location/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking LocationRoom update contract: the old authenticated PUT /api/locationroom/{id} broad request body with id, locationId, room name, slug, description, capacity, and sort order is replaced by PATCH /api/locationroom/{id} / route name UpdateLocationRoom. The route ID is authoritative and UpdateLocationRoomDto no longer includes id; request bodies contain nullable property groups location, name, slug, description, capacity, and sortOrder. Clearable slug, description, and capacity values use OptionalUpdate<T>, and parent location changes are rejected when the target location is not in the same tenant as the room. PATCH requires If-Match with the current LocationRoomDto or LocationRoomListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateLocationRoomAsync(Guid id, UpdateLocationRoomDto body, string? if_Match = null, ...). Old PUT /api/locationroom/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were 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 by PATCH /api/organization/{id} / route name UpdateOrganization. The route ID is authoritative and UpdateOrganizationDto contains nullable property groups fullName, websiteUrl, email, country, city, postcode, and address. Clearable websiteUrl.value uses OptionalUpdate<string?>, so clients can explicitly clear the URL with { "hasValue": true, "value": null }. PATCH requires If-Match with the current OrganizationDto or OrganizationListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateOrganizationAsync(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 at PUT /api/organization/{id}/approval-status. Old PUT /api/organization/{id} profile clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking Group profile/hierarchy update contract: the old authenticated PUT /api/group/{id} broad request body with fullName, description, parentOrganizationId, and parentGroupId is replaced by PATCH /api/group/{id} / route name UpdateGroup. The route ID is authoritative and UpdateGroupDto contains nullable property groups fullName, description, parentOrganization, and parentGroup. Clearable description.value, parentOrganization.value, and parentGroup.value use OptionalUpdate<T>, so clients can explicitly clear text or parent relationships with { "hasValue": true, "value": null }. PATCH requires If-Match with the current GroupDto or GroupListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateGroupAsync(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 at PUT /api/group/{id}/approval-status. Old PUT /api/group/{id} profile clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking Event update contract: the old authenticated PUT /api/event/{id} draft update body is replaced by PATCH /api/event/{id} / route name UpdateEvent. The route ID is authoritative and UpdateEventDto now 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 use OptionalUpdate<T>, so clients can distinguish omitted fields from explicit clears. PATCH requires If-Match with the current event concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateEventAsync(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. Old PUT /api/event/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking EventSession update contract: the old authenticated PUT /api/eventsession/{id} broad body is replaced by PATCH /api/eventsession/{id} / route name UpdateEventSession. The route ID is authoritative and UpdateEventSessionDto now 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 use OptionalUpdate<T>, including explicit schedule clears and Islamic-aspect clears. PATCH requires If-Match with the current session concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateEventSessionAsync(Guid id, UpdateEventSessionDto body, string? if_Match = null, ...). Session create-draft, schedule, publish, archive, cancel, and complete remain separate lifecycle commands. Old PUT /api/eventsession/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were 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 by PATCH /api/eventagendaitem/{id} / route name UpdateEventAgendaItem. The route ID is authoritative and UpdateEventAgendaItemDto now contains nullable property groups event, title, description, schedule, location, room, kind, and sortOrder. Clearable description, location, room, and kind values use OptionalUpdate<T>, so clients can explicitly clear a value without relying on ambiguous JSON nulls. PATCH requires If-Match with the current EventAgendaItemDto or EventAgendaItemListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateEventAgendaItemAsync(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. Old PUT /api/eventagendaitem/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking EventDay update contract: the old authenticated PUT /api/eventday/{id} broad body with id, eventId, local date, label, description, banner text/image, publication state, sort order, and day-scope registration fields is replaced by PATCH /api/eventday/{id} / route name UpdateEventDay. The route ID is authoritative and UpdateEventDayDto now contains nullable property groups event, localDate, label, description, bannerText, bannerImage, publication, sortOrder, and registration. Clearable label, description, bannerText, and bannerImage values use OptionalUpdate<T>, so clients can explicitly clear values without ambiguous JSON nulls. PATCH requires If-Match with the current EventDayDto or EventDayListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateEventDayAsync(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. Old PUT /api/eventday/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking EventSeries update contract: the old authenticated PUT /api/eventseries/{id} broad body with body id, title, description, slug, featured image, and publication fields is replaced by PATCH /api/eventseries/{id} / route name UpdateEventSeries. The route ID is authoritative and UpdateEventSeriesDto no longer includes id; request bodies contain nullable property groups title, description, slug, featuredImage, and publication. Clearable description, slug, and featuredImage values use OptionalUpdate<T>, so clients can explicitly clear text or image references without ambiguous JSON nulls. PATCH requires If-Match with the current EventSeriesDto or EventSeriesListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateEventSeriesAsync(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. Old PUT /api/eventseries/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were 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 by PATCH /api/eventregistration/{id} / route name UpdateEventRegistration. The route ID is authoritative and UpdateEventRegistrationDto no longer includes id; request bodies contain nullable property groups user, session, intent, approvalStatus, and atprotoRecord. Clearable intent, approvalStatus, and atprotoRecord values use OptionalUpdate<T>, so clients can explicitly clear relationship/status fields without ambiguous JSON nulls. PATCH requires If-Match with the current EventRegistrationDto or EventRegistrationListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateEventRegistrationAsync(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-empty event_registrations.concurrency_stamp through migration AddEventRegistrationConcurrencyStamp. Old PUT /api/eventregistration/{id} clients receive 405 Method Not Allowed. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were regenerated through the documented API/NSwag workflow.

  • Breaking EventSessionLanguage update contract: session-language relationship updates are now exposed as PATCH /api/eventsessionlanguage/{id} / route name UpdateEventSessionLanguage. The route ID is authoritative and UpdateEventSessionLanguageDto contains nullable relationship groups session and language; each present group must include its required non-nullable ID. PATCH requires If-Match with the current EventSessionLanguageDto or EventSessionLanguageListDto concurrencyStamp; stale stamps fail as a concurrency conflict, and weak or missing ETags fail validation. Generated clients must call UpdateEventSessionLanguageAsync(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-empty event_session_languages.concurrency_stamp through migration AddEventSessionLanguageConcurrencyStamp. Target milestone: API version 0.1 development mainline; no compatibility window for pre-v1 clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were 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 / generated ImportEventAsync; 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 are CreateDraftEventSessionAsync, ScheduleEventSessionAsync, PublishEventSessionAsync, CancelEventSessionAsync, CompleteEventSessionAsync, and ArchiveEventSessionAsync. EventSessionDto and EventSessionListDto now expose nullable schedule/local projection fields plus isScheduled, concurrencyStamp, eventSessionStatusId, eventSessionStatusMasterCode, and eventSessionStatusFullName. Old clients that treated startTime/endTime as always present must handle null for 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 version 0.1 development mainline; no compatibility window for pre-v1 generated clients. Contract evidence: schemas/openapi.json, docs/API_CONTRACT_INVENTORY.md, and Explore.Blazor.Client/Clients/EventApiClient.g.cs were 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-refund recovery for definitive provider blocks, plus event-level refund/material-change campaign progress and resume recovery. Buyer-refunded totals follow main provider proof while Refunded requires 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.

Historical Baseline (v0.1.0)

  • 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.