Build a Human-Approved AI Opportunity Bulletin in Tencent RTC Community Chat A developer built a human-approved AI opportunity bulletin for Tencent RTC's social-messaging community, using a language model to extract candidate fields from submitted text while requiring moderators to verify authenticity and control publication. The workflow includes states for capture, extraction, review, approval, and publishing, with an explicit 'publish_unknown' state to handle delivery uncertainty. The system emphasizes that AI should only convert supplied material into a reviewable draft, not decide legitimacy or publish autonomously. A community opportunity post creates an awkward tension: members want timely information, but publishing it can look like an endorsement. Add AI summarization and the ambiguity gets worse. Did a person verify the deadline and eligibility, or did a model confidently fill in missing details? The useful role for AI here is narrow: convert supplied material into a reviewable draft. It should not decide whether an opportunity is legitimate, rank who deserves it, or publish on its own. In this tutorial, we will build an opportunity bulletin for a Tencent RTC social-messaging community with: Tencent RTC's Social Messaging solution covers group discussions, large communities, 1-to-1 chat, rich media, and related social experiences. That makes the bulletin a workflow inside the community conversation rather than a separate publishing system: Social Messaging solution https://trtc.io/solutions/social-messaging . A language model can demonstrate that it can extract candidate fields from supplied text. That does not demonstrate that the source is authentic or that its terms are still current. Use this division of responsibility: | Decision | Owner | |---|---| | Extract a possible deadline, organizer, reward, or eligibility statement | AI assistant | | Prove each extracted field came from the submitted text | Application validator | | Decide whether the source is trustworthy enough to share | Moderator | | Decide whether publication implies endorsement | Community policy | | Publish, reject, correct, or withdraw the post | Moderator-controlled workflow | | Translate the displayed post | Reader-controlled chat feature | This reframes the human concern. Moderators are not there to polish AI prose; they are accountable for deciding what the community is willing to distribute. Our post will move through these states: captured └── extracting ├── review └── extraction failed review ├── approved ├── rejected └── captured source edited; revision increases approved └── publishing ├── published ├── approved confirmed not sent └── publish unknown delivery may have happened published ├── expired └── superseded by a new, reviewed revision publish unknown matters. If the chat service accepted a message but the client lost the response, blindly retrying may create a duplicate. That uncertainty is a real state, not an exception to hide. mkdir community-opportunity-desk cd community-opportunity-desk npm init -y npm install --save-dev typescript tsx @types/node npx tsc --init mkdir -p src test Add scripts to package.json : { "scripts": { "test": "tsx --test test/ .test.ts", "check": "tsc --noEmit" } } The domain code will not depend on an AI vendor or chat SDK. Those integrations sit behind ports so we can test the risky transitions locally. Do not ask the model for a polished summary alone. Require every extracted claim to identify the exact source characters supporting it. Create src/domain.ts : js import { createHash } from "node:crypto"; export type ClaimName = | "title" | "organizer" | "deadline" | "eligibility" | "reward"; export type Claim = { displayValue: string; quote: string; start: number; end: number; }; export type Extraction = Partial