Commit Graph
2011 Commits
Author SHA1 Message Date
Jesse Hallam 27a5abe2d4 Log an error instead of refusing to start on unsupported Postgres, Elasticsearch, and OpenSearch versions (#37929) 2026-08-12 16:55:41 -04:00
Edgar Bellot MicóandClaude Sonnet 5 d0be8f408e MM-70240: Adjust post and thread payload sanitization (#37920)
* MM-70216: Adjust post and thread payload sanitization

* MM-70216: Preserve MM blocks actions in thread test fixture

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 15:07:08 -04:00
Devin Binnie bf0f9de078 [MM-70189] Add operators for CIDR and version checks to the simple policy editor (#37918) 2026-08-12 16:25:48 +00:00
Ben SchumacherandClaude 523292f081 Remove unused context import left behind by UserStore.Get migration (#37921)
PR #37646 (Migrate UserStore Get to request context) migrated
context.Background() call sites to request.CTX but left the now-dead
"context" import in place, assuming other usages in the same files
would keep it alive. A concurrently merged sibling PR (#37637,
GetAllProfilesInChannel migration) removed those other usages first,
so by the time #37646 landed on top of the moved master tip the
import had no remaining references, breaking go vet/build on master.

Affected: server/channels/store/sqlstore/user_store.go,
server/channels/store/storetest/user_store.go,
server/channels/store/storetest/mocks/UserStore.go,
server/channels/store/localcachelayer/user_layer.go(_test.go),
server/channels/app/post_persistent_notification.go

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-12 10:07:43 +00:00
9f0ae6a220 [MM-70222] Migrate UserStore Get to request context (#37646)
* Migrate UserStore Get to request context

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Clean up context imports after UserStore Get migration

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix UserStore Get test context fallout

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Update app interfaces for request-aware user get

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Update UserStore Get test fakes

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Thread request context through user get callers

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Use email service logger for batching context

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Pass request context to trial license lookup

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Pass request context to buildShownByList

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Pass request context to buildFavoritedByList

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Add request context to PromoteGuestToUser

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Pass request context to trial and demote flows

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Use request context in AddUserToTeam lookup

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Use plugin request context for trial license

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Use request contexts throughout team user lookups

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Document future GetUser request context migration

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
2026-08-12 09:33:46 +02:00
270a503054 [MM-70225] Migrate Store.GetDiagnostics to request.CTX (#37635)
* Migrate store diagnostics to request context

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Keep store context rule only in server AGENTS

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-08-12 09:03:09 +02:00
265f1509fa [MM-70223] Migrate GetAllProfilesInChannel to request context (#37637)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
2026-08-12 09:02:22 +02:00
Edgar Bellot MicóandBill Gardner 8ce3c54a5e MM-70016: Fix edge case in team invitation handling (#37741)
* MM-70016: Fix edge case in team invitation handling

* Add test coverage for guest magic link invitation token

---------

Co-authored-by: Bill Gardner <billg@wavearts.com>
2026-08-10 23:06:24 +02:00
Devin Binnie a6d008c5c2 [MM-70188] Convert the os_platform session attribute to a select field (#37901) 2026-08-10 16:03:20 -04:00
Edgar Bellot Micó 9cf617a14b MM-70072: Fix role validation for channel and team member updates (#37791)
* MM-70072: Fix role validation for channel and team member updates

* MM-70072: Assert specific error ID in guest/admin role regression tests

Make the new regression tests assert the specific error ID returned for
the guest+admin role combination, rather than only checking for a
generic bad-request status.
2026-08-10 15:40:13 -04:00
Edgar Bellot MicóandClaude Sonnet 5 f1e13cf62e MM-70040: Tighten team search filter combination logic (#37749)
* MM-70025: Fix team search filter combination logic

See MM-70025 for more details.

* MM-70025: Strengthen team search filter tests

Address CodeRabbit review feedback on PR #37749.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 16:47:27 +02:00
cursor[bot] d04687af22 Fix flaky TestNewSyncsMarkdownMaxLenWithMaxPostSize (#37830)
Automatic Merge
2026-08-10 12:00:10 +03:00
a2a4903293 [MM-62445] Allow changing a team's name (slug) via mmctl team rename (#37169)
* Allow renaming a team's name (slug) via the team update API

The team update API path is sanitized and intentionally ignores the
Name field, so the previously orphaned RenameTeam app method (which no
longer persisted the Name change) is now wired into the updateTeam
handler and fixed to persist the new slug and emit a team update event.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Add --name flag to mmctl team rename

Allow changing a team's name (the URL slug) via mmctl team rename. The
command now accepts --name and/or --display-name, and at least one must
be provided.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Polish team rename error message and fix err shadow

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Strengthen team rename tests

Add regression coverage that the rename path cannot leak non-renamable
fields (email/type), that invalid and duplicate renames leave the slug
unchanged, and a unit case for renaming name and display name together.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Address PR feedback: 1 answered, 1 resolved, 0 declined

* Remove unused App.RenameTeam method

The rename path now goes through App.UpdateTeam (via the team update API
and mmctl), leaving App.RenameTeam orphaned. Remove it and update the
name-occupied error source label to UpdateTeam.

* Return non-not-found errors from team name lookup

Only treat store.ErrNotFound from GetByName as an available slug; any
other lookup failure is now returned instead of silently allowing the
rename to proceed. Addresses CodeRabbit review feedback on the team
update path.

* Remove low-value TestUpdateTeamNameLookupError per review feedback

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-08-10 09:11:17 +02:00
Harshil Sharma 351b4f9686 Updated order of validation in getFile API (#37843)
* Updated order of validation in getFile API

* Lint fix
2026-08-10 09:47:08 +05:30
68535c13bc [MM-70130] Add permanent banner for non-production developer license keys (#37846)
* [MM-70130] Add permanent banner for non-production developer license keys

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* [MM-70130] Update non-production license banner text

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-07 16:44:46 +00:00
bf194bc9d4 MM-57390: Return error when inviting deactivated users to a team (#37096)
* Return error when inviting deactivated users to team

When a user account is deactivated, attempts to invite them to a team
via email should return an error rather than attempting to send the
invitation email.

Added deactivated user checks in:
- InviteNewUsersToTeamGracefully
- InviteNewUsersToTeam
- InviteGuestsToChannelsGracefully
- InviteGuestsToChannels
- localInviteUsersToTeam (mmctl path)

For each invite email, the code now calls GetUserByEmail and checks
whether the returned user has DeleteAt != 0 (deactivated). If so, the
invite is rejected with api.team.invite_members.account_deactivated.app_error.

Fixes MM-57390

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Add missing deactivated-user invite tests for CI coverage

Add tests for InviteGuestsToChannels (non-graceful) and
localInviteUsersToTeam (graceful and non-graceful) to satisfy
PR test analysis requirements.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Handle GetUserByEmail errors in deactivated-user invite checks

Add IsDeactivatedUserEmail helper that distinguishes user-not-found
from lookup failures, and use it across invite paths to avoid
silently proceeding when email lookups fail.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Extract checkForDeactivatedInvites helper to DRY up invite paths

* Tighten checkForDeactivatedInvites doc comment

* Fix govet err shadow in InviteGuestsToChannels

Reuse the existing err from prepareInviteGuestsToChannels instead of
redeclaring it when calling checkForDeactivatedInvites, which was
failing check-style (govet shadow). Apply the same pattern in
InviteNewUsersToTeam for consistency.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* chore: retrigger CI and CodeRabbit review

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Reuse CheckForDeactivatedInvites in local invite path

Export the helper and replace the duplicated non-graceful deactivated
email loop in localInviteUsersToTeam so it shares the same logic as
InviteNewUsersToTeam and InviteGuestsToChannels.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* chore: retrigger CodeRabbit after quiet period

Co-authored-by: mattermost-code <matty-code@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-08-07 11:58:34 -04:00
Edgar Bellot MicóandClaude Opus 5 04a0efba6f MM-70115: Update account type switch handling (#37861)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:53:30 +02:00
318fd81259 Fix flaky TestComplianceStore/postgres/MessageExport_UntilUpdateAt (#37856)
Use the team-name generator that excludes reserved route prefixes; random IDs can begin with a reserved prefix and make Team.Save fail before the export assertions run.

Tests-only change.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-08-06 11:35:49 -04:00
Christopher Poile eed994d106 [MM-70114] Fix flaky channel guard broadcast test (#37847) 2026-08-06 10:43:18 -04:00
4eac9292c0 [MM-69587] Remove CustomProfileAttributes feature flag (#37389)
* [MM-69587] Remove CustomProfileAttributes feature flag

Remove the CustomProfileAttributes feature flag and all associated
conditional gating, leaving custom profile attributes permanently
enabled (still Enterprise-license gated).

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-69587] Update admin sidebar snapshots for permanent CPA

User Attributes now appears based solely on license tier since the
feature flag no longer gates it.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Update channel_header snapshot styled-components icon hash after master merge

Align pending-join-requests snapshot with the Icon-clPswv hash used by all
other ChannelHeader snapshots; CI received this after merging discoverable
channels from master.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* chore: retrigger CI after unrelated Postgres shard 1 flake

Server CI Postgres (shard 1) failed on TestCheckLdapUserPasswordConcurrency
(mock DoLogin panic under concurrent On/Called) and a one-shot
TestLicenseFromBytesEnvironmentMismatch mismatch that passed on re-run.
Neither touches CustomProfileAttributes removal; empty commit to retrigger.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-08-05 16:28:54 -03:00
Miguel de la CruzandMiguel de la Cruz 36af1ee5fc Post attributes feature flag group (#37829)
* Add post attribtues feature flag and helpers

* Adds post attributes group registration

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
2026-08-05 14:58:53 +02:00
Jesse Hallam 61bc7f18e4 [MM-69647] Remove CloudDedicatedExportUI feature flag and dead code (#37836)
The CloudDedicatedExportUI flag (default false, shipped v9.7.0) has been
off everywhere since it shipped. It gated the Cloud Enterprise System
Console "Export Storage" admin UI and the accompanying Test Connection
resolution that picked the ExportDriverName/Export* credential fields
over the primary file-store fields. Per the decision recorded on
MM-69647, remove the feature.

Because App.UseExportFileStore() has always returned false (the flag
never flipped on), simply dropping the flag term would have left
IsCloud() && DedicatedExportStore -- silently activating the never-
exercised Export* resolution for cloud servers. Instead revert the
MM-68854 dispatch entirely and lock in the long-standing primary-field
behavior:

- Server: remove UseExportFileStore()/ResolvedFileStoreDriverName(),
  collapse CheckMandatoryS3Fields/CheckMandatoryAzureFields and
  TestFileStoreConnectionWithConfig to the primary file-store fields,
  and restore testFileStore's primary DriverName dispatch. Drop the
  obsolete TestFileStoreTestConnection test.
- Webapp: remove the "Export Storage" admin console section, its
  en.json strings, and the admin_sidebar fixtures/snapshot entry.
- Remove the struct field, its SetDefaults entry, and the e2e mirror.

The underlying FileSettings.DedicatedExportStore config and Export*
fields are kept -- they remain a live config-file/env feature used by
bulk and compliance exports, independent of the removed UI.
2026-08-04 19:38:47 -04:00
Nick MisasiandCursor Agent c7eff70026 ABAC: plugin-keyed resource types, trusted plugin PAP/CEL APIs, and AuthZEN-style decision API (#37509)
* MM: add v0.5 plugin access control policy model, registry, and decision outcomes

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM: add plugin access control PDP/PAP app-layer methods with fail-closed semantics

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM: expose plugin access control API surface (EvaluateAccessControl + PAP/CEL methods)

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM: add store-layer round-trip tests for v0.5 plugin access control policies

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM: extract plugin access control app code into plugin_access_control.go

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM: add atomic type-guarded AccessControlPolicyStore.DeleteIfType

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM: plugin PAP hardening — atomic typed delete, indistinguishable 404s, audit every attempt

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM: close plugin Get-by-ID TOCTOU via GetPolicyOfType; stamp save audit operation at entry

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Fix gob RPC poisoning from native attribute select options

NativeUserAttributeFields stored bool-select options as
[]map[string]string inside PropertyField.Attrs (map[string]any). gob
requires concrete types inside interface values to be registered, and
[]map[string]string is not registered in client_rpc.go, so encoding the
GetAccessControlFieldsAutocomplete reply failed and net/rpc shut down
the shared plugin API connection, breaking every subsequent plugin API
call.

Build the options from gob-registered containers ([]any/map[string]any)
instead; JSON output is byte-identical. Add gob round-trip regression
tests covering every plugin access control API reply payload: the
autocomplete response including native attribute fields (fails against
the old code), policies with JSON-decoded Props, visual AST condition
values of every runtime shape, expression check errors, query users
responses, and evaluation decisions.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Move plugin access control gob-safety tests into their own file

Pure move: plugin_access_control_test.go crossed 1000 lines; the gob-safety
helper and TestPluginAccessControlGobSafety now live in
plugin_access_control_gob_test.go, unchanged.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM: resolve plugin policy existence when ABAC is unavailable (Option B)

Every evaluation-impossible branch of EvaluatePluginAccessRequest
(service nil / unlicensed / flag off / user load or subject build
failure / evaluator infra error / unknown outcome) now performs a raw
open-core store read on the already-validated resource ID: no stored
row returns no_policy so the caller can safely apply legacy behavior;
any stored row (with a Warn on a foreign-type anomaly) or a failed
read returns unavailable so the caller must fail closed. This lets the
plugin drop its local policy index entirely.

Strengthens the EvaluateAccessControl doc contract accordingly and
reworks the fail-closed test matrix with with/without-row splits per
branch, a foreign-type-row case, a store-read-error case on the store
mock, and passthrough rows pinning that the fallback read never runs
when the evaluator answers.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Clarify EvaluateAccessControl failure-mapping doc

A failure with a definitive store miss maps to no_policy under the
Option B semantics, so 'failures never map to allow or no_policy' was
inaccurate. State precisely: never allow; no_policy only on positively
determined non-existence; deny for defensive failures on a resolved
policy; unavailable otherwise.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Tighten ABAC comments

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Document AccessDecision.Outcome as plugin-API-only

The evaluator populates Outcome on every lane, but the only production
reader is the app layer's EvaluatePluginAccessRequest, which maps it
into PluginAccessControlDecision; core channel/team enforcement reads
the collapsed Decision bool alone. State that on the field.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retrigger CI to rebuild enterprise image with updated enterprise branch

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Document that only the evaluator's plugin lane sets AccessDecision.Outcome

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: key plugin resource types as plugin_id:type and drop the static registry

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: replace AccessDecision.Outcome with the AuthZEN decision context reason

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: drop type-scoped policy get/delete and check the type in the app layer

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: compare plugin policy type ownership exactly instead of case-insensitively

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: bound the whole policy type to the Type column width

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: require an allow before treating a decision as the no-policy fallback

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: treat a colliding foreign-type policy row as no_policy, not a deny

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: gate plugin policy reads on a raw store read before normalization

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: pin policy type immutability on save in the store tests

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: reuse a single unavailable-error constructor in the existence fallback

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: re-check plugin policy ownership on the normalized get read

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ABAC: confirm plugin policy ownership before surfacing a normalization error

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-04 09:59:48 -04:00
Pablo Vélez 7130ae598f MM-70054 - Enable Team Membership ABAC feature flag by default (#37781)
Automatic Merge
2026-08-04 09:30:09 +03:00
Jesse Hallam ae0bec4d67 Add MFI plugin signature public key behind feature flag (#37793)
* Add MFI plugin signature public key stub behind feature flag

* Replace MFI plugin signing key stub with actual public key

* Add federal-signed test plugin fixtures and verify against MFI key

* Rename federal test plugin fixtures to match mfiPluginPublicKey

* Test verifyPlugin's feature-flagged MFI key path
2026-08-03 11:57:16 -03:00
cursor[bot] c17064f72c Fix flaky TestImportValidateDirectPostImportData (#37795)
Automatic Merge
2026-08-02 01:00:09 +03:00
Pablo Vélez 5152df2418 Mm 70056 missing info banner team abac team admin (#37790)
* [MM-70056] Return imported parent policies so team admins see the system policy banner

* [MM-70055] Resolve parent policy expressions for team admins in the mode-flip modal

* [MM-70057] Let team admins read team sync jobs for their own team policy
2026-07-31 20:25:32 +02:00
Alejandro García Montoro 3019460ac5 MM-69445: Markdown fixes (#37388)
* Cap markdown block nesting depth

blockQuoteStart and listStart recursed once per nested '>' or
list marker on a line, guarded only by an indentation check that
bounds CommonMark indentation, not nesting count. A single
crafted line (e.g. "- " repeated tens of thousands of times)
forced recursion and slice-copy work that grew super-linearly
with nesting depth, letting an authenticated user burn a
disproportionate amount of server CPU per request.

Thread a depth counter through blockStart, blockStartOrParagraph,
blockQuoteStart, and listStart, and stop opening new nested
blocks past maxNestingDepth. This bounds nesting whether it is
built by one line or accrued across many, and caps the recursion
and copy cost to a small constant regardless of input size.

Also apply the existing maxLen byte cap directly inside Parse(),
not just Inspect(), so any caller of Parse is protected
uniformly.

* Add tests for markdown block nesting depth cap

Cover the new maxNestingDepth guard: rejecting a well-formed
block quote/list item once the depth limit is reached, capping
recursion for a single deeply-nested line, and honoring depth
already accrued by open ancestor blocks. Also cover Parse()'s
byte-length cap and add benchmarks showing the nesting-depth
cases now scale roughly linearly with input size instead of
super-linearly.

* Reject oversized posts before markdown parsing

createPost, updatePost, and patchPost ran
PostWithProxyRemovedFromImageURLs, which parses the raw message,
before the message-length check inside Post.IsValid ever ran.
An oversized message paid the full parsing cost even though it
was always going to be rejected.

Add rejectOversizedMessage, mirroring the rune-count check in
Post.IsValid, and call it in all three handlers before any
markdown processing touches the message.

* Add tests for oversized post/patch rejection

Cover the rejectOversizedMessage check added for createPost,
updatePost, and patchPost: a message over MaxPostSize is
rejected with a 400 in all three handlers before it reaches
markdown processing.

* Sync markdown parse cap with real max post size

Parse and Inspect capped input at a hardcoded ~128KB, assuming a
64KB max post size. The real configured limit comes from the
posts.message column and can be raised past that default, so on
installations with an enlarged column, markdown parsing (mentions,
links, formatting) was silently skipped for legitimately-sized
posts.

Have the markdown package pull its cap from a function registered
via SetMaxPostSizeFunc, defaulting to the old conservative constant
only if nothing was ever registered. PlatformService.New registers
PlatformService.MaxPostSize itself once the store is ready, so the
cap always reflects the real limit regardless of which code path
parses markdown first, and the store layer stays unaware that
markdown parsing exists.

* Add tests for markdown max post size sync

Cover SetMaxPostSizeFunc raising the cap and being called on every
MaxLen invocation. Also verify, end to end against a real database,
that PlatformService.New wires markdown.MaxLen to the real
configured max post size.

* Fix tests

* Address review comments

- Simplify setting the maximum number of runes for a post
- Simplify comments
- Rename variables and functions to reflect whether they represent bytes
  or runes.
2026-07-31 17:05:13 +00:00
Ben CookeandMattermost Build 466b5bb783 redact logs (#37757)
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-30 23:12:52 -04:00
rogetiandMatt Rogers c6db95f35d Preserve relative redirect_to on root SiteURL (#37725)
* Preserve relative redirect_to on root SiteURL

fullyQualifiedRedirectURL cleaned an empty SiteURL path to "." and required
the redirect target to have that as a prefix, so on a root SiteURL every
same-origin relative redirect (e.g. the /oauth/authorize resume after SSO
login) was discarded and only the site origin returned. Normalize an empty
prefix path to "/". Add root-SiteURL cases to TestFullyQualifiedRedirectURL.

Signed-off-by: Matt Rogers <mrogers@palantir.com>

* Use segment-aware path check for redirect prefix

Address review: replace the empty-prefix normalization with pathWithinPrefix,
which compares on path segments. This fixes the root SiteURL case (empty path)
and also stops a non-root prefix like /mm from matching /mmfoo, and preserves
same-origin absolute targets with only a query/fragment (?foo=bar, #section).

Signed-off-by: Matt Rogers <mrogers@palantir.com>

* Add /mmfoo regression test for segment-aware prefix check

Signed-off-by: Matt Rogers <mrogers@palantir.com>

---------

Signed-off-by: Matt Rogers <mrogers@palantir.com>
Co-authored-by: Matt Rogers <mrogers@palantir.com>
2026-07-30 16:38:21 -04:00
Pablo Vélez 5ef9e70d95 Mm 69832 abac review nits (#37640)
* MM-69831 - Add configurable interval for the ABAC membership sync schedulers

* MM-69832 - Hoist the ManageSystem permission check in createAccessControlPolicy

* MM-69832 - Add policy-scoped search term to the ABAC team invite candidate endpoint

* MM-69832 - Switch strict ABAC invite picker to server-side policy-scoped typeahead

* MM-69832 - Rename the ABAC invite candidate search param to term for consistency

* MM-69832 - Gate ABAC invite full-name term search behind ShowFullName for non-admins

* MM-69832 - Add API and jest coverage for the ABAC invite term search and full-name privacy gate

* MM-69832 - Clean up comments
2026-07-30 15:17:18 +02:00
Jesse Hallam d4d216e93e Add ClusterInterface.Shutdown to surface skipped cluster sends (#37753)
* Add ClusterInterface.Shutdown to surface skipped cluster sends

* Defer cluster interface shutdown so it runs on every exit path
2026-07-30 14:48:59 +02:00
Devin Binnie b021e5be06 [MM-69734] Add no session data, ensure session attributes are not leaked through evaluation trace to non sysadmins (#37600) 2026-07-30 08:42:38 -04:00
Alejandro García MontoroandMattermost Build 9230eb6f6a MM-69725: Propagate context through Extractor.Extract using new mattermost/pdf fork (#37579)
* Propagate context through Extractor.Extract

Adds ctx context.Context to the Extractor interface and all
implementations so callers can cancel in-flight extractions.

pdfExtractor passes the context down to GetPlainText(ctx), which
checks ctx.Done() at each page boundary. When extractWithTimeout
fires (deadline exceeded or parent cancel), it cancels the derived
context via defer cancel(), and the PDF goroutine stops at the next
page rather than running to completion, eliminating the heap
growth that caused OOM on complex documents.

For non-context-aware extractors (docconv, plain, archive) the
context is accepted but not yet acted on; they continue to run
to completion on a detached goroutine after the timeout, as before.

The call site in channels/app/file.go passes rctx.Context() so
request cancellation (e.g. HTTP disconnect) also propagates.

* Use new mattermost/pdf fork

* Update notice reflecting the new fork

* Fail archiveExtractor.Extract early on ctx errors

* Bump mattermost/pdf to cd8a834041c4

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-28 17:29:36 +02:00
Nick MisasiandCursor Agent 6fefcc80a4 MM-67336: Request structured JSON output for AI message rewrites (#37581)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-28 10:13:16 -04:00
Ibrahim Serdar Acikgoz f6c1459ebf MM-69612: Add opt-in EnableAuditLogging setting for ABAC (#37322) 2026-07-28 13:12:09 +02:00
Ibrahim Serdar Acikgoz d33ad5a7e9 [MM-69810] Update golang.org/x/image dep (#37595) 2026-07-28 13:11:38 +02:00
Pablo Vélez 1dacc2d514 Mm 69830 abac membership messaging fixes (#37618)
* MM-69828 - Fix ABAC team Access-tab stuck-public cards, parent-policy mode-flip count

* Add fallback handling for parent-policy fetch failure in AccessTab component

* MM-69829 - Show Attribute Based indicator for policy-governed teams in admin Teams list

* MM-69829 - Clear stale navigation-block flag when opening a membership policy in the editor

* MM-69829 - Save cleanly when removing a team membership policy instead of prompting to re-apply

* MM-69829 - Count qualifying team members correctly in the apply-policy confirmation modal

* fix failing test

* do not show apply policy confirmation modal on policy unlink

* Refactor TableEditor state management and integrate deleteAccessControlPolicy action

* fix ci linter for playwright test

* adjust e2e to the new flow - no policy/rule , no blocking, clean state abac off

* MM-69830 - Return an OK body when deleting an access policy so rule removal saves cleanly

* MM-69830 - Soften the self-exclusion message on public teams to reflect advisory enforcement

* MM-69830 - Soften membership-policy notices to advisory copy on public teams

* MM-69830 - Uncheck auto-add when the last team membership rule is removed

* MM-69830 - Warn that removing all rules drops attribute enforcement instead of showing the save-count modal

* MM-69830 - Dispatch the deleted policy id so removing team rules doesn't crash the reducer

* MM-69830 - Skip the self-exclusion block on public teams instead of rewording it

* MM-69830 - Use advisory wording in the save-rules confirmation on public teams

* MM-69830 - Use advisory auto-add descriptions on public teams

* MM-69830 - Block switching a team to private when the admin would be self-excluded by the rules

* fix prettier issues and adjust comments

* MM-69830 - Assert advisory confirmation copy for team admins on public teams

* MM-69830 - Show a saving state on the membership save panel while a confirmed save runs

* MM-69830 - Save immediately when confirming the switch-to-private mode flip

* MM-69829 - Surface delete failures and page through all members in the ABAC team save flow

* MM-69830 - Align the remove-all-rules confirmation with performSave's parent-import check

* MM-69830 - Drop an unnecessary cast on the team policy result and memoize the self-exclusion modal handler
2026-07-25 09:39:56 +02:00
Pablo VélezandMattermost Build 1cc20031fe MM-69857 - Keep parent imports and team scope system-managed on the policy update endpoint (#37625)
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-24 13:31:31 +02:00
Devin BinnieandMattermost Build f06971b657 Fix two Session Attribute issues (#37620)
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-22 17:53:53 +00:00
Scott Bishel a8f55e5327 [MM-69885] Allow integrations to update mm_blocks_actions on their own posts (#37583)
* [MM-69885] Allow integrations to update mm_blocks_actions on their own posts

* prune mm_blocks_actions on post updates to match remaining buttons
2026-07-22 09:01:45 -06:00
25f3a75cb7 [MM-67163] Scheduled Recaps (#35495)
* docs: map existing codebase with focus on recaps

- STACK.md - Technologies and dependencies
- ARCHITECTURE.md - System design and patterns
- STRUCTURE.md - Directory layout
- CONVENTIONS.md - Code style and patterns
- TESTING.md - Test structure
- INTEGRATIONS.md - External services
- CONCERNS.md - Technical debt and issues

* docs: initialize project

Scheduled AI Recaps — enable recurring scheduled recaps instead of only manual one-off summaries

* chore: add project config

Mode: yolo
Depth: standard
Parallelization: enabled

* docs: complete domain research for scheduled recaps

Research files:
- STACK.md: Job server patterns, ScheduledPosts reference implementation
- FEATURES.md: Table stakes vs differentiators, anti-features to avoid
- ARCHITECTURE.md: Component boundaries, data flow, 5-phase build order
- PITFALLS.md: Timezone, cluster, schema critical pitfalls
- SUMMARY.md: Synthesized findings and roadmap implications

Key findings:
- Use ScheduledPosts polling pattern, not JobServer schedulers
- Store user intent + pre-computed NextRunAt for efficient polling
- Leverage existing recap worker, extend with time_period support
- Critical: Get timezone/schema right in Phase 1 to avoid migrations

* docs: define v1 requirements

35 requirements across 5 categories:
- Scheduling (7)
- Management (8)
- Backend Infrastructure (10)
- Frontend Wizard (7)
- Frontend Scheduled Tab (7)

Topic-based recaps deferred to v2

* docs: create roadmap (5 phases)

* docs(01): capture phase context

Phase 01: Database Foundation
- OpenCode discretion on implementation patterns
- Implicit requirements from downstream phases noted

* docs(01-database-foundation): research phase domain

Phase 1: Database Foundation
- Analyzed existing Recap and ScheduledPost patterns
- Documented schema design for recurring schedules
- Identified timezone/DST handling approach using IANA zones
- Catalogued store interface patterns
- Documented common pitfalls and anti-patterns

* docs(01): create phase plan for database foundation

Phase 01: Database Foundation
- 2 plans in 2 waves
- Wave 1: Model + migration (01-01-PLAN.md)
- Wave 2: Store layer + tests (01-02-PLAN.md)
- Ready for execution

* feat(01-01): create ScheduledRecap model with DST-aware NextRunAt computation

- Add ScheduledRecap struct with all required fields for schedule configuration
- Add day-of-week bitmask constants matching Go's time.Weekday (Sunday=0)
- Add channel mode constants (specific, all_unreads)
- Add time period constants (last_24h, last_week, since_last_read)
- Implement ComputeNextRunAt with timezone-aware scheduling using time.LoadLocation
- Implement IsValid for input validation
- Add PreSave/PreUpdate lifecycle methods
- Add Auditable method for audit logging

* feat(01-01): create database migration for ScheduledRecaps table

- Create ScheduledRecaps table with all required columns
- Add index for user queries (idx_scheduled_recaps_user_id)
- Add index for scheduler polling (idx_scheduled_recaps_next_run_at)
- Add composite index for efficient scheduler query (idx_scheduled_recaps_enabled_next_run)
- Add index for user + soft delete queries (idx_scheduled_recaps_user_delete)
- Add down migration to drop all indexes and table

* test(01-01): add unit tests for ScheduledRecap with DST edge cases

- Test day-of-week bitmask constants and operations
- Test ComputeNextRunAt for Monday-only, weekday, and every-day schedules
- Test timezone handling - different timezones produce different UTC millis
- Test DST spring forward edge case (March 2024) - Go normalizes non-existent times
- Test DST fall back edge case (November 2024) - Go uses first occurrence
- Test error cases: invalid timezone, invalid time format, zero days
- Test IsValid method for all validation rules
- Test PreSave and PreUpdate lifecycle methods
- Test Auditable method returns expected fields
- Fix ComputeNextRunAt to validate time format using regex before parsing

* docs(01-01): complete ScheduledRecap model and migration plan

Tasks completed: 3/3
- Task 1: Create ScheduledRecap model with constants and NextRunAt computation
- Task 2: Create database migration for ScheduledRecaps table
- Task 3: Add unit tests for ComputeNextRunAt with DST edge cases

SUMMARY: .planning/phases/01-database-foundation/01-01-SUMMARY.md

* feat(01-02): add ScheduledRecapStore interface to store.go

- Add ScheduledRecapStore interface with CRUD operations (Save, Get, Update, Delete)
- Add query operations (GetForUser, GetDueBefore)
- Add state update methods (UpdateNextRunAt, MarkExecuted, SetEnabled)
- Register ScheduledRecap() method in main Store interface

* feat(01-02): create SqlScheduledRecapStore implementation

- Implement CRUD operations (Save, Get, Update, Delete with soft delete)
- Implement GetForUser with pagination for user's scheduled recaps
- Implement GetDueBefore for scheduler polling query
- Implement efficient state updates (UpdateNextRunAt, MarkExecuted, SetEnabled)
- Handle JSON serialization/deserialization of ChannelIds array
- Follow existing patterns from recap_store.go

* feat(01-02): register ScheduledRecapStore in SqlStore

- Add scheduledRecap field to SqlStoreStores struct
- Initialize newSqlScheduledRecapStore in NewSqlStore
- Add ScheduledRecap() accessor method to SqlStore

* test(01-02): add comprehensive tests for ScheduledRecapStore

- Test CRUD operations (Save, Get, Update, Delete)
- Test GetForUser with pagination
- Test GetDueBefore scheduler query filtering
- Test state update methods (UpdateNextRunAt, MarkExecuted, SetEnabled)
- Test ChannelIds JSON serialization (array, empty, nil)
- All 13 test cases pass

* docs(01-02): complete ScheduledRecapStore plan

Tasks completed: 4/4
- Add ScheduledRecapStore interface to store.go
- Create SqlScheduledRecapStore implementation
- Register ScheduledRecapStore in SqlStore
- Create comprehensive store tests

SUMMARY: .planning/phases/01-database-foundation/01-02-SUMMARY.md

* fix(01): regenerate store mocks for ScheduledRecapStore

* docs(01): complete Database Foundation phase

Phase 1: Database Foundation
- 2 plans executed (model + store)
- 3 requirements complete (INFRA-01, INFRA-02, INFRA-10)
- Goal verified ✓

* docs(03): research phase scheduler integration domain

Phase 03: Scheduler Integration
- Standard stack identified (Mattermost job system)
- Architecture patterns documented (Scheduler + Worker pattern)
- Cluster-safe execution via leader-only scheduling
- Pitfalls catalogued (duplicate jobs, race conditions)
- Code examples from existing codebase patterns

* docs(02): create phase 2 API layer plans

Phase 02: API Layer
- 2 plans in 2 waves
- Plan 01 (Wave 1): App layer methods for CRUD + pause/resume
- Plan 02 (Wave 2): API handlers, routes, params, audit events
- Ready for execution

* feat(02-01): create App layer CRUD methods for ScheduledRecap

- Add CreateScheduledRecap with session-based userId, validation, NextRunAt computation
- Add GetScheduledRecap to retrieve by ID
- Add GetScheduledRecapsForUser with pagination
- Add UpdateScheduledRecap with NextRunAt recomputation when enabled
- Add DeleteScheduledRecap for soft delete
- Add PauseScheduledRecap to disable without deleting
- Add ResumeScheduledRecap with NextRunAt recomputation before enabling
- Regenerate store layer files for ScheduledRecapStore interface

* docs(02-01): complete App layer CRUD methods plan

Tasks completed: 3/3
- Regenerate store mocks (already complete from Phase 1)
- Create App layer file with CRUD methods
- Verify app layer interfaces (no interface file exists)

SUMMARY: .planning/phases/02-api-layer/02-01-SUMMARY.md

* feat(02-02): add audit event constants for scheduled recaps

- AuditEventCreateScheduledRecap for recap configuration creation
- AuditEventGetScheduledRecap for viewing single recap
- AuditEventGetScheduledRecaps for listing user recaps
- AuditEventUpdateScheduledRecap for configuration updates
- AuditEventDeleteScheduledRecap for recap deletion
- AuditEventPauseScheduledRecap for pausing execution
- AuditEventResumeScheduledRecap for resuming execution

* feat(02-02): add ScheduledRecapId to params and context

- Add ScheduledRecapId field to Params struct
- Parse scheduled_recap_id from URL path variables
- Add RequireScheduledRecapId validation method

* feat(02-02): add route registration for scheduled recaps

- Add ScheduledRecaps and ScheduledRecap routes to Routes struct
- Initialize route prefixes in Init function
- Add InitScheduledRecap call in initialization

* feat(02-02): create API handlers for scheduled recaps

- InitScheduledRecap registers all 7 API routes
- createScheduledRecap validates required fields and creates recap
- getScheduledRecap retrieves with authorization check
- getScheduledRecaps lists user's recaps with pagination
- updateScheduledRecap updates with ownership verification
- deleteScheduledRecap soft deletes with authorization
- pauseScheduledRecap disables execution with ownership check
- resumeScheduledRecap re-enables with NextRunAt recomputation
- All handlers include audit logging and feature flag check

* docs(02-02): complete API handlers plan

Tasks completed: 4/4
- Add audit event constants for scheduled recaps
- Add ScheduledRecapId to params and context
- Add route registration in api.go
- Create API handler file

SUMMARY: .planning/phases/02-api-layer/02-02-SUMMARY.md

* docs(02): complete API Layer phase

Phase 2: API Layer
- 2 plans executed in 2 waves
- 16/16 must-haves verified
- INFRA-05 through INFRA-09 complete
- Ready for Phase 3: Scheduler Integration

* docs(03): create phase plan for scheduler integration

Phase 03: Scheduler Integration
- 2 plans in 2 waves
- Wave 1: Job constant, scheduler, worker
- Wave 2: Job registration, App method
- Ready for execution

* fix(03): revise plan 03-02 Task 2 for worker context compatibility

- CreateRecapFromSchedule now creates recap directly via store
- Uses sr.UserId instead of rctx.Session().UserId (unavailable in worker)
- Creates JobTypeRecap job directly instead of delegating to CreateRecap
- Updated key_links to reflect store and job linkage

* docs(04): capture phase context

Phase 04: Scheduled Tab
- Implementation decisions documented
- Phase boundary established
- Figma references captured (123:62940, 123:19772)

* feat(03-01): add JobTypeScheduledRecap constant

- Add JobTypeScheduledRecap constant with value 'scheduled_recap'
- Add to AllJobTypes slice for job type validation

* feat(03-01): create ScheduledRecap scheduler

- Add Scheduler struct wrapping PeriodicScheduler
- 1-minute polling interval (SchedulerPollingInterval constant)
- Enabled when cfg.FeatureFlags.EnableAIRecaps is true
- ScheduleJob polls GetDueBefore for due recaps
- Creates job with CreateJobOnce for deduplication
- Job data: scheduled_recap_id, user_id, channel_ids, agent_id

* feat(03-01): create ScheduledRecap worker

- Define AppIface interface with CreateRecapFromSchedule method
- Use SimpleWorker pattern following recap/worker.go pattern
- Enabled when cfg.FeatureFlags.EnableAIRecaps is true
- Extract job data: scheduled_recap_id, user_id, channel_ids, agent_id
- Verify ScheduledRecap exists and is enabled before execution
- Call app.CreateRecapFromSchedule to create the actual recap
- Compute next run time using sr.ComputeNextRunAt
- Call MarkExecuted to update LastRunAt, NextRunAt, RunCount atomically
- Disable non-recurring schedules after execution

* docs(03-01): complete job system components plan

Tasks completed: 3/3
- Add JobTypeScheduledRecap constant
- Create scheduler implementation
- Create worker implementation

SUMMARY: .planning/phases/03-scheduler-integration/03-01-SUMMARY.md

* docs(04): create phase 4 plans - Scheduled Tab UI

Phase 04: Frontend - Scheduled Tab
- 4 plans in 4 waves
- Wave 1: TypeScript types + Client4 API methods
- Wave 2: Redux layer (action types, actions, reducer, selectors)
- Wave 3: ScheduledRecapItem component (card UI with toggle, menu)
- Wave 4: Scheduled tab integration with human verification

Covers requirements TAB-01 through TAB-07 and MGMT-01 through MGMT-08

* feat(03-02): add ScheduledRecap job registration in initJobs

- Register JobTypeScheduledRecap with worker and scheduler
- Import scheduled_recap package for job components
- Worker uses App interface for CreateRecapFromSchedule
- Scheduler polls for due recaps at 1-minute intervals

* feat(03-02): implement CreateRecapFromSchedule App method

- Create Recap from ScheduledRecap configuration
- Use sr.UserId instead of session (worker context has no session)
- Create recap record directly via store
- Create JobTypeRecap job to trigger processing
- Handle both specific channels and all_unreads mode

* docs(03-02): complete app integration plan

Tasks completed: 3/3
- Add job registration in initJobs
- Implement CreateRecapFromSchedule App method
- Verify full integration compiles

SUMMARY: .planning/phases/03-scheduler-integration/03-02-SUMMARY.md

* docs(phase-3): complete scheduler integration phase

* feat(04-01): add ScheduledRecap TypeScript types

- Add ScheduledRecap type matching Go model fields
- Add ScheduledRecapInput type for create/update operations
- Types exported via @mattermost/types/recaps

* feat(04-01): add Client4 scheduled recap route and imports

- Add getScheduledRecapsRoute() method returning /scheduled_recaps endpoint
- Import ScheduledRecap and ScheduledRecapInput types

* feat(04-01): add Client4 scheduled recap API methods

- createScheduledRecap: POST /scheduled_recaps
- getScheduledRecaps: GET /scheduled_recaps (paginated)
- getScheduledRecap: GET /scheduled_recaps/:id
- updateScheduledRecap: PUT /scheduled_recaps/:id
- deleteScheduledRecap: DELETE /scheduled_recaps/:id
- pauseScheduledRecap: POST /scheduled_recaps/:id/pause
- resumeScheduledRecap: POST /scheduled_recaps/:id/resume

* docs(04-01): complete TypeScript types and Client4 methods plan

Tasks completed: 3/3
- Add ScheduledRecap TypeScript type
- Add Client4 scheduled recap route helper
- Add Client4 scheduled recap API methods

SUMMARY: .planning/phases/04-scheduled-tab/04-01-SUMMARY.md

* feat(04-02): add scheduled recap action types

- GET_SCHEDULED_RECAPS_REQUEST/SUCCESS/FAILURE
- RECEIVED_SCHEDULED_RECAP and RECEIVED_SCHEDULED_RECAPS
- PAUSE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
- RESUME_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
- DELETE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE

* feat(04-02): add scheduled recap Redux actions

- getScheduledRecaps: fetches paginated scheduled recaps
- pauseScheduledRecap: pauses a scheduled recap
- resumeScheduledRecap: resumes a paused scheduled recap
- deleteScheduledRecap: deletes a scheduled recap

* feat(04-02): add scheduled recaps to reducer

- Add scheduledRecaps to RecapsState type
- Handle RECEIVED_SCHEDULED_RECAP for single recap
- Handle RECEIVED_SCHEDULED_RECAPS for bulk updates
- Handle DELETE_SCHEDULED_RECAP_SUCCESS for removal

* feat(04-02): add scheduled recap selectors

- getScheduledRecapsState: base selector for raw state
- getAllScheduledRecaps: returns all scheduled recaps as array
- getActiveScheduledRecaps: filters enabled, non-deleted recaps
- getPausedScheduledRecaps: filters disabled, non-deleted recaps
- getScheduledRecapById: returns single recap by ID

* feat(04-02): update GlobalState type for scheduled recaps

- Import ScheduledRecap type from recaps
- Add scheduledRecaps field to recaps entity state

* docs(04-02): complete Redux store and actions plan

Tasks completed: 5/5
- Add scheduled recap action types
- Add scheduled recap Redux actions
- Add scheduled recaps to reducer
- Add scheduled recap selectors
- Update GlobalState type for scheduled recaps

SUMMARY: .planning/phases/04-scheduled-tab/04-02-SUMMARY.md

* feat(04-03): add i18n strings for scheduled recap UI

- Add scheduled tab label
- Add active/paused toggle states
- Add run stats strings (last run, run count, never run, next run)
- Add toast messages for pause/resume/delete
- Add kebab menu labels (edit, delete)
- Add delete confirmation modal strings
- Add empty state strings (title, description, cta)
- Add day formatting strings (weekdays, weekend, everyday, individual days)
- Add schedule format string

* feat(04-03): create useScheduleDisplay hook for schedule formatting

- Add bitmask constants matching Go model (Sun=1, Mon=2, etc.)
- formatDaysOfWeek: smart groupings (Every day, Weekdays, Weekends) or comma-separated
- formatTimeOfDay: locale-appropriate 12/24hr time from HH:MM
- formatSchedule: combines days and time with i18n format string
- formatNextRun: smart relative formatting (Today, Tomorrow, Day name, Date)
- formatLastRun: formatted date or 'Never run'
- formatRunCount: pluralized run count

* feat(04-03): create ScheduledRecapItem component

- Render card with title and schedule pattern subtitle
- Show next run time when schedule is active
- Toggle between Active/Paused states with pause/resume actions
- Run stats (last run, run count) appear on hover
- Kebab menu with Edit and Delete options
- Delete confirmation modal with FormattedMessage
- Use useScheduleDisplay hook for all formatting

* feat(04-03): add ScheduledRecapItem styles

- Card with border, radius, and hover state
- Flexbox layout with title/subtitle and actions
- Title with truncation (ellipsis) for long names
- Subtitle with metadata separator styling
- Run stats with opacity transition on hover
- Toggle button min-width for consistent sizing
- Kebab menu button hover state

* docs(04-03): complete ScheduledRecapItem component plan

Tasks completed: 4/4
- Add i18n strings for scheduled recap UI
- Create useScheduleDisplay hook for schedule formatting
- Create ScheduledRecapItem component
- Add ScheduledRecapItem styles

SUMMARY: .planning/phases/04-scheduled-tab/04-03-SUMMARY.md

* feat(04-04): create ScheduledRecapsEmptyState component

- Empty state displays when no scheduled recaps exist
- Shows illustration with icons, title, description
- CTA button to create first recap
- Supports disabled state when agents bridge is disabled

* feat(04-04): create ScheduledRecapsList component

- Renders empty state when no scheduled recaps exist
- Maps over scheduled recaps to render ScheduledRecapItem
- Passes edit and create handlers through to children

* feat(04-04): add Scheduled tab to main Recaps component

- Add Scheduled tab after Unread and Read tabs
- Fetch scheduled recaps on mount with getScheduledRecaps
- Display ScheduledRecapsList when on scheduled tab
- Wire up edit handler (opens create modal - Phase 5 adds pre-fill)
- Import scheduled_recap_item.scss for styling

* style(04-04): add SCSS styles for scheduled recaps

- Add .scheduled-recaps-list styles (flex column, centered, gap)
- Add .scheduled-recaps-empty-state styles (centered, illustration, text)
- Consistent with existing recap UI styling patterns

* docs(04-04): complete Scheduled tab integration plan

* docs(04): update STATE.md for phase 4 completion

* docs(04): complete Scheduled Tab phase

Phase 4: Scheduled Tab
- 4 plans executed across 4 waves
- 15/15 requirements verified
- Human verified UI works correctly

* docs(05): capture phase context

Phase 05: Enhanced Wizard
- Implementation decisions documented
- Phase boundary established

* docs(05): add research hints for component discovery

* docs(05): research phase domain for enhanced wizard

Phase 05: Enhanced Wizard - Frontend Implementation
- Standard stack identified (existing codebase components)
- Architecture patterns documented (multi-step modal, bitmask days)
- Pitfalls catalogued (timezone, validation, edit mode)
- Code examples from codebase referenced

* docs(05): create phase plan for enhanced wizard

Phase 05: Frontend - Enhanced Wizard
- 6 plans in 3 waves
- Wave 1: Redux actions, DayOfWeekSelector
- Wave 2: ScheduleConfiguration, Run once toggle
- Wave 3: Modal integration, Edit wiring
- Ready for execution

* feat(05-01): add action type constants for create/update scheduled recap

- CREATE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
- UPDATE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE

* feat(05-02): create DayOfWeekSelector component

- Bitmask-based day selection matching server model
- Monday-first ordering for work schedule intuition
- XOR toggle for clean state management
- aria-pressed accessibility support

* feat(05-01): add createScheduledRecap async action

- Takes ScheduledRecapInput parameter
- Calls Client4.createScheduledRecap
- Dispatches RECEIVED_SCHEDULED_RECAP on success
- Follows existing pauseScheduledRecap pattern

* feat(05-02): add DayOfWeekSelector styles

- Flexbox layout with 8px gap between buttons
- 40x40px day buttons with hover states
- Selected state uses button-bg color
- Error and disabled state styling

* feat(05-01): add updateScheduledRecap async action

- Takes id and ScheduledRecapInput parameters
- Calls Client4.updateScheduledRecap
- Dispatches RECEIVED_SCHEDULED_RECAP on success
- Follows existing action patterns

* docs(05-02): complete DayOfWeekSelector plan

Tasks completed: 2/2
- DayOfWeekSelector component with bitmask state
- Styled button group with toggle/hover/error states

SUMMARY: .planning/phases/05-enhanced-wizard/05-02-SUMMARY.md

* docs(05-01): complete Redux actions for scheduled recaps plan

Tasks completed: 3/3
- Add action type constants for create/update scheduled recap
- Add createScheduledRecap async action
- Add updateScheduledRecap async action

SUMMARY: .planning/phases/05-enhanced-wizard/05-01-SUMMARY.md

* feat(05-04): add run once toggle to RecapConfiguration

- Add runOnce, setRunOnce, and isEditMode props to Props type
- Import Toggle component
- Add run once toggle section at bottom of Step 1
- Toggle hidden when isEditMode is true
- Include descriptive text below toggle

* feat(05-03): create ScheduleConfiguration component for Step 3

- Add day-of-week selection using DayOfWeekSelector
- Add time picker with 30-minute intervals and locale-aware formatting
- Add time period dropdown (Previous day, Last 3 days, Last 7 days)
- Add custom instructions textarea with 500 char limit
- Add next run preview with timezone support
- Use getCurrentTimezone selector for user timezone

* feat(05-04): add run once toggle styles

- Add .run-once-group with top separator border
- Style toggle and label with proper alignment
- Add description text with left margin for alignment
- Use consistent spacing and typography

* feat(05-03): add Step 3 schedule configuration styles

- Add step-three layout with vertical flex and gap
- Add form-group styling with label and error states
- Add next-run-preview styling with background and subtle text
- Add textarea overrides for custom instructions input

* docs(05-04): complete run once toggle plan

Tasks completed: 2/2
- Add run once toggle to RecapConfiguration
- Add run once toggle styles

SUMMARY: .planning/phases/05-enhanced-wizard/05-04-SUMMARY.md

* docs(05-03): complete ScheduleConfiguration plan

Tasks completed: 2/2
- Create ScheduleConfiguration component
- Add Step 3 styles to SCSS

SUMMARY: .planning/phases/05-enhanced-wizard/05-03-SUMMARY.md

* feat(05-05): add schedule state and edit mode props to modal

- Add editScheduledRecap prop for edit mode detection
- Add schedule state (daysOfWeek, timeOfDay, timePeriod, customInstructions)
- Add runOnce state and validation state (daysError, timeError)
- Add useEffect to pre-fill form in edit mode
- Import createScheduledRecap, updateScheduledRecap actions
- Import ScheduleConfiguration component and getCurrentTimezone selector

* feat(05-06): update handleEditScheduledRecap to pass scheduled recap to modal

- Find scheduled recap by ID from scheduledRecaps array
- Pass editScheduledRecap via dialogProps to CreateRecapModal
- Early return if scheduled recap not found

* feat(05-05): update step navigation for run once and schedule flows

- Update handleNext to clear validation errors on navigation
- Update handlePrevious to clear validation errors on navigation
- Update getTotalSteps for run once (2-3 steps) vs scheduled (always 3)
- Update getActualStep for proper step indicator mapping

* feat(05-05): update renderStep for schedule vs run once flows

- Pass runOnce, setRunOnce, and isEditMode props to RecapConfiguration
- Show ChannelSummary for run once mode at step 3
- Show ScheduleConfiguration for scheduled mode at step 3
- Pass all schedule state props to ScheduleConfiguration component

* feat(05-05): update handleSubmit for immediate and scheduled recaps

- Add schedule field validation for non-run-once mode
- Dispatch createRecap for run once mode (existing behavior)
- Dispatch updateScheduledRecap for edit mode
- Dispatch createScheduledRecap for new scheduled recaps
- Navigate to ?tab=scheduled after creating/editing scheduled recap
- Add proper error messages for schedule validation failures

* feat(05-05): update modal header and button text for edit mode

- Update canProceed to validate schedule fields in step 3
- Add getConfirmButtonText helper for context-aware button text
- Show 'Start recap' for run once, 'Save changes' for edit mode
- Show 'Create schedule' for new scheduled recaps
- Update headerText to show 'Edit your recap' in edit mode

* docs(05-05): complete wizard integration plan

Tasks completed: 5/5
- Add schedule state and edit mode props
- Update step navigation for run once and schedule flows
- Update renderStep for schedule vs run once flows
- Update handleSubmit for immediate and scheduled recaps
- Update modal header and button text for edit mode

SUMMARY: .planning/phases/05-enhanced-wizard/05-05-SUMMARY.md

* fix(05-06): JSON.stringify body in scheduled recap API calls

createScheduledRecap and updateScheduledRecap were passing objects
directly to doFetch body, causing '[object Object]' to be sent instead
of JSON. Fixed to match createRecap pattern.

* fix(05-06): align time period values with server model

Frontend was using 'last_3_days' and 'last_7_days' but server expects
'last_24h', 'last_week', and 'since_last_read'. Updated options to match.

* fix(05-06): remove duplicate border on custom instructions textarea

- GenericModal adds a border to all .form-control elements
- Input widget's Input_fieldset already provides a border container
- This caused a double-border visual glitch on the textarea
- Added border: none to the inner textarea to fix the issue

* fix(05-06): reserve space for next run preview to prevent modal height jump

- Always render next-run-preview container (previously conditionally rendered)
- Use visibility:hidden instead of not rendering when no preview available
- Add non-breaking space placeholder to maintain consistent element height
- Prevents jarring visual jump when user selects a day of the week

* fix(05-06): remove border/background from next-run-preview

- Remove padding, border-radius, and background-color from .next-run-preview
- Style as plain text with subtle color and smaller font size
- Keep margin for appropriate spacing from time selector

* fix(05-06): use abbreviated timezone in next recap preview

- Use Intl.DateTimeFormat with timeZoneName: 'short' to get timezone
  abbreviation (e.g., EST, PST, EDT) instead of full label
- Remove unused getCurrentTimezoneLabel selector import
- Preview now shows 'Monday at 9:00 AM (EST)' instead of
  'Monday at 9:00 AM ((UTC-05:00) Eastern Time (US & Canada))'

* fix(05-06): prevent modal height jump when next run preview appears

- Move next-run-preview inside time-selection-group as helper text
- Add min-height: 16px to reserve space when preview is hidden
- Use visibility: hidden instead of display: none for consistent height
- Reduce step-three gap from 20px to 16px for better spacing
- Add margin-bottom: 0 to form-group to override default spacing

* fix(05-06): add section titles and fix spacing in schedule configuration

- Add section titles per Figma design (Heading 100 style):
  - 'When would you like your summary sent?' as main header
  - 'On which days should your recap run?' for days section
  - 'At what time?' for time section
  - 'Select a time period for your recap to cover' for time period
  - 'Additional instructions for {agentName}' for custom instructions
- Pass agentName prop from parent to show selected agent name
- Fix spacing: reserve space for next-run preview with container
  to prevent time period section from jumping when preview appears
- Update SCSS with schedule-section groups and proper spacing

* fix(05-06): remove duplicate title and fix subtitle-dropdown spacing

- Remove 'When would you like your summary sent?' duplicate title
- Add scoped CSS rule for 12px total spacing between subtitle and dropdown

* fix(05-06): use standard Toggle without text labels for active/paused state

- Remove onText/offText props from Toggle component
- Add ariaLabel for accessibility (describes toggle state and action)
- Update SCSS to remove min-width constraint that was for text display
- Navigation to scheduled tab after creating scheduled recap already works correctly

* fix(05-06): toggle color and tab navigation after creating scheduled recap

- Use btn-toggle-primary class for scheduled recap toggle to display proper button-bg color
- Add useQuery hook to read tab query parameter from URL
- Sync activeTab state with URL tab parameter to enable navigation after modal close

* fix(05-06): sync tab state with URL bidirectionally

- Add handleTabChange callback that updates both state and URL
- Use history.replace() to update URL without polluting browser history
- Remove tab param from URL when switching to 'unread' (default tab)
- Simplify URL sync useEffect to always update from tabParam
- This enables proper navigation after creating scheduled recaps

* fix(05-06): fix navigation to scheduled tab after creating scheduled recap

- Replace useRouteMatch() with getCurrentRelativeTeamUrl selector
- Modal was using route match which returned wrong URL context (modal is rendered at root level)
- Use team selector to get correct team URL for navigation
- Update test to remove unnecessary useRouteMatch mock

* fix(05-06): sort scheduled recaps by newest first

- Update getAllScheduledRecaps selector to sort by create_at descending
- Follows same pattern as other recap selectors (getUnreadRecaps, getReadRecaps)
- Derived selectors (getActiveScheduledRecaps, getPausedScheduledRecaps) inherit sort order

* docs(05-06): complete edit wiring and UI polish plan

* docs(phase-05): complete Enhanced Wizard phase

Phase 5: Enhanced Wizard
- 6 plans executed across 3 waves
- 13 requirements verified
- Multi-step wizard for creating/editing scheduled recaps
- Run once and scheduled flows
- Full edit mode with pre-fill
- Extensive UI polish based on human feedback

All 39 requirements complete. Milestone ready for audit.

* docs(v1): milestone audit complete - all requirements satisfied

- 39/39 requirements verified
- 5/5 phases passed
- 100% cross-phase integration
- 5/5 E2E flows complete
- 2 minor tech debt items (non-blocking)

* chore: remove .planning from git tracking

- Add .planning to .gitignore
- Remove .planning files from git index (kept locally)
- Planning files are for local development only

* feat(06-01): add AIRecapSettings and RecapLimitSettings structs

- RecapLimitSettings with 7 limit fields (recaps/day, scheduled, channels, posts, tokens, posts/day, cooldown)
- AIRecapSettings with master toggle and per-limit enforcement toggles
- SetDefaults methods with sensible defaults (10 recaps/day, 5 scheduled, etc.)
- isValid/IsValid validation methods enforcing natural minimums

* feat(06-01): integrate AIRecapSettings into Config struct

- Add AIRecapSettings field to Config struct
- Call AIRecapSettings.SetDefaults() in Config.SetDefaults()
- Call AIRecapSettings.IsValid() in Config.IsValid()

* test(06-01): add tests for AIRecapSettings and RecapLimitSettings

- TestAIRecapSettingsSetDefaults: verifies all defaults match spec
- TestRecapLimitSettingsValidation: verifies validation rejects invalid values
- TestAIRecapSettingsPreservesExistingValues: verifies SetDefaults preserves existing
- TestAIRecapSettingsIsValid: verifies IsValid delegates to DefaultLimits

* feat(06-02): create EffectiveRecapLimits struct

- Add EffectiveRecapLimits struct with 7 resolved limit fields
- Add LimitSource type with system/group/user constants
- Add UnlimitedValue constant (-1) for disabled limits
- Add IsLimitEnabled helper function for enforcement code

* feat(06-02): create GetEffectiveLimits resolution function

- Add GetEffectiveLimits(userID) returning resolved limits for any user
- Resolve limits from AIRecapSettings.DefaultLimits config
- Apply per-limit enforcement toggles (disabled = -1 unlimited)
- Add helper functions getValueOrDefault and getBoolOrDefault
- Structure for Phase 8 group/user resolution with TODOs

* test(06-02): add tests for GetEffectiveLimits function

- TestGetEffectiveLimitsDefaults verifies system defaults returned
- TestGetEffectiveLimitsWithDisabledToggle verifies -1 returned for disabled limits
- TestGetEffectiveLimitsWithCustomDefaults verifies custom config honored
- TestGetEffectiveLimitsAllTogglesDisabled verifies all -1 when all disabled
- TestGetEffectiveLimitsUnlimitedConfigValue verifies -1 config value honored
- TestIsLimitEnabled verifies helper correctly identifies enabled limits

* feat(07-01): add RecapStatusSkipped constant and SkipReason field

- Add RecapStatusSkipped constant for recaps skipped due to limit violations
- Add SkipReasonDailyLimit and SkipReasonCooldown skip reason constants
- Add ScheduledRecapId field to Recap struct for tracking scheduled recaps
- Add SkipReason field to Recap struct for tracking why recap was skipped
- Update Auditable() method to include new fields
- Update recapColumns and recapToMap to include new fields

* feat(07-01): add store interface methods for limit enforcement

- Add CountForUserSince to RecapStore for daily limit enforcement
- Add GetLastCompletedManualRecap to RecapStore for cooldown checking
- Add CountForUser to ScheduledRecapStore for max scheduled recaps limit
- Update mock implementations for both stores

* feat(07-01): implement store methods in SQL stores

- Implement CountForUser in SqlScheduledRecapStore
  - Counts active (non-deleted, enabled) scheduled recaps for a user
- Implement CountForUserSince in SqlRecapStore
  - Counts recaps since timestamp, excluding skipped recaps
- Implement GetLastCompletedManualRecap in SqlRecapStore
  - Returns most recent completed manual recap (no ScheduledRecapId)
  - Returns nil, nil when no manual recap exists

* feat(07-03): add daily limit check to scheduled recap worker

- Add GetEffectiveLimits and GetUser to AppIface for limit checking
- Check MaxRecapsPerDay before executing scheduled recap
- Create skipped recap record when daily limit exceeded
- Use user's timezone for midnight calculation
- Update next run time even when skipping (scheduler moves on)

* feat(07-03): add cooldown check to manual recap creation

- Check CooldownMinutes before allowing manual recap creation
- Return HTTP 429 with retry-after info when cooldown active
- Only checks against completed manual recaps (per CONTEXT.md)
- Failed recaps don't consume cooldown (checks completed only)

* feat(07-03): add i18n messages for cooldown errors

- Add cooldown_active error with retry info template
- Add cooldown_check_failed error message
- Uses "Your organization's policy limits..." pattern

* feat(07-02): add limit checks to CreateScheduledRecap

- Add max scheduled recaps limit check using CountForUser store method
- Add max channels per recap limit check against ChannelIds length
- Return HTTP 400 with clear error messages when limits exceeded
- Uses GetEffectiveLimits for limit resolution (ENF-01, ENF-02, ENF-08)

* feat(07-02): add i18n error messages for scheduled recap limits

- Add max_scheduled_reached message with "Your organization's policy limits..." pattern
- Add max_channels_exceeded message with limit and requested count
- Add count_failed internal error message

* test(07-04): add unit tests for post/token truncation

- Test proportional post distribution across channels
- Test minimum 1 post per channel guarantee
- Test empty channel handling
- Test token estimation (4 chars/token heuristic)
- Test token limit truncation removes from largest channels

Verifies ENF-05, ENF-06 truncation implementation.

* test(07-05): add ENF-07 permission preservation tests

- Add tests verifying over-limit users can view/edit/delete existing recaps
- Tests confirm management operations do NOT check limits (grandfathering)
- Tests confirm creation IS still blocked when over limit
- Add migration 000151 for missing ScheduledRecapId/SkipReason columns

ENF-07: Limits only block creation, not management of existing resources

* feat(08-01): create UnlimitedNumberSetting component

- Number input with Unlimited checkbox for admin console settings
- When checked: disables input and sets value to -1 (unlimited)
- When unchecked: enables input and sets value to defaultValue
- Supports disabled state and setByEnv footer

* test(08-01): add unit tests for UnlimitedNumberSetting

- Tests rendering with numeric and unlimited values
- Tests checkbox toggle behavior (check/uncheck)
- Tests number input changes
- Tests disabled state and setByEnv footer
- Tests custom unlimited label and placeholder
- 11 test cases covering core functionality

* feat(08-02): add Recaps subsection to admin_definition.tsx

- Import UnlimitedNumberSetting component
- Add 'recaps' subsection under site configuration section
- Include master enable toggle for AI Recap Limits
- Add 3 grouped sections: Quota Limits, Content Limits, Time Limits
- Configure all 7 limit settings with proper config keys
- Add AIRecapSettings and RecapLimitSettings TypeScript types
- All settings disabled when master toggle is off

* feat(08-02): add i18n strings for Recaps admin section

- Add admin.sidebar.recaps for navigation
- Add admin.site.recaps for section title
- Add admin.recaps.enable.* for master toggle
- Add admin.recaps.sections.* for section descriptions
- Add admin.recaps.max*.* for all limit field labels/descriptions
- Add admin.recaps.cooldownMinutes.* for time limit settings
- Add admin.recaps.unlimited for checkbox label
- Total: 24 new i18n strings

* style(08-02): remove section comments to fix lint errors

Remove inline comments that triggered lines-around-comment lint rule

* feat(09-01): add RecapLimitStatus model and App layer logic

- Add RecapLimitStatus, DailyUsageStatus, CooldownStatus structs
- Implement App.GetRecapLimitStatus with daily usage count and cooldown calculation

* feat(09-01): add GET /api/v4/recaps/limit_status endpoint

- Register route and handler
- Return structured limit status
- Add error translation

* feat(09-01): add TypeScript types for limit status

- Export RecapLimitStatus and related types

* feat(09-02): add recap limit status redux integration

- Add Client4.getRecapLimitStatus method
- Add Redux action, reducer, and selector for limit status
- Update GlobalState and initial state to include limitStatus

* fix(09-02): update CreateRecapModal error handling and tests

- Check dispatch result.error instead of try/catch to handle server errors
- Display server error message (e.g. policy limits) inline
- Fix TypeScript errors: displayName property and missing props in tests

* feat(09-03): implement user-facing limit status UI

* Verify Phase 9: User-Facing UX

* Remove UAT artifact

* fix: UI/UX issues (badge, toggle, input, tooltip)

* fix: Increase RecapUsageBadge tooltip z-index

* blank lines

* [MM-67163] checkpoint: scheduled recaps feature working

All phases (01-09) complete and verified:
- Database foundation with DST-aware scheduling
- API layer for CRUD operations
- Job scheduler and worker
- Scheduled tab UI with list/create/edit/delete
- Enhanced wizard with schedule configuration
- Config settings and admin console section
- Limit enforcement (daily, cooldown, token, post)
- User-facing limit status badge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [MM-67163] simplify: reduce duplication and fix issues across scheduled recaps

- Extract requireScheduledRecapOwnership helper for 5 API handlers
- Consolidate ResumeScheduledRecap from 4 store calls to 2
- Remove redundant Get() in PauseScheduledRecap
- Extract advanceSchedule helper in worker to deduplicate skip/success paths
- Remove double PreSave() in store Save method
- Remove dead code fallback in GetEffectiveLimits
- Deduplicate ScheduledRecapInput construction in create modal
- Fix missing fetchRecapLimitStatus import (TS error)
- Extract day-of-week bitmask constants to @mattermost/types/recaps
- Fix hardcoded English strings in schedule_display formatNextRun

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address CodeRabbit review feedback on scheduled recaps

Server fixes:
- Gate getRecapLimitStatus with requireRecapsEnabled guard
- Normalize error mapping (404 vs 500) in scheduled recap handlers
- Enforce MaxChannelsPerRecap limit in UpdateScheduledRecap
- Return explicit error for unsupported all_unreads mode in scheduled recaps
- Add compensation logic to clean up orphan recaps on job creation failure
- Fix proportional post truncation to strictly enforce maxPosts cap
- Add missing CountForUser retry wrapper in RetryLayerScheduledRecapStore
- Handle NULL ScheduledRecapId in manual recap cooldown lookup
- Exclude soft-deleted rows in scheduled recap Get query

Frontend fixes:
- Pass isCreationBlocked to ScheduledRecapsList empty state
- Fix same-day nextRunAt mislabeled as "Tomorrow" in schedule display
- Handle thunk error results in scheduled recap item actions
- Replace scheduled recaps map on full refresh instead of merging

Made-with: Cursor

* Handle thunk error results in scheduled recap toggle handler

Check dispatch result for errors in handleToggle to prevent
false-success UI flows when pause/resume operations fail.

Made-with: Cursor

* Fix CI failures: mock store, permissions, migrations, lint, and Playwright config

- Add ScheduledRecap() to storetest.Store mock and retrylayer test setup
- Register sysconsole_read_ai_recaps / sysconsole_write_ai_recaps permissions
- Renumber scheduled_recaps migration from 150→156 and recap_skip_fields from 151→157 to resolve version conflicts
- Fix ESLint errors in recap components (operator-linebreak, import order, labels, headers, etc.)
- Add AIRecapSettings to Playwright default_config.ts

Made-with: Cursor

* Address CodeRabbit Round 2 review feedback

- Add OpenAPI spec for GET /api/v4/recaps/limit_status with schema
  definitions for RecapLimitStatus, EffectiveRecapLimits,
  DailyUsageStatus, and CooldownStatus
- Set ScheduledRecapId when creating recaps from schedules to prevent
  cooldown logic from treating scheduled recaps as manual
- Handle past nextRunAt timestamps in schedule display: show "Yesterday"
  for -1 day and full date for older past dates

Made-with: Cursor

* Add missing variable declarations for AI recaps permissions

Made-with: Cursor

* Fix jsx-max-props-per-line lint errors in schedule_configuration.tsx

Made-with: Cursor

* Fix stylelint property order in recap SCSS files

Made-with: Cursor

* Update admin sidebar snapshots to include Recaps section

Made-with: Cursor

* Fix Go lint issues and add OpenAPI specs for scheduled_recap endpoints

- Use max/min builtins instead of if-statements (modernize/minmax)
- Use range-over-int syntax for for-loops (modernize/rangeint)
- Fix tautological Monday&Monday test (staticcheck/SA4000)
- Add OpenAPI specs for all 7 scheduled_recap API routes
- Add ScheduledRecap model definition to definitions.yaml

Made-with: Cursor

* Regenerate i18n en.json for recap-related strings

Made-with: Cursor

* Fix gofmt indentation in recap.go

Made-with: Cursor

* Fix scheduled recap CI regressions

Align the scheduled recap soft-delete store test with the current Get behavior and add the missing scheduled recap i18n strings so server and enterprise checks stay in sync.

Made-with: Cursor

* Address CodeRabbit review feedback on scheduled recaps

- Use session user ID instead of client-controlled recap.UserId for
  limit enforcement in UpdateScheduledRecap (security hardening)
- Add missing i18n entry for app.recap.fetch_posts.app_error
- All other review comments were already addressed in prior commits

Made-with: Cursor

* Fix schedule configuration import order

Reorder the moment import so the webapp lint job passes again on the scheduled recap PR.

Made-with: Cursor

* Allow selecting "all unreads" recap type when no current unreads exist

With scheduled recaps, users should be able to select "all unreads" even
without current unread channels since unreads will exist when the schedule
runs. The "run once" toggle is now disabled when all unreads is selected
with no current unreads, preserving the pre-scheduling behavior of
preventing an immediate recap with nothing to summarize.

Made-with: Cursor

* Fix indentation in recap_configuration.tsx to satisfy eslint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Recaps test mocks for scheduled recap state.

Keep the webapp test shard green by mirroring the new selectors and mount-time actions used by the Recaps page.

Made-with: Cursor

* Update server/channels/app/recap.go

* Update server/channels/app/recap.go

* Update server/channels/app/recap_limits.go

* Update server/channels/app/recap_limits.go

* Update server/channels/app/scheduled_recap.go

* Update server/channels/app/scheduled_recap.go

* Update webapp/channels/src/components/recaps/scheduled_recaps_empty_state.tsx

* Update server/channels/app/scheduled_recap.go

* Fix gofmt formatting in recap limits

Made-with: Cursor

* Fix recap limits for soft-deleted recaps

Keep deleted recaps in quota and cooldown checks so soft deletion cannot bypass AI usage enforcement.

Made-with: Cursor

* Fix translation

* Fix server check-style: concurrent indexes and lint cleanups

- Use CREATE/DROP INDEX CONCURRENTLY in 000168 scheduled recaps migrations
  (required by mattermost-govet concurrentIndex check).
- gofmt validation constants in scheduled_recap.go.
- Replace string += loops in recap tests with strings.Repeat for modernize linter.

Made-with: Cursor

* Fix 000168 migration: run CONCURRENTLY indexes outside transaction

PostgreSQL rejects CREATE/DROP INDEX CONCURRENTLY inside a transaction.
Morph requires -- morph:nontransactional for these migrations, matching
other index migrations in the repo.

Made-with: Cursor

* Stabilize scheduled Recaps for review

Bring the scheduled Recaps work back into a shippable state by tightening backend scheduling and limit semantics, cleaning up the UI flows, and adding focused Recaps E2E coverage.

Made-with: Cursor

* Fix scheduled recaps lint failures

Made-with: Cursor

* Fix scheduled recap Go lint

Made-with: Cursor

* Fix scheduled recaps Playwright check

Made-with: Cursor

* Sync scheduled recaps i18n catalog

Made-with: Cursor

* Fix server recaps CI checks

Made-with: Cursor

* Stabilize recap server CI setup

Made-with: Cursor

* Recaps: enforce token limit, remove dead truncation code, simplify scheduled worker

- Delete unused multi-channel truncation subsystem (FetchAndTruncatePostsForRecap, truncatePostsProportionally)
- Enforce MaxTokensPerRecap in the live recap path (previously a silent no-op)
- Drop redundant non-atomic daily-limit pre-check in the scheduled worker; rely on the atomic check in CreateRecapFromSchedule
- Disable non-recurring schedules on all terminal paths (including daily-limit skips) via finalizeSchedule
- Trim scheduled-recap job payload to scheduled_recap_id
- Exclude skipped recaps from GetRecapsForUser

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps webapp: format schedule times in the schedule timezone, static day i18n, typed schedule fields

- Display next-run and schedule times using the scheduled recap's timezone (shared schedule_time_format helper) instead of browser-local time
- Replace dynamic day-of-week i18n message IDs with static descriptors so strings are extractable/translatable
- Add ScheduledRecapTimePeriod/ScheduledRecapChannelMode union types
- Use shared Button in the scheduled recaps empty state

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps e2e: cover scheduled edit/delete/all-unreads/empty-state and token-limit enforcement

- Add UI coverage for editing, deleting, and the empty state of scheduled recaps
- Add all-unreads scheduled recap modal flow
- Add immediate-recap token-limit enforcement tests (single and per-channel), validating MaxTokensPerRecap truncation end-to-end
- Extend recaps page object and helpers

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: regenerate store layers/mocks and i18n after cleanup; fix lint

- Regenerate timerlayer/retrylayer/RecapStore mock to canonical order
- Drop orphaned app.recap.fetch_posts.app_error i18n key (removed with dead fetch helper)
- Add missing semicolon in UnlimitedNumberSetting props

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: regenerate default roles permissions and admin sidebar snapshot

- Regenerate Cypress default_roles_permissions fixture to include AI Recaps sysconsole permissions
- Update admin sidebar snapshot for the new Recaps section

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: stabilize flaky cooldown round-up test

The cooldown round-up subtest placed the prior recap 30s before a 2-minute
cooldown, leaving only ~30s of slack before the rounded remaining time would
flip from 2 to 1 minute. Under heavily loaded CI this could intermittently fail
the exact-minute assertion. Move the prior recap to 1s ago so the remaining time
sits near the top of the 2-minute band (~59s slack) while still exercising
ceiling rounding.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: store ScheduledRecap.ChannelIds as jsonb

Postgres is the only supported database, so the prior TEXT+JSON-string
workaround for MySQL compatibility is unnecessary. Store ChannelIds in a
jsonb column and type the model field as model.StringArray, which removes the
bespoke marshal/unmarshal and intermediate scan struct in the store.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: gate recap limit settings on the ai_recaps permission

Add access:"ai_recaps" to DefaultLimits and all RecapLimitSettings fields so a
delegated admin with sysconsole_write_ai_recaps can save the limit values,
instead of falling back to requiring manage_system.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: drop bespoke Users row lock for limit enforcement

The recap limit savers were the only SELECT ... FOR UPDATE in the sqlstore.
Conform to the prevailing pattern (e.g. channel_bookmark_store.Save): enforce
MaxScheduledRecaps / MaxRecapsPerDay with a transactional count + insert and no
row lock, accepting the same best-effort behavior under concurrency as channels,
team members, and bookmarks.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: document SaveOnceByTypeAndData dedup semantics

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: use SERIALIZABLE isolation for limit-check inserts

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: mark orphaned recap skipped when job enqueue fails

When CreateJob fails after the recap row is committed, flag the recap
skipped with reason job_creation_failed instead of leaving it pending.
Skipped recaps are excluded from the daily-limit count, so this frees
the quota slot for a recap that will never run, and keeps CreateRecap
consistent with CreateRecapFromSchedule.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ci: retrigger CI (flaky enterprise npm cache EEXIST)

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ci: retrigger CI (flaky Vet API container init)

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: re-check channel read permission at recap execution time

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* fix scheduled recap job server test setup

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ci: retry flaky webapp test

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ci: retry documentation impact review

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM-67163: Address review feedback: remove unused userID param, return AppError from GetRecapLimitStatus

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM-67163: Bulk channel permission check for recap creation, bounded by channel limit

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* chore: rerun CI

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* chore: rerun CI after network failure

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-21 13:10:25 -04:00
2851af059d Add admin-locked profile fields for email users and pre-provisioned names on invites (#37458)
* Add TeamSettings.LockProfileFieldsForEmailUsers with server-side enforcement

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add API tests for LockProfileFieldsForEmailUsers enforcement

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Hide admin-locked profile fields in user settings and add System Console dropdown

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Support pre-set username and name on team email invites

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add tests for invite profiles; fix resend worker channel-list parsing

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add pre-set profile inputs to member invite modal

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Prefill and lock pre-set username on signup page

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add first/last name editing to System Console user detail and document new setting

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Fix lint issues in invite modal profile inputs

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Fix double outline on invite modal profile inputs inside GenericModal

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Refactor invite emails to InviteEmailData struct and harden invite profile validation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Centralize profile-lock permission exemption in app layer and add config coverage

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Per-field name locking in profile settings, typed lock setting, and shared invite profile helpers

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Add Playwright E2E coverage for locked profile fields and pre-set invite profiles

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Sync playwright package-lock with merged workspace versions

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Restore upstream playwright package-lock (fix npm ci drift)

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Assert invite input cleared instead of chip text after adding email

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Fix invite modal scroll, username error layout, and clipped autocomplete

Keep the footer pinned while tall profile rows scroll, show username
validation full-width after blur, and portal select menus so they are not
clipped by the scroll container.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fixes for autocomplete items not aligning properly

* Make invite autocomplete menu portal opt-in and fix modal chrome

Confine document.body menu portaling to the invite modal via a menuPortal
prop, and restore click-away, slide-in animation, and header alignment for
the scrolling invite modal layout.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix eslint lines-around-comment on menuPortal props

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix stylelint property order in invitation modal SCSS.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix stylelint property order in invitation_modal.scss

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Adapt invite modal E2E to portaled autocomplete menus

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Simplify locked profile invite implementation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Fix invite modal review and E2E feedback

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry flaky enterprise CI

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry Docker image export CI

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Minimize locked profile fields diff

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Fix locked profile E2E documentation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Address minimized test review feedback

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Fix email test whitespace

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry OpenSearch download CI

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry flaky Cypress thread navigation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Preserve legacy invite behavior without profiles

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Clarify invite profile validation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry flaky enterprise E2E

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matthew Birtch <2040554+matthewbirtch@users.noreply.github.com>
2026-07-17 14:29:44 -04:00
Alejandro García Montoro d6d2c50189 Clear session cache on OAuth token refresh (#37459)
newSessionUpdateToken removed the old session row from the DB but
did not clear the in-memory session cache. Because GetSession
returns the cached entry without querying the DB, the rotated
(old) access token remained valid for up to SessionCacheInMinutes
(default 10 min) after rotation.

Add a ClearSessionCacheForUser call after the DB removal, matching
what RevokeAccessToken already does, so the old token is rejected
immediately.

Add a regression test that verifies the old token hits the cache
before refresh and is rejected right after rotation.
2026-07-17 15:03:47 +00:00
Pablo VélezandMattermost Build 3a820143a1 MM 69100 - Team ABAC Membership - members sync and end user surfaces (#37054)
* MM-69063 - Add team ABAC model and constants foundation

* Add team ABAC store EXISTS, channel Type retrofit, policy count split, and index migration

* Add team ABAC app layer: access gate, hydrators, assign/unassign, cleanup, and  GetTeamMembersToRemove store

* Enforce team membership ABAC on join and hide policy governed teams from  non-qualifying users in the directory

* Add team_ids to access policy assign/unassign, expose per-team policy GET,  and support abac_match_only for not_in_team user listing

* Add team ABAC client methods, websocket handler, per-team System Console policy UI, and hide policy-governed teams from non-qualifying users

* Make team ABAC mode-aware: advisory on public teams, strict on private, and surface governed private teams to qualifying users in directory listings

* Flag-gate team ABAC mutation/read APIs and fix policy-save error handling, member-removal limit, team-id  validation, export, and audit cleanup

* coderabbit feedback; Broadcast team policy enforcement updates on policy create/update and activation, not only on delete

* Update team access control policy schema to allow nullable policies and enhance test cases with channel counts

* Enhance access control policy tests to include team policy search alongside channel policy search

* Add team membership access control feature flag to docker-compose generation

* Implement team access control policy checks and refactor related components

* MM-69100 - add team membership ABAC sync worker with mode-aware removal and auto-add

* Add team ABAC removal/auto-add notifications, cascade audit, and team custom-rules save backend

* Add Team Settings team membership tab with custom-rules editor, auto-add toggle, save confirmation, and mode-flip  sync trigger

* Add team access control policy panels, job details team list, and e2e coverage for membership tab and discoverability cards

* ix save double-submit with loading state, team privacy via updateTeamPrivacy, mode-flip count accuracy, sync trigger logic, i18n keys, and e2e test cleanup

* Add team membership policy notices to invite and add-to-team flows, team access control system messages, and a team policy attributes endpoint

* Add team membership recommended tag for qualifying users and team requirements notice in the members modal

* temp

* adjust styling, add new e2e, fix team admin job permissions issues

* e2e clean items created

* fix directory and team integration e2e tests

* Surface team sync results in the Sync Job Details modal via a Teams tab linked from the chained channel job

* code clean up, adjust styling

* Add access control attributes endpoint and enhance E2E tests for team membership

* Enhance team access control by updating permission checks and syncing jobs

* Add API-level enforcement gate tests for ABAC team membership

* Fix formatting in team channel settings component by adding missing semicolon

* Fix formatting in Client4 class by adjusting type annotations for clarity

* revert unwanted changes in package-lock

* Refactor job permission test and simplify error handling in team details component

* Update test assertions and enhance auto-add functionality in team membership tab

* Update onboarding tests to use 'Public Team' card for team access settings

* Refactor AccessSettings and TeamPolicyEditor components to replace allowOpenInviteCheckbox with public and private team buttons, and update access control job dispatching logic.

* Remove redundant state updates for channel assignments in access control policy reducer

* Implement email suppression for team membership notifications and add related tests

* Authorize ABAC team self-join by attribute match instead of the join_private_teams role

* comments clean up

* Gate team privacy type normalization behind active ABAC, keep legacy allow_open_invite-only path  otherwise

* adjust job details styles

* Add team membership policy disconnect confirmation, fix linked-policy affected-member count, and polish job details modal

* Combine custom rules with system policy expressions for accurate confirm counts

* Implement child resource count stamping and deletion gating for access control policies

* Enhance team sync job triggering logic for membership changes without auto-add

* Refactor team privacy handling to align with open-directory model; update comments for clarity on allow_open_invite logic.

* Deduplicate team ABAC sync jobs per policy to prevent concurrent runs emitting duplicate DMs and audit records on HA clusters

* fix linter warnings

* Add confirmation for policy removal in TeamDetails tests

* Update delete policy message to include Teams in the warning

* Add linked teams warning to policy deletion and update translations

* Add Auto-add feature for policy selection and team management

- Enhanced PolicySelectionModal to include an Auto-add checkbox for each policy.
- Updated PolicyList to manage Auto-add state and reflect changes in the UI.
- Modified TeamAccessControl to handle Auto-add functionality and persist changes.
- Added tests to ensure Auto-add behavior works as expected in various scenarios.
- Updated translations for Auto-add related strings.

* Add team membership sync footer and enhance job fetching with policy ID

* Refactor team membership policy handling and sync footer implementation

* Add team sync channel cascade test for private channel membership

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-17 09:55:36 +03:00
Ben Cooke 3b64a2ac84 Property owners for property fields and values, new audit logs for cpa value changes and new pluginapis (#37299) 2026-07-16 18:12:27 -04:00
catalintomai 5f7f967a7d MM-69268 - Add ChannelTypeSpace backing-channel type for Docs (#37321) 2026-07-16 22:34:57 +02:00
Ben SchumacherandClaude Sonnet 5 86888cbac6 [MM-69007] Speed up Support Packet tests by shortening the CPU profile sample (#36761)
* [MM-69007] Add CPU profile duration option to support packet API

Support packet generation always blocks on a 5s CPU profile sample.
Add cpu_profile_duration_seconds to GET /api/v4/system/support_packet
so callers can shorten or skip it; nil keeps the 5s default,
0 skips CPU profile generation, positive values override the duration.
Validated as an integer in [0, 300].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add test for zero CPU profile duration skipping cpu.prof

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add API-level validation tests for cpu_profile_duration_seconds

Cover negative, above-max, non-integer, and clearly out-of-range values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Document cpu_profile_duration_seconds query parameter

Adds the new optional query parameter to the support_packet endpoint
in the API reference spec so the generated docs match the handler
behavior added in this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-69007: Drop HTTP API surface, keep test-only CPU profile speedup

Rescope this PR to not expose CPU profile duration control via the
GET /api/v4/system/support_packet endpoint:
- Remove the cpu_profile_duration_seconds query parameter, its
  validation, and audit metadata from api4/system.go
- Remove the corresponding api4 validation test
- Remove the OpenAPI documentation for the query parameter

model.SupportPacketOptions.CPUProfileDurationSeconds and the
platform.GenerateSupportPacket/getCPUProfile plumbing remain, since
they're what let the platform/app support-packet tests pass a short
duration (1s, or 100ms for TestGetCPUProfile) instead of waiting on
the real 5s default. Clarify the field's doc comment to reflect that
it's for programmatic/test callers only, not the HTTP API.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* MM-69007: Cut CPU profile test duration from 1s to 100ms

Change model.SupportPacketOptions.CPUProfileDuration from *int
(whole seconds, so the minimum non-zero value was 1s) to
*time.Duration, so test callers can pass a sub-second duration.
Update all GenerateSupportPacket test call sites to use a shared
100ms duration instead of 1s, and extract it into a package-level
shortCPUProfileDuration var in each test file to avoid repeating the
literal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* MM-69007: Remove unused SupportPacketOptions JSON plumbing

Neither SupportPacketOptionsFromReader nor the json tags on
IncludeLogs/PluginPackets are used anywhere in this repo -
api4/system.go builds SupportPacketOptions directly from request
form values, never from JSON. Drop the unused JSON tags, decode
helper, and now-unused encoding/json and io imports.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-16 15:48:57 +05:30
Ibrahim Serdar Acikgoz 8d10e91d38 MM-69798: Add AccessControlSettings setting to disable channel access indicators (#37519) 2026-07-16 07:14:23 +02:00
Jesse Hallam 901b4256bd Fix assorted down migrations, add additional testing (#37464) 2026-07-15 07:40:12 -03:00