Skip to content

Sharing and review

Nothing leaves a conversation unless the participant approves the exact words and the exact audience. A reviewer then decides whether those words may enter the shared space. This page is both sides: the participant's /me routes and the reviewer's /staff/reviews.

The four modes

GET /me/concerns/{concern}/sharing answers the four modes with plain-language text, the proposed derivative, the exact audience of each mode, and the flags Voice detected in the proposed text:

Mode Wire name Who sees the text Reviewed
private private the participant alone no
de-identified de-identified everyone, with no name and no link to the author yes
representative only representative-only reviewers and representatives yes
publicly attributed public-attribution everyone, under a name the participant chooses yes

Private is offered first. A mode outside the four is refused with 422 and contribution.mode.invalid.

Approve the exact text

The participant sends the mode and the text as they will be shared. The derivative is theirs to write or edit; Voice's proposed text is a starting point, never what is submitted unless they send it:

curl -s localhost:9000/me/concerns/$CNC/contributions -H "Authorization: Bearer $ME" \
  -H 'Content-Type: application/json' \
  -d '{"mode":"de-identified","text":"Since the Route 14 bus was withdrawn, children on our side of the ring road walk to school along it."}'
{ "id": "ctb-01M5…", "concern": "cnc-01M5…", "mode": "de-identified", "state": "approved by you", "flags": [], "issue": null }

The reply holds the text exactly as sent: that is the approval, recorded as a disclosure grant with its scope, audience, purpose and time. Then the participant submits it:

curl -s -X POST localhost:9000/me/contributions/$CTB/submit -H "Authorization: Bearer $ME"
{ "id": "ctb-01M5…", "state": "under moderation", "flags": ["Minor"] }

A private contribution is kept private and never reviewed; submitting it answers 409 and contribution.private. A submission whose text differs from the approved one is refused with contribution.text.not-approved. On submission the review workflow runs the privacy detectors and the privacy-flagging agent and records the flags: Distinctive, Health, Minor or SmallGroup, each with a reason that never quotes the text.

What the reviewer sees

curl -s localhost:9000/staff/reviews -H "Authorization: Bearer $REV"
[ { "contribution": "ctb-01M5…", "text": "Since the Route 14 bus was withdrawn, …", "mode": "de-identified", "audience": "everyone, with no name",
    "flags": ["Minor"], "suggestedIssues": [ { "issue": "iss-01M57P…", "title": "Rural school bus withdrawal" } ], "ageSeconds": 41 } ]

The reviewer sees the derivative, its mode and audience, the flags and the suggested issues, and never the conversation it came from. They decide one of three things:

curl -s localhost:9000/staff/reviews/$CTB/approve -H "Authorization: Bearer $REV" \
  -H 'Content-Type: application/json' -d '{"issue":"iss-01M57P…"}'
curl -s localhost:9000/staff/reviews/$CTB/approve -H "Authorization: Bearer $REV" \
  -H 'Content-Type: application/json' -d '{"new":{"title":"…","summary":"…"}}'
curl -s localhost:9000/staff/reviews/$CTB/reject -H "Authorization: Bearer $REV" \
  -H 'Content-Type: application/json' -d '{"reason":"The street name identifies the household."}'

Approval into an existing issue or a new one publishes the derivative: a de-identified copy in plaintext on the issue, an attributed or restricted copy sealed under the author's key. A rejection carries a reason, which the participant sees; a rejection with none is refused with contribution.reason.required. A decision from a principal without the reviewer role is refused with 403 and review.not-reviewer.

Enhanced review

A flagged contribution cannot be approved for public visibility until an enhanced review is recorded, as a separate step with a reason:

curl -s localhost:9000/staff/reviews/$CTB/enhanced -H "Authorization: Bearer $REV" \
  -H 'Content-Type: application/json' -d '{"reason":"The child is not named and the road serves many households."}'

Approving a flagged contribution without it is refused with 409 and contribution.second-step.required; sending both in one request is refused with contribution.second-step.same-click. The two steps may be the same reviewer, and each is recorded with who and when.

The participant's side

GET /me/contributions/{contribution} answers the state, the review's outcome and reason, the suggested issue and the association. States, as the participant sees them: draft, approved by you, under moderation, published, not published, withdrawn.

When a published contribution was suggested an issue, the participant accepts it or asks for a new one; the association is kept apart from the text and can be changed under review:

curl -s localhost:9000/me/contributions/$CTB/issue -H "Authorization: Bearer $ME" \
  -H 'Content-Type: application/json' -d '{"accept":"iss-01M57P…"}'

Withdraw

curl -s -X POST localhost:9000/me/contributions/$CTB/withdraw -H "Authorization: Bearer $ME"

A withdrawn disclosure leaves its audience and the counts: the copy is dropped from the issue's state, its page and the search index, and the chronology shows a withdrawal with no text. Withdrawn is terminal; any later command but a read answers 409 and contribution.withdrawn.

What a reader sees

A published de-identified copy appears on the issue with its text, publishedAt and a public reference pub-…, and nothing that links it to its author. Public search snippets are built from the public view's columns only. No public route ever returns a private field, which the suites check on every public route.