Floor Control and Speaking Flow
This document covers how the system implements Part XVI (S.O. 78–83) and Part XVII (S.O. 97) of the National Assembly Standing Orders (7th Edition) — the rules of debate, floor requests, the speaking queue, microphone control, and time limits.
1. Key Standing Orders Implemented
| Standing Order | Rule | System enforcement |
|---|---|---|
| S.O. 78 | Every Member wishing to speak shall address a request to the Speaker | POST /sittings/:id/speaking-requests |
| S.O. 79 | When two or more Members rise at the same time, the Speaker calls upon them | Queue ordering + Point of Order priority |
| S.O. 81 | No Member shall speak after the question has been put | Blocked when a VoteSession is OPEN |
| S.O. 82 | No Member shall speak more than once to a question | Prior-speech check per agenda item |
| S.O. 82A | Member who has spoken may speak to an amendment | Point of Information requests allowed |
| S.O. 83 | Any Member may raise a Point of Order at any time | POINT_OF_ORDER type floats to queue top |
| S.O. 97 | Time limits on debate — auto-assigned by role | timeLimit set on grant from role lookup |
2. Speaking Request Lifecycle
A speaking request moves through the following states:
Member submits request
│
▼
PENDING ◄──── queued, visible to Speaker
│
Speaker acts
┌────┴────┐
▼ ▼
GRANTED DENIED ──► terminal
│
▼
IN_PROGRESS ──► mic ON, timer running
│
├──► PAUSED (mic OFF, timer frozen) ──► IN_PROGRESS (resumed)
│
▼
COMPLETED ──► terminal
PENDING / GRANTED / IN_PROGRESS ──► CANCELLED (Member withdraws)
Status Meanings
| Status | Meaning |
|---|---|
PENDING | Request submitted, waiting for Speaker to act |
GRANTED | Speaker recognised the Member — mic not yet ON |
IN_PROGRESS | Mic is ON, Member is speaking, timer running |
DENIED | Speaker declined the request |
COMPLETED | Speech ended (mic OFF, speakingEnd recorded) |
CANCELLED | Member withdrew request |
3. Request Types (S.O. 78, 83)
requestType constant | Standing Order | Description |
|---|---|---|
REGULAR | S.O. 78 | Standard floor request to speak in debate |
POINT_OF_ORDER | S.O. 83 | Point of order — floats to top of queue |
POINT_OF_INFORMATION | S.O. 82A | Point of information — exempt from S.O. 82 repeat-speaker rule |
URGENT_MATTER_MOVER | S.O. 33 | Mover of urgent adjournment motion |
URGENT_MATTER_OTHER | S.O. 33 | Other speaker on urgent adjournment matter |
4. Queue Ordering (S.O. 79)
When the Speaker views the speaking queue (GET /sittings/:id/speaking-requests), requests are sorted as follows:
- Points of Order (
POINT_OF_ORDER) always sort to position 0 — ahead of all other requests - Within each priority group, requests are ordered by
queuePosition(insertion order) then byrequestedAttimestamp
This directly implements S.O. 79: "If two or more Members request to speak at the same time, the Speaker shall call upon them in such order as the Speaker deems fit", with the system providing the Speaker a pre-ordered queue.
5. Standing Order Enforcement on Submission
The POST /sittings/:id/speaking-requests endpoint enforces three SO rules before creating the request:
S.O. 78 — Active Sitting Required
Throws BadRequestException if sitting.status ≠ IN_PROGRESS
"SO 78 – Speaking requests can only be made during an active sitting"
S.O. 81 — No Speaking After Question Put
Throws BadRequestException if VoteSession WHERE agendaItemId = x AND status = OPEN exists
"SO 81 – No Member may speak after the question has been put"
S.O. 82 — No Repeat Speaking
Throws BadRequestException if a prior GRANTED / IN_PROGRESS / COMPLETED request exists
for the same (userId, agendaItemId) combination.
Does NOT apply to POINT_OF_ORDER or POINT_OF_INFORMATION request types.
"SO 82 – A Member may not speak more than once to the same question"
6. Granting and Time Limits (S.O. 79, 97)
POST /sittings/speaking-requests/:requestId/grant — Speaker recognises the Member.
On grant, the service automatically resolves the timeLimit from the member's role:
| Member role | S.O. 97 | Time limit | System constant |
|---|---|---|---|
leader_majority / leader_minority | S.O. 97 | 60 minutes | SPEAKING_TIME_LIMITS.LEADER_MAJORITY_MINORITY = 3600 s |
member (regular debate) | S.O. 97 | 20 minutes | SPEAKING_TIME_LIMITS.REGULAR_DEBATE = 1200 s |
URGENT_MATTER_MOVER type | S.O. 33 | 10 minutes | SPEAKING_TIME_LIMITS.URGENCY_MOVER = 600 s |
URGENT_MATTER_OTHER type | S.O. 33 | 5 minutes | SPEAKING_TIME_LIMITS.URGENCY_OTHER = 300 s |
| Statement (S.O. 43) | S.O. 43 | 3 minutes | SPEAKING_TIME_LIMITS.STATEMENT = 180 s |
| Petition presenter (S.O. 225) | S.O. 225 | 5 minutes | SPEAKING_TIME_LIMITS.PETITION = 300 s |
POINT_OF_ORDER type | S.O. 83 | No limit | SPEAKING_TIME_LIMITS.POINT_OF_ORDER = 0 (Speaker decides) |
The resolved timeLimit (in seconds) and remainingTime are set on the SpeakingRequest record and sent to all clients via the micOn WebSocket event.
7. Microphone Control (S.O. 79)
After a request is GRANTED, the Speaker activates the microphone:
Mic ON — POST /sittings/speaking-requests/:requestId/mic-on
- Sets
micStatus = ON,status = IN_PROGRESS - Records
speakingStarttimestamp - Emits
micOnWebSocket event to the sitting room withtimeLimit,remainingTime,speakingStart, andserverNow
Point of Order interruption: If a POINT_OF_ORDER mic-on request is made while another Member has mic ON, the system automatically:
- Pauses the current speaker — sets
micStatus = OFF, recordsremainingTime, setspausedAt - Emits
micOffwithevent: PAUSED_FOR_POINT_OF_ORDERto the sitting room - Turns on the Point of Order member's mic
Mic OFF — POST /sittings/speaking-requests/:requestId/mic-off
- Sets
micStatus = OFF,status = COMPLETED - Records
speakingEnd - Emits
micOffWebSocket event
Pause — POST /sittings/speaking-requests/:requestId/pause
- Sets
micStatus = OFFwhile keepingstatus = IN_PROGRESS - Calculates and stores
remainingTime - Records
pausedAt - Emits
floorUpdatewithevent: PAUSED
Resume — POST /sittings/speaking-requests/:requestId/resume
- Restores
micStatus = ON - Clears
pausedAt, restoresspeakingStart - Emits
micOnwith restoredremainingTime
8. WebSocket — Floor Gateway
All floor events are emitted on the floor namespace. Clients must join a sitting room first:
// Subscribe
emit('joinSitting', '<sittingId>')
// → { status: 'joined', room: 'sitting_<id>', serverNow: '...' }
// Unsubscribe
emit('leaveSitting', '<sittingId>')
Events Received in the Sitting Room
| Event | When emitted | Payload fields |
|---|---|---|
floorUpdate | Queue changes (request created/granted/denied/cancelled/paused) | event, sittingId, request, serverNow |
micOn | Member's mic activated | sittingId, requestId, userId, timeLimit, remainingTime, speakingStart, serverNow, member |
micOff | Member's mic deactivated or paused | sittingId, requestId, userId, serverNow, event, remainingTime, member |
agendaUpdate | Agenda item state changed | sittingId, agenda item data |
voteUpdate | Vote session created/closed | sittingId, sessionId, event, serverNow |
micOff event values:
event value | Meaning |
|---|---|
COMPLETED | Speech concluded normally |
PAUSED_FOR_POINT_OF_ORDER | Paused because a Point of Order was raised |
PAUSED | Speaker paused speech manually |
9. API Reference — Floor Control
| Method | Endpoint | Permission | Who |
|---|---|---|---|
| POST | /api/v1/sittings/:id/speaking-requests | floor:request_speak | Member submits request |
| GET | /api/v1/sittings/:id/speaking-requests | floor:list_requests | View ordered speaking queue |
| GET | /api/v1/sittings/:id/current-speaker | sitting:read | Get current IN_PROGRESS speaker |
| GET | /api/v1/sittings/:id/active-vote | vote:read | Get active vote (SO 81 check) |
| POST | /api/v1/sittings/speaking-requests/:id/grant | floor:grant_speak | Speaker grants request |
| POST | /api/v1/sittings/speaking-requests/:id/deny | floor:deny_speak | Speaker denies request |
| POST | /api/v1/sittings/speaking-requests/:id/mic-on | floor:mic_control | Activate microphone |
| POST | /api/v1/sittings/speaking-requests/:id/mic-off | floor:mic_control | Mute microphone |
| POST | /api/v1/sittings/speaking-requests/:id/pause | floor:mic_control | Pause speech, freeze timer |
| POST | /api/v1/sittings/speaking-requests/:id/resume | floor:mic_control | Resume paused speech |
| POST | /api/v1/sittings/speaking-requests/:id/cancel | floor:request_speak | Member cancels own request |
10. Full Floor Flow Example
1. Clerk creates sitting (SCHEDULED)
2. Speaker starts sitting → status = IN_PROGRESS
3. Member A POSTs /speaking-requests { requestType: "REGULAR" }
→ queuePosition = 1, status = PENDING
→ WebSocket: floorUpdate { event: "REQUEST_CREATED" }
4. Member B POSTs /speaking-requests { requestType: "REGULAR" }
→ queuePosition = 2, status = PENDING
5. Member C POSTs /speaking-requests { requestType: "POINT_OF_ORDER" }
→ queuePosition = 3, but sorts to position 0 in queue response
6. Speaker GETs /speaking-requests
→ [ Member C (POO, pos 0), Member A (pos 1), Member B (pos 2) ]
7. Speaker POSTs /speaking-requests/:A_id/grant
→ status = GRANTED, timeLimit = 1200 (20 min, regular member)
→ WebSocket: floorUpdate { event: "REQUEST_GRANTED" }
8. Speaker POSTs /speaking-requests/:A_id/mic-on
→ status = IN_PROGRESS, micStatus = ON, speakingStart = now
→ WebSocket: micOn { timeLimit: 1200, remainingTime: 1200, speakingStart, serverNow }
9. Member C (POO) POSTs /speaking-requests/:C_id/mic-on
→ Member A auto-paused: micOff { event: "PAUSED_FOR_POINT_OF_ORDER" }
→ Member C: micOn (no timer)
10. Speaker POSTs /speaking-requests/:C_id/mic-off
→ WebSocket: micOff { event: "COMPLETED" }
11. Speaker POSTs /speaking-requests/:A_id/resume
→ Member A resumes with remaining time preserved
→ WebSocket: micOn { remainingTime: <preserved> }
12. Speaker POSTs /speaking-requests/:A_id/mic-off
→ status = COMPLETED, speakingEnd recorded
→ WebSocket: micOff { event: "COMPLETED" }
13. Speaker POSTs /sittings/:id/complete → COMPLETED