> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Regulatory Change Management — Integration Contract

> GR-17 detects changes in registered regulatory source documents (AHCCCS AMPM bulletins, DHCS Documentation Standards, federal CMS / SAMHSA notices, etc.), uses…

**Feature ID:** GR-17\
**Spec:** [GR-17 Regulatory Change Management](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/specs/GR-17-regulatory-change-management.md)\
**Version:** 1.0\
**Status:** 📝 Planned\
**Last Updated:** 2026-04-19\
**Owner:** GR (Governance & Risk)

***

## Overview

GR-17 detects changes in registered regulatory source documents (AHCCCS AMPM bulletins, DHCS Documentation Standards, federal CMS / SAMHSA notices, etc.), uses PF-27 Platform AI to classify the impact against the org's existing `gr_regulatory_requirements` rows, and routes proposed actions through a human approval workflow that auto-creates `gr_compliance_checks` follow-ups, PF-10 notifications, and (optionally) re-ingests source documents into PF-61 so GR-06-EN-01's RAG corpus stays current.

**Integration patterns used:**

* **Platform Integration Layer:** PF-10 (Notifications), PF-27 (Platform AI), PF-30 (Permissions), PF-46 (Data Retention), PF-60 (RAG), PF-61 (Knowledge Base), PF-96 (Jurisdiction Profiles)
* **Event Publisher:** `gr_regulatory_change_detected`, `gr_regulatory_change_approved`
* **Cross-core (read-only):** Reads `gr_regulatory_requirements` (GR-03) for impact classification; writes `gr_compliance_checks` (GR-03) on approval via GR-03's existing mutation API (no duplicate insert logic)
* **External integrations:** AHCCCS AMPM index (HTML/PDF, no auth), DHCS Documentation Standards (HTML, no auth); polling-based ingestion (webhook deferred)

***

## Integration 1: GR-17 → GR-03 (Cross-core, intra-core helper)

**Pattern:** Direct DB read + GR-03's `gr_compliance_checks` insert API\
**Status:** 📝 Planned

**Reads:** `gr_regulatory_requirements` (id, title, topic tags, owner, jurisdiction\_profile\_id) — used as PF-27 prompt context for impact classification. RLS is org-scoped via `gr_has_org_access()`; classifier runs under the org's service-role context with explicit `organization_id` filter.

**Writes:** On approval of a `gr_regulatory_change_impacts` row with `proposed_action ∈ {review, update, supersede}`, GR-17 calls GR-03's existing compliance-check insert API to create a `gr_compliance_checks` row referencing the affected `requirement_id`. GR-17 does NOT duplicate the GR-03 insert logic; it uses the platform helper.

**Tenant isolation:** All reads/writes carry `organization_id` (defense-in-depth); RLS enforces.

**Failure mode:** GR-03 insert failure rolls back the approval transaction (impact status flip + check insert + notification publish are one DB transaction; PF-61 enqueue is post-commit and may be retried independently).

***

## Integration 2: GR-17 → GR-06-EN-01 (Event consumer, downstream)

**Pattern:** Event Consumer (subscribes to GR-17 events) + PF-61 corpus refresh\
**Status:** 📝 Planned

GR-06-EN-01 (AI State Compliance Checking) consumes GR-17's events and the refreshed PF-61 corpus to give compliance officers state-aware, citation-grounded answers reflecting the latest authoritative text.

* Subscribes to `gr_regulatory_change_detected` to surface "rule change detected" notices in the advisor surface (without exposing classification details until human approval).
* Consumes `gr_regulatory_change_approved` to know that a previously-stale citation now has a current document.
* Reads from PF-61 corpus that GR-17 re-ingests on approval (no direct DB coupling between GR-17 and GR-06-EN-01).

***

## Integration 3: GR-17 → PF-10 (Platform Layer — Notifications)

**Pattern:** Platform Integration Layer (PF-10)\
**Usage:**

* Approval-side: notify the affected `gr_regulatory_requirements` owner that a follow-up `gr_compliance_checks` row has been created; CC the org's compliance officer role.
* Source-health: notify org admins when a source has been `errored` for ≥ 24 hours or when PF-61 re-ingestion fails after approval.

**Notification keys (registered in PF-10):**

* `gr_regulatory_change_followup_assigned` (per-approval; targets requirement owner; CC compliance officer role)
* `gr_regulatory_source_errored` (rate-limited; one per 24 h per source; targets org admin)
* `gr_pf61_reingest_failed` (after 5 failed retries per FR-5.4; targets org admin)
* `gr_regulatory_source_oversized_review` (snapshot `oversized = true` per FR-2.5; targets source registrant + org admin)
* `gr_regulatory_source_phi_guard_triggered` (PHI/PII heuristic match per FR-2.6; targets org admin)

**Auth / tenant:** Standard PF-10 helpers; payload is metadata-only (UUIDs + labels), never source body text.

***

## Integration 4: GR-17 → PF-27 (Platform Layer — Platform AI)

**Pattern:** Platform Integration Layer (PF-27)\
**Usage:** `gr-classify-regulatory-change-impact` edge function calls PF-27 to extract the changed sections, tag affected topics, propose an impact mapping to `gr_regulatory_requirements`, and assign a confidence score.

**Prompt content (NO PHI):**

* Diff summary (regulatory text only — public)
* Org's relevant `gr_regulatory_requirements.title` + topic tags (no patient/employee identifiers)
* Resolved jurisdiction profile context from `pf_resolve_jurisdiction_profile()` (PF-96): state, Medicaid program, regulatory body, retention rules
* AI model version recorded on the resulting `gr_regulatory_change_impacts.ai_model_version` for audit

**Refusal conditions:**

* Source body matches PHI/PII heuristics (SSN, MRN patterns) — abort, flag for manual review, do NOT call PF-27.
* Source body exceeds `regulatory_change_source_size_cap_mb` — abort, flag for manual review.

**Idempotency:** Re-running classification on the same `(change_event_id, requirement_id)` updates the existing row; no duplicates.

***

## Integration 5: GR-17 → PF-30 (Platform Layer — Permissions v2)

**Pattern:** Direct dependency\
**New permission keys** (seeded via migration into `pf_module_permissions` and assigned to roles in `pf_role_permissions`; constants added to `src/platform/permissions/constants.ts`):

| Key                                       | Purpose                                                              | Default Roles                                              |
| ----------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------- |
| `gr.compliance.regulatory_source.admin`   | Register / edit / soft-delete sources; manual "Poll now"             | `gr.compliance.admin`, `org_admin`                         |
| `gr.compliance.regulatory_change.view`    | View change feed and impact details                                  | `gr.compliance.admin`, `gr.compliance.viewer`, `org_admin` |
| `gr.compliance.regulatory_change.approve` | Approve / reject impact rows; triggers `gr_compliance_checks` insert | `gr.compliance.admin`, `org_admin`                         |

Audit gate: `npm run audit:permissions` (covered by `validate:governance`).

***

## Integration 6: GR-17 → PF-46 (Platform Layer — Data Retention)

**Pattern:** Direct dependency\
**Usage:** Snapshot, change-event, classification, and approval audit retention follows PF-46 retention rules. Default for compliance evidence: **7 years**. Source rows support soft-delete (`deleted_at`); snapshots and change events are append-only and never logically deleted.

**Retention policy keys (configured in PF-46):**

* `gr_regulatory_source_snapshots` — retain 7 years from `fetched_at`
* `gr_regulatory_change_events` — retain 7 years from `created_at`
* `gr_regulatory_change_impacts` — retain 7 years from `approved_at` / `rejected_at` (whichever applies)

***

## Integration 7: GR-17 → PF-60 / PF-61 (Platform Layer — RAG / Knowledge Base)

**Pattern:** Platform Integration Layer (PF-61 ingestion API; PF-60 indexes the corpus)\
**Status:** 📝 Planned

`gr-pf61-refresh-source` edge function is invoked post-approval when `proposed_action ∈ {update, supersede}` (and only when `regulatory_change_pf61_auto_refresh_enabled = true` for the org). It enqueues PF-61 ingestion of the affected source document; PF-60 re-indexes asynchronously.

* Re-ingestion respects PF-61's existing tenant scoping.
* Re-ingestion failure does NOT roll back the approval; failure is logged and a `gr_pf61_reingest_failed` notification is sent to the org admin (Integration 3).
* UI surfaces a `pf_kb_refresh_status` badge on the approved impact row (`queued | running | done | failed`).

***

## Integration 8: GR-17 → PF-96 (Platform Layer — Jurisdiction Profiles)

**Pattern:** Direct dependency\
**Usage:**

* Each `gr_regulatory_sources` row binds to exactly one `pf_jurisdiction_profiles.id`.
* Watcher reads jurisdiction context to scope per-source polling cadence overrides and known-source allowlists.
* Classifier prompt includes resolved jurisdiction profile context (state, Medicaid program, regulatory body) so PF-27 can propose state-correct mappings.
* AI auto-`no_op` confidence threshold honors PF-96 jurisdiction-scoped override (`policy_overrides.gr_17.ai_auto_no_op_threshold`) before falling back to the org's `gr_module_settings` default and finally the platform default.

**Resolution helper:**

* Frontend: `useJurisdictionProfile(orgId?: string, siteId?: string)` — wrapper around the PF-96 canonical contract `pf_resolve_jurisdiction_profile(p_org_id, p_site_id)`
* Server: `pf_resolve_jurisdiction_profile(p_org_id, p_site_id)` — canonical RPC (SECURITY DEFINER)
* Edge functions: `getJurisdictionProfile(supabaseClient, orgId, siteId?)` — canonical edge function helper

***

## Event Contracts

### Event: `gr_regulatory_change_detected`

**Channel:** `gr_events`\
**Publisher:** GR-17 (`gr-regulatory-change-watcher` after creating a `gr_regulatory_change_events` row)\
**Subscribers:** GR-06-EN-01 (advisor surface), org admin notifications via PF-10\
**Status:** 📝 Planned

```typescript theme={null}
{
  event_type: 'gr_regulatory_change_detected';
  organization_id: uuid;
  timestamp: timestamptz;
  change_event_id: uuid;
  source_id: uuid;
  jurisdiction_profile_id: uuid;
  severity: 'info' | 'minor' | 'major' | 'critical';
  // No source body or diff text in payload — consumers read via DB lookup with their own RLS context.
}
```

### Event: `gr_regulatory_change_approved`

**Channel:** `gr_events`\
**Publisher:** GR-17 (`gr_regulatory_change_impacts` status → `approved` transition; transactional)\
**Subscribers:** GR-03 (compliance check is already inserted in the same transaction; this event is informational), GR-06-EN-01 (corpus freshness signal), GR-08 (accreditation evidence linkage)\
**Status:** 📝 Planned

```typescript theme={null}
{
  event_type: 'gr_regulatory_change_approved';
  organization_id: uuid;
  timestamp: timestamptz;
  change_event_id: uuid;
  impact_id: uuid;
  requirement_id: uuid | null;       // null when impact_status = 'unmatched' but approved as informational
  follow_up_check_id: uuid | null;    // null when proposed_action = 'no_op'
  proposed_action: 'review' | 'update' | 'supersede' | 'no_op';
  approved_by: uuid;
}
```

### Events Consumed: None.

***

## API Contracts

**GR-03 Compliance Check Insert API** (called synchronously by GR-17 on approval per FR-4.3):

* **Endpoint pattern:** Server-side RPC or typed mutation helper (exact signature TBD; tracked in PENDING\_CONTRACTS.md)
* **Logical request payload:** `{ organization_id: UUID, requirement_id: UUID, regulatory_change_id?: UUID (optional GR-17 linkage), proposed_action: 'review' | 'update' | 'supersede' (NOT 'no_op'), actor_id: UUID, custom_fields?: JSONB }`
* **Response:** `{ check_id: UUID, status: 'pending' | 'inserted' }`
* **Transactional semantics (NFR-3):** This insert MUST occur within the same DB transaction as the GR-17 approval status flip and PF-10 notification publish. Rollback on any leg.
* **Authorization:** Caller MUST have `gr.compliance.regulatory_change.approve` permission (enforced by GR-17 approval mutation; GR-03 insert API enforces org-scoped RLS).
* **Error codes:** Standard RLS denial (403 / 42501); FK violation if `requirement_id` deleted (409 / 23503); sanitized via `sanitizeErrorMessage()`.
* **Idempotency:** Re-running the same `(requirement_id, regulatory_change_id)` pair MAY upsert or MAY error per GR-03 internal policy (GR-17 does not rely on duplicate-insert handling; approval is single-fire per impact row).
* **Constraint:** Caller MUST NOT invoke this API when `proposed_action = 'no_op'` (GR-17 approval logic skips insert for no-op actions per FR-3.6 and FR-4.3).

See Integration 1 (GR-17 → GR-03) and Integration 5 (PF-30 permission key `gr.compliance.regulatory_change.approve`). Final signature tracked in [PENDING\_CONTRACTS.md](/architecture/integrations/PENDING_CONTRACTS) until GR-17 Phase 3 implementation.

***

## External Integrations

| Source                                    | URL                                                | Auth                   | Polling         | Owner                                     | Status     |
| ----------------------------------------- | -------------------------------------------------- | ---------------------- | --------------- | ----------------------------------------- | ---------- |
| AHCCCS AMPM index (Arizona)               | `https://azahcccs.gov/shared/MedicalPolicyManual/` | None (public)          | Daily (default) | Compliance officer (per-org registration) | 📝 Planned |
| DHCS Documentation Standards (California) | `https://www.dhcs.ca.gov/...` (per profile)        | None (public)          | Daily (default) | Compliance officer (per-org registration) | 📝 Planned |
| Additional state Medicaid sources         | per PF-96 profile                                  | None (public, default) | Daily (default) | Compliance officer                        | 📝 Planned |

Add to `docs/architecture/integrations/EXTERNAL_INTEGRATIONS.md` on Phase 1 implementation.

***

## Security & Tenant Isolation

* All four GR-17 tables include `organization_id` and are RLS-protected via `gr_has_org_access()` SECURITY DEFINER helper.
* UPDATE policies include `WITH CHECK` (constitution §5.2.4).
* Snapshots are append-only (no UPDATE/DELETE policies for non-superuser).
* Source documents are public regulatory text only. Ingestion refuses content matching PHI/PII heuristics; abort + flag for manual review; never persist suspected PHI.
* AI prompts contain only requirement titles + topic tags + diff text (no patient/employee identifiers).
* URL validation: `https://` required; reject private / loopback IPs at registration time.
* Edge functions use `_shared/auth.ts` helpers (`verifyOrgAccess()` for user-initiated polls; service-role pattern for cron with explicit `organization_id` validation per call — never inline `pf_user_role_assignments` queries).
* Sanitized error messages via `sanitizeErrorMessage()` for any user-facing error.

***

## Scheduled Processing

| Cron                          | Cadence                                                                        | Function                                                                |
| ----------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| Source poll                   | 15-minute tick; per-source skip if `now() - last_polled_at < cadence_interval` | `gr-regulatory-change-watcher`                                          |
| Classification fallback retry | Every 15 min for events stuck in `classifying` ≥ 30 min                        | `gr-classify-regulatory-change-impact`                                  |
| Source-health alerts          | Daily                                                                          | inline in watcher; notifies via PF-10 when a source is `errored` ≥ 24 h |

**Cadence detail:** Global cron tick every 15 minutes (`*/15 * * * *`); per-source skip if `now() - last_polled_at < cadence_interval` where `cadence_interval` is `hourly = 60 min`, `daily = 24 h`, `weekly = 7 d` per FR-2.1. Per-org soft cap of 50 sources processed per tick (FIFO by `last_polled_at NULLS FIRST`) and 10 concurrent in-flight polls. Manual "Poll now" rate-limited to 1/min/source (HTTP 429 + UI cooldown badge); manual polls bypass cadence skip but still observe the concurrency cap. Scheduler language and SLA align with `gr-regulatory-change-watcher` behavior and classification fallback retry policy.

DB trigger on `gr_regulatory_change_events` insert kicks off classification synchronously (then handed to the cron fallback if the worker errors).

***

## Pending Contract Items

| Item                                                                                      | Blocked On                         | Notes                                                                                            |
| ----------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------ |
| Final `gr_regulatory_change_detected` and `gr_regulatory_change_approved` payload schemas | GR-17 Phase 2 implementation       | Confirm field names with GR-06-EN-01, GR-03, GR-08 consumers                                     |
| PF-61 ingestion API contract for `regulatory_source` document type                        | PF-61 implementation status        | Confirm `source_type` string and metadata schema                                                 |
| GR-03 `gr_compliance_checks` insert helper signature                                      | GR-17 Phase 3 implementation       | Confirm whether GR-03 exposes a typed mutation API or DB function (avoid duplicate insert logic) |
| AHCCCS AMPM source canonical URL list (per Arizona profile)                               | PF-96 Arizona profile finalization | Seed migration for the default Arizona profile registers AMPM index source                       |

See [PENDING\_CONTRACTS.md](/architecture/integrations/PENDING_CONTRACTS) for tracking.

***

## Contract Validation

Validated against [CONTRACT\_VALIDATION\_CHECKLIST.md](/architecture/integrations/CONTRACT_VALIDATION_CHECKLIST):

* [ ] Event payload schemas finalized (GR-17 Phase 2)
* [x] Publisher / subscriber declared for each event
* [x] Auth and tenant isolation documented per function
* [x] PHI restrictions documented (NO PHI in `gr_regulatory_*` tables; AI prompts metadata-only)
* [x] Platform layer usage documented (PF-10, PF-27, PF-30, PF-46, PF-60, PF-61, PF-96)
* [ ] Integration matrix updated in [CROSS\_CORE\_INTEGRATIONS.md](/architecture/integrations/CROSS_CORE_INTEGRATIONS) (planned with Phase 1 implementation)

***

## References

* [GR-17 Spec](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/specs/GR-17-regulatory-change-management.md)
* [GR-03 Spec](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/specs/GR-03-compliance-tracking.md)
* [GR-06-EN-01 Spec](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/specs/GR-06-EN-01-ai-state-compliance-checking.md)
* [GR-14 Integration](/architecture/integrations/regulatory-incident-reporting-automation-integration) — peer regulator-facing automation (different direction; outbound submissions vs inbound change watching)
* [PF-96 Spec](https://github.com/Encore-OS/encoreos/blob/development/specs/pf/specs/PF-96-medicaid-state-compliance-configuration.md)
* [EVENT\_CONTRACTS.md](/architecture/integrations/EVENT_CONTRACTS) — to add `gr_regulatory_change_detected`, `gr_regulatory_change_approved`
* [PLATFORM\_INTEGRATION\_LAYERS.md](/architecture/integrations/PLATFORM_INTEGRATION_LAYERS) — PF-10, PF-27, PF-30, PF-46, PF-60, PF-61, PF-96
* [CROSS\_CORE\_INTEGRATIONS.md](/architecture/integrations/CROSS_CORE_INTEGRATIONS)
* [PENDING\_CONTRACTS.md](/architecture/integrations/PENDING_CONTRACTS)
