Engagements and notes
The government ledger's record of industry interactions
An engagement records a real interaction between one or more organizations and one command. Examples include a conference discussion, a technical demonstration, a site visit, or a follow-up. It is the ledger's durable answer to “who met whom, when, and why?”
Engagement
| Column | Prisma type | Required? | Default | Meaning |
|---|---|---|---|---|
id | String | Yes | cuid() | Engagement identifier. |
title | String | Yes | None | Short interaction title. |
type | EngagementType | Yes | None | Interaction category. |
status | EngagementStatus | Yes | PLANNED | Workflow state. |
date | DateTime | Yes | None | Calendar date/time represented as a timestamp. |
location | String? | No | NULL | Place or virtual location. |
summary | String? | No | NULL | Interaction summary. |
organizationId | String? | No | NULL | Denormalized primary organization — the first selected organization, or NULL for an Other-only engagement. The organizations relation is authoritative. |
otherOrganization | String? | No | NULL | Free-text participant name for an organization not in the directory. |
commandId | String | Yes | None | Government command responsible. |
createdById | String | Yes | None | Government creator. |
createdAt | DateTime | Yes | now() | Record creation time. |
updatedAt | DateTime | Yes | @updatedAt | Last edit time. |
The required command and creator foreign keys make an engagement attributable. Relations are notes (one-to-many EngagementNote), organizations (one-to-many EngagementOrganization join rows), and attachments (one-to-many EngagementAttachment). An engagement must have at least one directory organization or a non-empty otherOrganization — the API enforces this; the schema itself allows both to be null.
EngagementOrganization
Join table linking an engagement to every participating directory organization.
| Column | Prisma type | Required? | Default | Meaning |
|---|---|---|---|---|
id | String | Yes | cuid() | Row identifier. |
engagementId | String | Yes | None | Parent engagement; cascade-deleted with it. |
organizationId | String | Yes | None | Participating organization. |
createdAt | DateTime | Yes | now() | Link creation time. |
@@unique([engagementId, organizationId]) prevents duplicate links. Engagement.organizationId is kept in sync as a denormalized copy of the first selected organization so single-organization queries and older consumers keep working; multi-organization consumers must read the join table.
EngagementAttachment
| Column | Prisma type | Required? | Default | Meaning |
|---|---|---|---|---|
id | String | Yes | cuid() | Attachment identifier. |
engagementId | String | Yes | None | Parent engagement; cascade-deleted with it. |
fileName | String | Yes | None | Original file name (truncated to 255 characters). |
contentType | String | Yes | None | Server-assigned content type from the validated extension. |
sizeBytes | Int | Yes | None | File size. |
data | Bytes | Yes | None | The file bytes themselves. |
uploadedById | String | Yes | None | Government uploader. |
createdAt | DateTime | Yes | now() | Upload time. |
Attachment bytes are stored in the database, not in object storage, so the feature needs no S3/MinIO configuration (unlike submission files). Limits enforced by the upload route: at most 10 attachments per engagement and 15 MB per file, with allowed types PDF, DOCX, PPTX, TXT, PNG, and JPG validated by extension and magic bytes.
EngagementType
| Value | Meaning |
|---|---|
CONFERENCE | Conference interaction. |
TRADE_SHOW | Trade show or exhibition. |
SYMPOSIUM | Symposium or research gathering. |
INDUSTRY_FORUM | Industry forum. |
TECH_EXERCISE | Technology exercise. |
SITE_VISIT | Visit to an organization or government site. |
MEETING | Ordinary meeting. |
DEMO | Demonstration. |
OTHER | Another interaction type. |
EngagementStatus
| Value | Meaning |
|---|---|
PLANNED | Scheduled but not complete. |
COMPLETED | Interaction occurred. |
FOLLOW_UP | Additional action is needed. |
CLOSED | Ledger work is complete. |
EngagementNote
| Column | Prisma type | Required? | Default | Meaning |
|---|---|---|---|---|
id | String | Yes | cuid() | Note identifier. |
engagementId | String | Yes | None | Parent engagement. |
authorId | String | Yes | None | Government author. |
body | String | Yes | None | Note text. |
createdAt | DateTime | Yes | now() | Creation time. |
Notes are append-style context tied to an engagement and author. They are different from OrgDialogueNote, which is organization-level context not tied to one event.
Duplicate warning
The dashboard looks for possible duplicate coordination when the same organization has engagements with two or more commands within the past 90 days or upcoming. This is a warning for humans, not a uniqueness constraint; the schema intentionally permits legitimate multi-command engagement.
Why creator and author are separate
Engagement.createdById records who created the ledger event. Each EngagementNote.authorId records who added a particular follow-up. A later editor does not become the original creator, and several government users can contribute notes without changing the engagement's creation attribution.
The required commandId is the government-side anchor for reporting and scope. The industry-side anchor is the organizations join set (or the otherOrganization free-text name when no participant is registered). One or the other is always present, because the ledger needs explicit accountability even for a conversation at a neutral event.
There is no unique constraint on title/date/organization. The duplicate warning is intentionally advisory because two legitimate meetings can have similar names.
