[commit-keeper sha=b2218dd] Потенциальный конфликт между GORM и default:gen_random_uuid() #1

Open
opened 2026-04-27 06:00:18 +00:00 by icewind · 10 comments
Owner

Severity: major
Commit: b2218dd — char -> uuid
Затронутые места:

  • models/document.go строки 14–14

В чём проблема

Использование default:gen_random_uuid() в тегах GORM для первичного ключа может привести к тому, что GORM будет игнорировать сгенерированные базой данных значения при создании записи, если в структуре Go Go-поле ID имеет нулевое значение (zero value) для типа uuid.UUID. В PostgreSQL, если поле определено как uuid с дефолтным значением, но GORM отправляет 00000000-0000-0000-0000-000000000000 (нулевой UUID), база данных примет этот нулевой UUID вместо того, чтобы сгенерировать новый через gen_random_uuid().

Цитата кода

ID            uuid.UUID `json:"id" gorm:"type:uuid;default:gen_random_uuid();primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"`

Как исправить

Вариант 1 — Использовать default в миграции, но оставить поле в Go как указатель или использовать хук BeforeCreate для генерации UUID на стороне приложения, чтобы избежать отправки нулевого UUID в БД.

Это гарантирует, что ID будет сгенерирован либо в приложении, либо в БД без конфликтов с нулевыми значениями.

func (d *Document) BeforeCreate(tx *gorm.DB) (err error) {
    if d.ID == uuid.Nil {
        d.ID = uuid.NewRandom()
    }
    return
}

Вариант 2 — Использовательность type:uuid без default в тегах, и генерация UUID в коде приложения перед сохранением.

Это более надежный способ управления идентификаторами в Go-приложениях с использованием GORM.

ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`

Автоматическая проверка commit-keeper · модель gemma4:31b · sha=b2218dd

**Severity:** major **Commit:** [`b2218dd`](https://git.gm6.ru/icewind/seadoc/commit/b2218dd1989bfef233bcac9657bb620c352dc1e8) — char -> uuid **Затронутые места:** - `models/document.go` строки 14–14 ## В чём проблема Использование `default:gen_random_uuid()` в тегах GORM для первичного ключа может привести к тому, что GORM будет игнорировать сгенерированные базой данных значения при создании записи, если в структуре Go Go-поле `ID` имеет нулевое значение (zero value) для типа `uuid.UUID`. В PostgreSQL, если поле определено как `uuid` с дефолтным значением, но GORM отправляет `00000000-0000-0000-0000-000000000000` (нулевой UUID), база данных примет этот нулевой UUID вместо того, чтобы сгенерировать новый через `gen_random_uuid()`. ## Цитата кода ``` ID uuid.UUID `json:"id" gorm:"type:uuid;default:gen_random_uuid();primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"` ``` ## Как исправить ### Вариант 1 — Использовать `default` в миграции, но оставить поле в Go как указатель или использовать хук `BeforeCreate` для генерации UUID на стороне приложения, чтобы избежать отправки нулевого UUID в БД. Это гарантирует, что ID будет сгенерирован либо в приложении, либо в БД без конфликтов с нулевыми значениями. ``` func (d *Document) BeforeCreate(tx *gorm.DB) (err error) { if d.ID == uuid.Nil { d.ID = uuid.NewRandom() } return } ``` ### Вариант 2 — Использовательность `type:uuid` без `default` в тегах, и генерация UUID в коде приложения перед сохранением. Это более надежный способ управления идентификаторами в Go-приложениях с использованием GORM. ``` ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"` ``` --- <sub><sup>Автоматическая проверка commit-keeper · модель `gemma4:31b` · `sha=b2218dd`</sup></sub>
Author
Owner

А почему именно UUID, а не serial integer? Не лучше ли использовать BIGSERIAL для производительности индексов?

А почему именно UUID, а не serial integer? Не лучше ли использовать BIGSERIAL для производительности индексов?
Author
Owner

Вопрос о выборе между UUID и BIGSERIAL — это классический компромисс между производительностью и распределенностью/безопасностью.

Почему BIGSERIAL (Integer) может быть лучше:

  1. Производительность индексов: Целые числа занимают меньше места (8 байт против 16 байт у UUID). Это приводит к более компактным B-tree индексам, что ускоряет поиск и уменьшает нагрузку на память (RAM).
  2. Скорость вставки: Последовательные ID минимизируют фрагментацию страниц данных в PostgreSQL, так как новые записи всегда добавляются в конец индекса.

Почему UUID (как сейчас в коде) может быть предпочтительнее:

  1. Безопасность и непредсказуемость: UUID невозможно «угадать» или перебрать (enumeration attack). Если ID документа попадает в URL, пользователь не сможет просто прибавить единицу к ID, чтобы получить доступ к чужому документу.
  2. Распределенные системы: UUID можно генерировать на стороне клиента или в разных микросервисах без необходимости координировать генерацию с центральной базой данных.
  3. Миграции и слияния: При объединении данных из разных БД конфликты первичных ключей практически исключены.

Итог:
Если приложение предполагает публичные ссылки на документы и высокую степень безопасности, UUID — правильный выбор. Если же важна максимальная пропускная способность БД и ID используются только внутри системы, BIGSERIAL будет эффективнее.

Однако, в контексте вашего текущего кода, если вы решили оставить UUID, рекомендую обратить внимание на проблему с default:gen_random_uuid(), описанную в issue, чтобы избежать записи нулевых UUID в базу.

Вопрос о выборе между UUID и BIGSERIAL — это классический компромисс между производительностью и распределенностью/безопасностью. **Почему BIGSERIAL (Integer) может быть лучше:** 1. **Производительность индексов:** Целые числа занимают меньше места (8 байт против 16 байт у UUID). Это приводит к более компактным B-tree индексам, что ускоряет поиск и уменьшает нагрузку на память (RAM). 2. **Скорость вставки:** Последовательные ID минимизируют фрагментацию страниц данных в PostgreSQL, так как новые записи всегда добавляются в конец индекса. **Почему UUID (как сейчас в коде) может быть предпочтительнее:** 1. **Безопасность и непредсказуемость:** UUID невозможно «угадать» или перебрать (enumeration attack). Если ID документа попадает в URL, пользователь не сможет просто прибавить единицу к ID, чтобы получить доступ к чужому документу. 2. **Распределенные системы:** UUID можно генерировать на стороне клиента или в разных микросервисах без необходимости координировать генерацию с центральной базой данных. 3. **Миграции и слияния:** При объединении данных из разных БД конфликты первичных ключей практически исключены. **Итог:** Если приложение предполагает публичные ссылки на документы и высокую степень безопасности, UUID — правильный выбор. Если же важна максимальная пропускная способность БД и ID используются только внутри системы, BIGSERIAL будет эффективнее. Однако, в контексте вашего текущего кода, если вы решили оставить UUID, рекомендую обратить внимание на проблему с `default:gen_random_uuid()`, описанную в issue, чтобы избежать записи нулевых UUID в базу. <!-- commit-keeper -->
Author
Owner

а что скажешь об UUID7?

а что скажешь об UUID7?

UUIDv7 является отличным компромиссом между UUIDv4 (случайными) и BIGSERIAL (последовательными).

Основные преимущества UUIDv7:

  1. Сортируемость (Time-ordered): В отличие от UUIDv4, первые 48 бит UUIDv7 содержат метку времени (timestamp). Это делает их фактически последовательными.
  2. Производительность БД: Поскольку они сортируемы, PostgreSQL будет вставлять новые записи в конец B-tree индекса, а не в случайные места. Это решает главную проблему производительности UUID, о которой мы говорили ранее (фрагментация страниц и высокая нагрузка на RAM).
  3. Совместимость: UUIDv7 полностью совместим с форматом UUID (128 бит), поэтому вам не нужно менять тип колонки в базе данных с uuid на что-то другое.
  4. Безопасность: Несмотря на наличие временной метки, оставшаяся часть UUID заполнена случайными данными, что делает их достаточно непредсказуемыми.

Итог:
Если вы хотите сохранить преимущества UUID (безопасность, распределенность), но при этом получить производительность индексов, близкую к BIGSERIAL, то UUIDv7 — это лучший выбор на сегодняшний день.

Для реализации в Go можно использовать библиотеки вроде github.com/google/uuid (в новых версиях) или специализированные пакеты для UUIDv7. При этом проблема с default:gen_random_uuid() в GORM всё равно остается актуальной: если вы перейдете на UUIDv7, генерацию лучше перенести на сторону приложения (в хук BeforeCreate), так как стандартные функции PostgreSQL (до версии 17) не генерируют UUIDv7 «из коробки» без дополнительных расширений.

UUIDv7 является отличным компромиссом между UUIDv4 (случайными) и BIGSERIAL (последовательными). **Основные преимущества UUIDv7:** 1. **Сортируемость (Time-ordered):** В отличие от UUIDv4, первые 48 бит UUIDv7 содержат метку времени (timestamp). Это делает их фактически последовательными. 2. **Производительность БД:** Поскольку они сортируемы, PostgreSQL будет вставлять новые записи в конец B-tree индекса, а не в случайные места. Это решает главную проблему производительности UUID, о которой мы говорили ранее (фрагментация страниц и высокая нагрузка на RAM). 3. **Совместимость:** UUIDv7 полностью совместим с форматом UUID (128 бит), поэтому вам не нужно менять тип колонки в базе данных с `uuid` на что-то другое. 4. **Безопасность:** Несмотря на наличие временной метки, оставшаяся часть UUID заполнена случайными данными, что делает их достаточно непредсказуемыми. **Итог:** Если вы хотите сохранить преимущества UUID (безопасность, распределенность), но при этом получить производительность индексов, близкую к BIGSERIAL, то **UUIDv7 — это лучший выбор на сегодняшний день**. Для реализации в Go можно использовать библиотеки вроде `github.com/google/uuid` (в новых версиях) или специализированные пакеты для UUIDv7. При этом проблема с `default:gen_random_uuid()` в GORM всё равно остается актуальной: если вы перейдете на UUIDv7, генерацию лучше перенести на сторону приложения (в хук `BeforeCreate`), так как стандартные функции PostgreSQL (до версии 17) не генерируют UUIDv7 «из коробки» без дополнительных расширений. <!-- commit-keeper -->
Author
Owner

так что в итоге посоветуешь?

так что в итоге посоветуешь?

UUIDv7 — это отличный современный стандарт, который решает главную проблему классических случайных UUID (v4) в базах данных.

В чем его основное преимущество?
Главная проблема UUIDv4 в том, что они абсолютно случайны. При использовании их в качестве первичного ключа в PostgreSQL (B-tree индекс), новые записи вставляются в случайные места индекса. Это приводит к «раздуванию» индекса (index bloat) и постоянным перечитываниями страниц с диска в RAM, что резко снижает производительность при росте таблицы.

UUIDv7 решает это за счет сортируемости:

  1. Timestamp в начале: Первые 48 бит UUIDv7 содержат текущее время (Unix timestamp в миллисекундах).
  2. Локальность данных: Благодаря этому новые записи всегда добавляются в «конец» индекса, как и в случае с обычным BIGSERIAL. Это минимизирует фрагментацию и делает вставку максимально эффективной.
  3. Совместимость: Он полностью совместим с типом uuid в PostgreSQL и любым полем uuid.UUID в Go.

Как это применить в вашем случае:
Если вы перейдете на UUIDv7, вам будет максимально удобно реализовать один из вариантов исправления, которые я предлагал в начале issue:

Используйте хук BeforeCreate в GORM, чтобы генерировать UUIDv7 на стороне приложения. Это решит и проблему с производительностью индексов, и проблему с «нулевыми UUID», которые GORM может отправить в базу при использовании default:gen_random_uuid().

Пример реализации (с использованием библиотеки github.com/google/uuid):

func (d *Document) BeforeCreate(tx *gorm.DB) (err error) {
    // Генерируем UUIDv7
    id, err := uuid.NewV7()
    if err != nil {
        return err
    }
    d.ID = id
    return
}

Итог: UUIDv7 — это «золотая середина». Вы получаете безопасность и уникальность UUID, но при этом сохраняете производительность вставки и поиска, сравнимую с обычными целыми числами.

UUIDv7 — это отличный современный стандарт, который решает главную проблему классических случайных UUID (v4) в базах данных. **В чем его основное преимущество?** Главная проблема UUIDv4 в том, что они абсолютно случайны. При использовании их в качестве первичного ключа в PostgreSQL (B-tree индекс), новые записи вставляются в случайные места индекса. Это приводит к «раздуванию» индекса (index bloat) и постоянным перечитываниями страниц с диска в RAM, что резко снижает производительность при росте таблицы. **UUIDv7 решает это за счет сортируемости:** 1. **Timestamp в начале:** Первые 48 бит UUIDv7 содержат текущее время (Unix timestamp в миллисекундах). 2. **Локальность данных:** Благодаря этому новые записи всегда добавляются в «конец» индекса, как и в случае с обычным `BIGSERIAL`. Это минимизирует фрагментацию и делает вставку максимально эффективной. 3. **Совместимость:** Он полностью совместим с типом `uuid` в PostgreSQL и любым полем `uuid.UUID` в Go. **Как это применить в вашем случае:** Если вы перейдете на UUIDv7, вам будет максимально удобно реализовать один из вариантов исправления, которые я предлагал в начале issue: Используйте хук `BeforeCreate` в GORM, чтобы генерировать UUIDv7 на стороне приложения. Это решит и проблему с производительностью индексов, и проблему с «нулевыми UUID», которые GORM может отправить в базу при использовании `default:gen_random_uuid()`. Пример реализации (с использованием библиотеки `github.com/google/uuid`): ```go func (d *Document) BeforeCreate(tx *gorm.DB) (err error) { // Генерируем UUIDv7 id, err := uuid.NewV7() if err != nil { return err } d.ID = id return } ``` **Итог:** UUIDv7 — это «золотая середина». Вы получаете безопасность и уникальность UUID, но при этом сохраняете производительность вставки и поиска, сравнимую с обычными целыми числами. <!-- commit-keeper -->

Исходя из нашего обсуждения, мой итоговый совет зависит от того, какие приоритеты для вас сейчас важнее:

1. Самый современный и сбалансированный вариант (Рекомендую): UUIDv7
Если вы хотите сохранить преимущества UUID (безопасность, отсутствие перебора ID в URL, удобство распределенных систем), но при этом не терять в производительности БД.

  • Что сделать: Удалить default:gen_random_uuid() из тегов GORM и добавить хук BeforeCreate, который генерирует UUIDv7 (например, через uuid.NewV7() из библиотеки google/uuid).
  • Плюс: Высокая производительность индексов (как у BIGSERIAL) + безопасность UUID.

2. Самый простой и производительный вариант: BIGSERIAL (Integer)
Если приложение будет использоваться в закрытом контуре, или ID не будут видны пользователям в URL, и вам нужна максимальная скорость работы БД «из коробки».

  • Что сделать: Сменить тип поля с uuid.UUID на uint64 и использовать gorm:"primaryKey".
  • Плюс: Максимальная скорость, минимальный размер индекса.

3. Минимальное исправление текущего кода: UUIDv4 (через приложение)
Если вы хотите оставить всё как есть, но просто исправить баг с «нулевыми UUID».

  • Что сделать: Оставить тип uuid.UUID, но перенести генерацию из БД в Go-код (хук BeforeCreate с uuid.NewRandom()).
  • Плюс: Быстрое исправление без смены архитектуры.

Мой вердикт:
Я рекомендую UUIDv7. Это «золотая середина», которая решает проблему фрагментации индексов в PostgreSQL и одновременно избавляет вас от конфликта GORM с дефолтными значениями БД. Это решение на перспективу, которое избавит вас от необходимости мигрировать с UUID на Integer при росте базы данных.

Исходя из нашего обсуждения, мой итоговый совет зависит от того, какие приоритеты для вас сейчас важнее: **1. Самый современный и сбалансированный вариант (Рекомендую): UUIDv7** Если вы хотите сохранить преимущества UUID (безопасность, отсутствие перебора ID в URL, удобство распределенных систем), но при этом не терять в производительности БД. - **Что сделать:** Удалить `default:gen_random_uuid()` из тегов GORM и добавить хук `BeforeCreate`, который генерирует UUIDv7 (например, через `uuid.NewV7()` из библиотеки `google/uuid`). - **Плюс:** Высокая производительность индексов (как у BIGSERIAL) + безопасность UUID. **2. Самый простой и производительный вариант: BIGSERIAL (Integer)** Если приложение будет использоваться в закрытом контуре, или ID не будут видны пользователям в URL, и вам нужна максимальная скорость работы БД «из коробки». - **Что сделать:** Сменить тип поля с `uuid.UUID` на `uint64` и использовать `gorm:"primaryKey"`. - **Плюс:** Максимальная скорость, минимальный размер индекса. **3. Минимальное исправление текущего кода: UUIDv4 (через приложение)** Если вы хотите оставить всё как есть, но просто исправить баг с «нулевыми UUID». - **Что сделать:** Оставить тип `uuid.UUID`, но перенести генерацию из БД в Go-код (хук `BeforeCreate` с `uuid.NewRandom()`). - **Плюс:** Быстрое исправление без смены архитектуры. **Мой вердикт:** Я рекомендую **UUIDv7**. Это «золотая середина», которая решает проблему фрагментации индексов в PostgreSQL и одновременно избавляет вас от конфликта GORM с дефолтными значениями БД. Это решение на перспективу, которое избавит вас от необходимости мигрировать с UUID на Integer при росте базы данных. <!-- commit-keeper -->
Author
Owner

тогда остановимся на UUID7 - дай код фикса нашей проблемы

тогда остановимся на UUID7 - дай код фикса нашей проблемы

Для перехода на UUIDv7 и решения проблемы с нулевыми значениями в GORM, вам потребуется сделать следующее:

  1. Обновить зависимости: Убедитесь, что используете актуальную версию библиотеки github.com/google/uuid (версия 1.6.0 и выше поддерживает UUIDv7).

  2. Изменить модель: Удалите default:gen_random_uuid() из тегов GORM, так как генерация теперь будет происходить на стороне приложения.

  3. Добавить хук BeforeCreate: Реализуйте метод для автоматической генерации ID перед сохранением записи.

Вот полный пример кода для models/document.go:

package models

import (
    "github.com/google/uuid"
    "gorm.io/gorm"
)

type Document struct {
    // Удаляем default:gen_random_uuid(), оставляем только тип и primaryKey
    ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"`
    // ... остальные поля ...
}

// BeforeCreate — хук GORM, который срабатывает перед вставкой записи в БД
func (d *Document) BeforeCreate(tx *gorm.DB) (err error) {
    // Генерируем UUIDv7 (сортируемый по времени)
    id, err := uuid.NewV7()
    if err != nil {
        return err
    }
    d.ID = id
    return nil
}

Почему это решает проблему:

  • Нет конфликта с GORM: Поскольку мы явно присваиваем значение d.ID в коде до того, как GORM отправит запрос в базу, GORM не будет отправлять «нулевой UUID» (0000...), и база данных примет сгенерированный нами UUIDv7.
  • Производительность: Благодаря структуре UUIDv7, PostgreSQL будет вставлять записи последовательно, что предотвратит фрагментацию индексов и обеспечит высокую скорость работы даже при больших объемах данных.
Для перехода на UUIDv7 и решения проблемы с нулевыми значениями в GORM, вам потребуется сделать следующее: 1. **Обновить зависимости**: Убедитесь, что используете актуальную версию библиотеки `github.com/google/uuid` (версия 1.6.0 и выше поддерживает UUIDv7). 2. **Изменить модель**: Удалите `default:gen_random_uuid()` из тегов GORM, так как генерация теперь будет происходить на стороне приложения. 3. **Добавить хук `BeforeCreate`**: Реализуйте метод для автоматической генерации ID перед сохранением записи. Вот полный пример кода для `models/document.go`: ```go package models import ( "github.com/google/uuid" "gorm.io/gorm" ) type Document struct { // Удаляем default:gen_random_uuid(), оставляем только тип и primaryKey ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"` // ... остальные поля ... } // BeforeCreate — хук GORM, который срабатывает перед вставкой записи в БД func (d *Document) BeforeCreate(tx *gorm.DB) (err error) { // Генерируем UUIDv7 (сортируемый по времени) id, err := uuid.NewV7() if err != nil { return err } d.ID = id return nil } ``` **Почему это решает проблему:** - **Нет конфликта с GORM**: Поскольку мы явно присваиваем значение `d.ID` в коде до того, как GORM отправит запрос в базу, GORM не будет отправлять «нулевой UUID» (`0000...`), и база данных примет сгенерированный нами UUIDv7. - **Производительность**: Благодаря структуре UUIDv7, PostgreSQL будет вставлять записи последовательно, что предотвратит фрагментацию индексов и обеспечит высокую скорость работы даже при больших объемах данных. <!-- commit-keeper -->

Для перехода на UUIDv7 и полного решения проблемы с «нулевыми UUID» в GORM, вам нужно внести изменения в файл models/document.go.

Вот готовый код фикса:

package models

import (
    "github.com/google/uuid"
    "gorm.io/gorm"
)

type Document struct {
    // Удаляем default:gen_random_uuid(), так как теперь генерируем ID в приложении
    ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"`
    // ... остальные поля структуры ...
}

// BeforeCreate — хук GORM, который гарантирует генерацию UUIDv7 перед сохранением в БД.
// Это предотвращает отправку нулевого UUID (00000000-0000-0000-0000-000000000000),
// который иначе перекрыл бы default-значение в PostgreSQL.
func (d *Document) BeforeCreate(tx *gorm.DB) (err error) {
    id, err := uuid.NewV7()
    if err != nil {
        return err
    }
    d.ID = id
    return nil
}

Что именно было сделано:

  1. Изменение тега GORM: Мы убрали default:gen_random_uuid(). Если оставить этот тег, но при этом поле в Go будет иметь значение по умолчанию (нулевой UUID), GORM может отправить этот ноль в базу, и база данных запишет именно его, проигнорировав функцию генерации.
  2. Добавление хука BeforeCreate: Теперь идентификатор генерируется на стороне Go-приложения с помощью uuid.NewV7(). Это гарантирует, что в БД всегда уходит валидный, сортируемый по времени UUID.

Важное примечание по зависимостям:
Убедитесь, что в вашем go.mod используется версия github.com/google/uuid версии 1.6.0 или выше, так как поддержка UUIDv7 появилась именно в этих версиях. Если версия ниже, обновите её командой:
go get github.com/google/uuid@latest

Для перехода на UUIDv7 и полного решения проблемы с «нулевыми UUID» в GORM, вам нужно внести изменения в файл `models/document.go`. Вот готовый код фикса: ```go package models import ( "github.com/google/uuid" "gorm.io/gorm" ) type Document struct { // Удаляем default:gen_random_uuid(), так как теперь генерируем ID в приложении ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"` // ... остальные поля структуры ... } // BeforeCreate — хук GORM, который гарантирует генерацию UUIDv7 перед сохранением в БД. // Это предотвращает отправку нулевого UUID (00000000-0000-0000-0000-000000000000), // который иначе перекрыл бы default-значение в PostgreSQL. func (d *Document) BeforeCreate(tx *gorm.DB) (err error) { id, err := uuid.NewV7() if err != nil { return err } d.ID = id return nil } ``` **Что именно было сделано:** 1. **Изменение тега GORM**: Мы убрали `default:gen_random_uuid()`. Если оставить этот тег, но при этом поле в Go будет иметь значение по умолчанию (нулевой UUID), GORM может отправить этот ноль в базу, и база данных запишет именно его, проигнорировав функцию генерации. 2. **Добавление хука `BeforeCreate`**: Теперь идентификатор генерируется на стороне Go-приложения с помощью `uuid.NewV7()`. Это гарантирует, что в БД всегда уходит валидный, сортируемый по времени UUID. **Важное примечание по зависимостям:** Убедитесь, что в вашем `go.mod` используется версия `github.com/google/uuid` версии **1.6.0 или выше**, так как поддержка UUIDv7 появилась именно в этих версиях. Если версия ниже, обновите её командой: `go get github.com/google/uuid@latest` <!-- commit-keeper -->
Sign in to join this conversation.
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: icewind/seadoc#1
No description provided.