The policy model
What a communication policy says - positioning, review mode, on-violation, allowed and prohibited claims, disclaimer rules - how the gate applies it, and its routes.
A communication policy is the authored document the gate applies to every patient-facing message. It is versioned per scope and managed at /api/v1/compliance/policies. This page is the model as the code reads it.
The fields
| Field | Values | What the gate does with it |
|---|---|---|
program_positioning | wellness, clinical | Under wellness, a fixed list of clinical terms is a violation on its own: diagnose, diagnosis, treat, treatment, cure, prevent, disease, medication, prescription, clinical trial. Under clinical that screen is off and only your claim lists apply. |
review_mode | none, on_violation (default), always | When a delivered message also needs a person to look at it. Anything the gate withholds needs one regardless. |
on_violation | allow, rewrite, block_and_review | What a violation becomes. rewrite is term substitution and is a hold for a patient audience. |
allowed_claims | list of phrases | Recorded on the policy; not enforced by the gate. |
prohibited_claims | list of phrases | A match is a violation. |
requires_disclaimer_for | list of phrases | A match without a disclaimer marker in the same message is a violation. |
name, description | text | Identity. name defaults to "Default Communication Policy". |
project_id | integer or null | Scope. A project policy overrides the organization policy for that project. |
Claims are normalized on write: trimmed and lower-cased. Values for the three enum fields are accepted in any case and stored lower-case.
How matching works
A phrase matches when it appears in the message as whole words, case-insensitively across Unicode, with a small inflection tolerance on the final word (plurals and simple tenses). It is term matching, not meaning: a policy that prohibits diagnosis does not catch "what you have is", and a policy that names nothing catches nothing beyond the wellness screen. State the policy in the words a model would actually produce.
A message "has a disclaimer" when it contains one of the fixed markers: "not medical advice", "not intended to diagnose", "consult a healthcare professional", "consult your clinician", "for wellness purposes only".
What a rewrite does
Each prohibited match is replaced with "wellness indicator", each wellness-screen term with "wellness", and the fixed wellness disclaimer is appended once when a disclaimer was required or a clinical term was replaced. The result is machine term substitution, which is why it is delivered only to a clinician audience and held for a person when the audience is a patient. The rewritten text is stored on the decision either way.
Scope resolution and the default
The gate resolves the active policy for the project first, then falls back to the organization's active policy. Every organization is provisioned with a restrictive default: wellness positioning, on_violation review, block_and_review on violation, empty claim lists. The default's strictness comes from the wellness screen, not from seeded claims. If no active policy resolves for a scope, the gate withholds and records policy_missing.
Versioning
PATCH /api/v1/compliance/policies/active does not edit in place: it deactivates the current active policy for that exact scope and inserts a new row with version incremented. Every decision record names the policy_id it was evaluated under, so a reviewer can always read the exact document that produced a decision.
Routes
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /api/v1/compliance/policies | Read | list, filter project_id, limit |
| GET | /api/v1/compliance/policies/active | Read | the resolved active policy for project_id (or the organization) |
| PATCH | /api/v1/compliance/policies/active | Write | upsert a new active version for the scope; body below |
| POST | /api/v1/compliance/policies/validate | Write | returns the normalized policy, a risk_level and warnings, without saving |
| POST | /api/v1/compliance/policies/draft | Write | assistant-drafted policy from intent, preferred_positioning, strictness, channels, optional existing_policy, use_ai |
| POST | /api/v1/compliance/policies/{policy_id}/activate | Write | re-activate a stored version |
Body for the upsert:
{
"project_id": 7,
"name": "Between-visits messaging",
"description": "Engagement and logistics only; no interpretation of readings.",
"program_positioning": "wellness",
"review_mode": "on_violation",
"on_violation": "block_and_review",
"allowed_claims": ["trend awareness", "reminder to take a reading"],
"prohibited_claims": ["diagnosis", "you have hypertension", "increase your dose"],
"requires_disclaimer_for": ["blood pressure", "heart rate"]
}The decision records
Every evaluation writes one record. GET /api/v1/compliance/checks (Admin scope) lists them with filters communication_channel, communication_ref, communication_ref_prefix, conversation_id, decision, project_id, review_required, release_status, limit, offset, and returns beside the page a histogram of release_status counts that ignores the active filter, so the queue's buckets are always visible. GET /api/v1/compliance/checks/{check_id} returns one record.
| Field | Meaning |
|---|---|
communication_channel, audience | Where the message was bound and who it was for |
communication_ref | Your reference for the message |
decision | allow, rewrite, block_and_review, policy_missing |
rationale | The violations, in words |
detected_claims | The phrases that matched |
original_content, rewritten_content | The draft, and the substituted text when any |
policy_id | The exact policy version applied |
review_required, release_status | auto_handled, held_for_review, released_after_approval, release_delivery_failed, rejected_after_review |
reviewed_by, reviewed_at, released_with, release_notes | The person's action, when there was one |
POST /api/v1/compliance/checks (Admin scope) inserts a record of a decision that was already made elsewhere; it does not evaluate. To have the gate evaluate a third party's draft, call POST /api/v1/govern/evaluate (Concepts).
Which surfaces are gated
Every path in the platform that composes patient-facing text calls the gate before sending: rule notifications, adherence nudges, agent nudges and notifications, symptom interview questions, manual patient notifications, and chat replies to a patient. Clinician-facing agent insights are evaluated with a clinician audience. There is no ungated path to a patient; the type that a notification body must be can only be produced by the gate or by a reviewer's release.