Merge NEXT: planning, generation map and portable configuration on top of the OIDC access model

NEXT predates the access work, so the identity layer follows master: access comes from
configured groups, instance administrators replace the site administration, and accounts are
created by SSO or by an administrator. Kept from NEXT: manual publication with the Review
changes panel and shared generator drafts, portable import/export (meguri/v2), the layered
generation map with binding provenance, JSM provider changes, contact/approval semantics and
the account-owned Person. Dropped: site administration, local sign-up, and the mandatory
unique email with its contact/account migrations. The manual-publication migration is
renumbered m_0010 after m_0009_access.

Accounts without an email still get a Person in every workspace they reach, a changed sign-in
email updates the account's person, and an account that loses access keeps its linked person so
schedules stay valid. Instance administrators may use Sync and shared drafts.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Aleksandr BogomiakovandClaude committed 2026-10-07 16:06:10 +01:00
commit 38105c65c2
57 files changed
+6890 -431

No files matched your search

+22 -9
View File
@@ -45,20 +45,25 @@ impl Permission {
pub struct User {
#[serde(default)]
pub language: Option<String>,
pub id: Uuid,
/// Canonical account-owned person; email is required by account persistence.
#[serde(flatten)]
pub person: Person,
pub username: String,
pub display_name: String,
pub country: String,
/// None follows the browser timezone; this never changes scheduling rules.
pub timezone: Option<String>,
#[serde(default)]
pub email: Option<String>,
#[serde(default)]
pub instance_admin: bool,
#[serde(default)]
pub has_password: bool,
}
impl std::ops::Deref for User {
type Target = Person;
fn deref(&self) -> &Person {
&self.person
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Member {
pub user: User,
@@ -191,9 +196,7 @@ impl WorkspaceDocument {
)?;
validate_name(&person.display_name)?;
ensure(
person
.user_id
.is_none_or(|id| id == person.id && members.contains(&id)),
person.user_id.is_none_or(|id| id == person.id),
crate::i18n::text("err_choose_a_user_who_has_access_to_this_workspace_41cf15"),
)?;
ensure(
@@ -450,7 +453,8 @@ impl WorkspaceDocument {
} = &self.integration
{
ensure(
Uuid::parse_str(cloud_id).is_ok() && Uuid::parse_str(team_id).is_ok(),
Uuid::parse_str(cloud_id).is_ok()
&& Uuid::parse_str(team_id.strip_prefix("og-").unwrap_or(team_id)).is_ok(),
crate::i18n::text("err_jsm_cloud_and_team_ids_must_be_uuids_f75b5f"),
)?;
ensure(
@@ -726,3 +730,12 @@ pub struct Timeline {
pub to: DateTime<Utc>,
pub assignments: Vec<Assignment>,
}
/// A compact representation of a verified calendar-day cycle, independent of providers.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecurringRotation {
pub start: DateTime<Utc>,
pub end: DateTime<Utc>,
pub shift_days: u16,
pub participants: Vec<Uuid>,
}