Meetings & Resolutions
Meetings & Resolutions
The meetings feature provides a complete management and archive layer for all formal meetings held by a block — Annual General Meetings (AGMs), Extraordinary General Meetings (EGMs), and directors' meetings for RMC/RTM companies.
Overview
Every meeting is scoped to a single block and moves through a defined status lifecycle. Once a meeting has been held or cancelled, it becomes part of the permanent, immutable archive for that block — preserving a legally auditable record of decisions, attendance, and votes.
Meeting Types
| Type | Description |
|---|---|
agm | Annual General Meeting — the mandatory annual meeting of leaseholders |
egm | Extraordinary General Meeting — called for urgent or specific business |
directors | Directors' meeting of the RMC/RTM company |
Meeting Status Lifecycle
scheduled → held
→ cancelled
scheduled: The meeting is planned but has not yet taken place. It can be edited or deleted.held: The meeting has taken place. It cannot be deleted — it forms part of the permanent archive.cancelled: The meeting was cancelled. It cannot be deleted — the cancellation is itself a historical record.
Archive protection: Attempting to delete a
heldorcancelledmeeting will return aPRECONDITION_FAILEDerror.
Data Model
Meetings (meetings)
The top-level meeting record for a block.
| Field | Type | Description |
|---|---|---|
id | text | Primary key |
orgId | text | Tenant identifier |
blockId | text | The block this meeting belongs to |
meetingType | agm | egm | directors | Type of meeting |
title | text | Human-readable title, e.g. "Maple Court AGM 2025" |
date | timestamp | Scheduled date and time |
venue | text? | Location or "Virtual — Zoom" etc. |
status | scheduled | held | cancelled | Current status (default: scheduled) |
quorumMet | boolean? | Whether quorum was achieved (set when marking as held) |
chairUserId | text? | The user who chaired the meeting |
minutesHtml | text? | HTML minutes from the rich text editor |
notes | text? | Internal notes (not published in minutes) |
createdBy | text | User who created the record |
Agenda Items (meeting_agenda_items)
Ordered list of items on the meeting agenda.
| Field | Type | Description |
|---|---|---|
meetingId | text | Parent meeting |
sortOrder | integer | Display order (1, 2, 3, …) |
title | text | Agenda item title |
description | text? | Additional context for the item |
resolutionText | text? | Proposed resolution wording (null if informational only) |
Attendees (meeting_attendees)
Tracks who attended, including proxy representatives.
| Field | Type | Description |
|---|---|---|
meetingId | text | Parent meeting |
unitId | text | The unit (leaseholder) attended or represented |
leaseholderName | text? | Name snapshot captured at the time of recording |
proxy | boolean | true if the leaseholder was represented by proxy |
proxyHolderName | text? | Name of the proxy holder (if proxy = true) |
arrivedAt | timestamp? | Time the attendee or proxy arrived |
Leaseholder name snapshots: The leaseholder name is read from the unit record when the attendee is added and stored on the attendee row. This ensures the archive remains accurate even if the unit changes ownership later.
Resolutions (resolutions)
Formal vote outcomes linked to specific agenda items.
| Field | Type | Description |
|---|---|---|
meetingId | text | The meeting at which the vote was held |
agendaItemId | text | The agenda item this resolution corresponds to |
text | text | Final wording of the resolution (may differ from the agenda item's proposed text if amended on the floor) |
votesFor | integer | Votes in favour |
votesAgainst | integer | Votes against |
votesAbstain | integer | Abstentions |
passed | boolean | Whether the resolution passed (determined by the chair per the articles of association) |
notes | text? | e.g. "Passed by show of hands", "Poll demanded" |
tRPC API Reference
All procedures are namespaced under meeting.*.
Access Control
| Permission level | Procedures |
|---|---|
org member (orgProcedure) | list, getById, listAgendaItems, listAttendees, listResolutions |
admin (adminProcedure) | All create, update, and delete procedures |
Meetings
meeting.list
Returns a paginated list of meetings for the organisation.
Input
{
cursor?: string // Pagination cursor
limit?: number // Page size
blockId?: string // Filter by block
meetingType?: 'agm' | 'egm' | 'directors'
status?: 'scheduled' | 'held' | 'cancelled'
}
Results are ordered by date descending (most recent first). Each row includes the joined blockName.
meeting.getById
Returns a single meeting with its full agenda, attendee count, and resolution count.
Input
{ id: string }
Response includes agendaItems[], attendeeCount, and resolutionCount in addition to all meeting fields. minutesHtml is included in this response but omitted from the list response.
meeting.create
Creates a new meeting for a block. The block must belong to the organisation.
Input
{
blockId: string
meetingType: 'agm' | 'egm' | 'directors'
title: string // max 500 chars
date: string // ISO 8601 datetime
venue?: string // max 500 chars
notes?: string
}
meeting.update
Updates one or more fields on a meeting. All fields are optional — only provided fields are changed.
Input
{
id: string
title?: string
date?: string // ISO 8601 datetime
venue?: string | null
meetingType?: 'agm' | 'egm' | 'directors'
status?: 'scheduled' | 'held' | 'cancelled'
quorumMet?: boolean | null
chairUserId?: string | null
minutesHtml?: string | null
notes?: string | null
}
Minutes and notes can be updated regardless of status — this allows minutes to be added after a meeting has been marked as held.
meeting.delete
Deletes a meeting and all its child records (agenda items, attendees, resolutions). Only scheduled meetings can be deleted.
Input
{ id: string }
Error: Returns PRECONDITION_FAILED if the meeting status is held or cancelled.
Agenda Items
meeting.listAgendaItems
{ meetingId: string }
Returns items ordered by sortOrder ascending.
meeting.createAgendaItem
{
meetingId: string
sortOrder: number // integer ≥ 1
title: string // max 500 chars
description?: string
resolutionText?: string // null if item is informational only
}
meeting.updateAgendaItem
{
id: string
sortOrder?: number
title?: string
description?: string | null
resolutionText?: string | null
}
meeting.deleteAgendaItem
{ id: string }
Also deletes any resolutions linked to this agenda item.
Attendees
meeting.listAttendees
{ meetingId: string }
Returns attendees with unitNumber joined from the units table. Ordered by createdAt ascending.
meeting.addAttendee
{
meetingId: string
unitId: string
proxy?: boolean // default false
proxyHolderName?: string // max 200 chars; required if proxy = true
arrivedAt?: string // ISO 8601 datetime
}
The leaseholder name is automatically snapshotted from the unit record at insert time.
meeting.removeAttendee
{ id: string }
Resolutions
meeting.listResolutions
{ meetingId: string }
Returns resolutions with agendaItemTitle joined. Ordered by createdAt ascending.
meeting.createResolution
{
meetingId: string
agendaItemId: string
text: string // Final resolution wording
votesFor: number // integer ≥ 0
votesAgainst: number // integer ≥ 0
votesAbstain: number // integer ≥ 0
passed: boolean
notes?: string
}
The agenda item must belong to the specified meeting, validated server-side.
meeting.updateResolution
{
id: string
text?: string
votesFor?: number
votesAgainst?: number
votesAbstain?: number
passed?: boolean
notes?: string | null
}
meeting.deleteResolution
{ id: string }
Proxy Voting
Proxy voting is first-class in the data model. When adding an attendee:
- Set
proxy: trueto indicate the leaseholder was not present in person. - Provide
proxyHolderNameto record who held the proxy. - The unit (leaseholder) is still referenced by
unitIdso the proxy vote is correctly attributed to the right leasehold.
Audit Logging
Every write operation emits an audit log entry. The following actions are recorded:
| Action | Trigger |
|---|---|
meeting.created | New meeting created |
meeting.updated | Meeting fields updated |
meeting.deleted | Meeting deleted |
meeting.agenda_item_created | Agenda item added |
meeting.agenda_item_updated | Agenda item updated |
meeting.agenda_item_deleted | Agenda item removed |
meeting.attendee_added | Attendee recorded |
meeting.attendee_removed | Attendee removed |
meeting.resolution_recorded | Resolution created |
meeting.resolution_updated | Resolution updated |
meeting.resolution_deleted | Resolution deleted |
Tenant Isolation & Security
- All four tables (
meetings,meeting_agenda_items,meeting_attendees,resolutions) are registered inRLS_TABLESfor database-level row-level security. - All tRPC queries include an
orgIdfilter — data from one tenant is never accessible to another. - Block ownership is validated on meeting creation: a meeting cannot be created for a block belonging to a different organisation.