Refusals
Every rule a Voice command can break, named. A refusal is an HttpProblem whose body is
{ "rule": "<name>", "status": <n>, "error": "<sentence>", "names": { … } }, the same shape the
reasoning service uses, so one client reads both. The names map carries the identifiers the rule is
about and never a personal value. A client reads a refusal by its rule, not its status alone: a
status passes for the wrong refusal.
Rules live in the entity or workflow that owns them; the endpoint maps the error to the status. Role
rules are checked in the endpoint from the authenticated principal, because an entity does not know
its caller. The domain module lists every rule in voice.domain.Refusal, and a test fails on a rule
in this table that is not there, or there and not here.
Participation
Rule
Status
Broken when
Names
participant.erased
410
any command but a query reaches a participant whose erasure completed
participant
session.invite.invalid
403
a session is started with no invite code, or one not among the pilot's codes
none
participant.credential.invalid
401
a recovery credential does not match the participant's hash
none
conversation.not-owned
403
a session acts on a conversation that belongs to another participant
conversation
conversation.message.empty
422
a message has no text, or exceeds 4,000 characters
conversation
proposal.not-held
404
a confirm or edit names a proposal the conversation does not hold
conversation, proposal
proposal.already-confirmed
409
a proposal is confirmed a second time
proposal, concern
proposal.stale
409
a confirm names a proposal extracted from messages the participant has since edited
proposal
Disclosure
Rule
Status
Broken when
Names
contribution.mode.invalid
422
a mode outside the four is sent
contribution
contribution.not-owned
403
a session acts on a contribution that belongs to another participant
contribution
contribution.state.invalid
409
a contribution is re-approved while under moderation or published, or associated with an issue once published
contribution, state
contribution.text.not-approved
409
a submission for review carries no approval with the exact text being submitted
contribution
contribution.private
409
a private contribution is submitted for review or published
contribution
contribution.not-under-review
409
a reviewer decision arrives for a contribution not under moderation
contribution
contribution.second-step.required
409
a flagged contribution is approved for public visibility with no enhanced review recorded
contribution, flags
contribution.second-step.same-click
409
the enhanced review and the approval arrive as one request
contribution
contribution.reason.required
422
a rejection or an enhanced review carries no reason
contribution
contribution.withdrawn
409
any command but a query reaches a withdrawn contribution
contribution
contribution.issue.not-held
404
an association names an issue that does not exist
contribution, issue
review.not-reviewer
403
a principal without the reviewer role decides a review
principal role
Issues and evidence
Rule
Status
Broken when
Names
issue.not-held
404
a route names an issue that does not exist, or one the caller may not see
issue
issue.contribution.duplicate
409
the same public reference is published twice; the issue answers 200 and changes nothing
issue, pub
issue.question.duplicate
409
the same question reference is linked twice; idempotent, 200
issue, question
evidence.state.invalid
409
an evidence link moves from rejected to accepted, or from superseded to anything
issue, evidence, from, to
evidence.reference.unresolved
422
a link names a reasoning id the reasoning service does not hold
kind, id
evidence.not-reviewer
403
a principal without the reviewer role accepts, rejects or supersedes evidence
principal role
review-prompt.not-open
409
a review prompt is resolved twice; the issue answers 200 and changes nothing
issue, prompt
option.endorsement
422
a policy option body carries a recommendation or ranking field
option
question.unsupported
200, body { "abstained": true }
a question has no accepted, unwithdrawn, unsuperseded claim; an answer, not an error
question
Deliberation
Rule
Status
Broken when
Names
expression.priority.range
422
a priority level is outside 1 to 5
issue
expression.option.not-held
404
a trade-off or consideration names an option the issue does not offer
issue, option
expression.withdrawn
409
an amendment reaches a withdrawn expression; expressing again is a new command
issue
aggregate.suppressed
200, cell omitted with "suppressed": true
a cell is under the threshold; an answer, not an error
issue, cell
Investigation and action
Rule
Status
Broken when
Names
investigation.not-representative
403
a principal without the representative role opens an investigation, records an action, a decision or an outcome, or publishes an update
principal role
investigation.suggested.not-approved
409
a task or action is attached to an investigation still suggested
investigation
action.date.future
422
an action is dated after today
action, date
action.date.before-issue
422
an action is dated before the issue opened
action, date, issue
action.fields.required
422
authority, owner, rationale, public summary or state is missing, or the state is not proposed, in-progress or completed
action, missing
action.state.backwards
409
an action moves from completed to an earlier state
action
outcome.action.not-held
404
an outcome names an action that does not exist on the issue
issue, action
update.text.required
422
a narrative update is published with no text
issue
update.correction.not-held
404
a correction names an update the issue does not hold
issue, development
staff.inactive
403
a deprovisioned staff account sends any staff command
principal
staff.not-operator
403
a principal without the operator role provisions or deprovisions staff
principal role
Interests and notifications
Rule
Status
Broken when
Names
interest.delivery.invalid
422
a delivery other than inbox-immediate is sent
none
interest.not-owned
403
a session changes another participant's interest
none
notification.duplicate
200, unchanged
a second preparation for the same participant and development arrives; the entity answers the first
development
notification.not-owned
404
a session reads another participant's inbox entry
none
development.stale
retry
the update workflow finds the follower view behind the development's time; the step retries, then fails over to a step that reads interests by id
development
Erasure
Rule
Status
Broken when
Names
erasure.unverified
202, state HeldForOperator
a request arrives from a session with no recovery credential; an answer, not an error
participant
erasure.already-requested
200, unchanged
a second request for the same participant, or a dormancy firing for a participant whose erasure is already running or done
participant
dormancy.reset
done
the dormancy timer fires for a participant with a later session; the timed action has nothing to do and says so, which is success
participant
erasure.not-operator
403
a principal without the operator role verifies a held request or reads a receipt
principal role
erasure.receipt.personal
build time
a receipt type gains a sealed or free-text field; a test fails
none
Public read interfaces
Rule
Status
Broken when
Names
public.not-found
404
a public route is asked for a private, restricted, withdrawn or unpublished record; the same answer as for a record that never existed, so existence leaks nothing
none
public.search.filter
build time
a public view query reads a column the classification table marks personal, restricted or sensitive; a test fails
view, column
sealed.in-public-body
build time
a public response type contains a sealed field or a participant id; a test fails
type
Cross-cutting
Rule
Status
Broken when
Names
role.required
401
a staff route is called with no accepted token
none
session.required
401
a citizen route is called with no session token
none
content.instructions
none
participant text or a document contains instructions aimed at the system; nothing happens, the text is content. There is no refusal because there is no command to refuse; the test is that no state changes
none