> ## 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.

# Competency Validation & Assessment Engine — Integration Contract

> GR-18 ships the competency assessment engine on top of GR-02 (Training & CEU Tracking). It owns assessments, attempts, responses, and the answer-key isolation…

**Feature ID:** GR-18
**Spec:** [GR-18 Competency Validation & Assessment Engine](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/specs/GR-18-competency-assessment-engine.md)
**Plan:** [GR-18 Implementation Plan](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/plans/GR-18-competency-assessment-engine-PLAN.md)
**Version:** 1.0
**Status:** 📝 Planned
**Last Updated:** 2026-04-24
**Owner:** GR (Governance & Compliance)
**Constitution Reference:** §1 Architecture — no direct core-to-core imports; cross-core via Platform Integration Layer and events.

> **Auto-created by `validate-spec --auto-fix`** (2026-04-24) to satisfy the Integration Doc Existence requirement (constitution §2.1). Pre-filled from the Integration Points section of the spec; sections marked **TODO** require author review before Phase A migration ships.

***

## Overview

GR-18 ships the competency assessment engine on top of GR-02 (Training & CEU Tracking). It owns assessments, attempts, responses, and the answer-key isolation pattern. It does **not** own courses, enrollments, or completions — those remain GR-02. Cross-core consumers (HR-22 transcripts, GR-19 in-service matrix) read assessment evidence exclusively through `@/platform/training` (PIL) and the `assessment_passed` / `assessment_failed` events.

**Architectural pattern:** Same-core writer (within GR) + PF-bus event publisher + PF Platform Integration Layer consumer. No direct core-to-core imports.

**Related documents:**

* [GR-INTEGRATION.md](/architecture/integrations/GR-INTEGRATION) — GR module integration anchor (if present)
* [CROSS\_CORE\_INTEGRATIONS.md](/architecture/integrations/CROSS_CORE_INTEGRATIONS) — integration matrix (TODO: add GR-18 row when this doc is reviewed)
* [EVENT\_CONTRACTS.md](/architecture/integrations/EVENT_CONTRACTS) — event payload schemas (TODO: register `assessment_passed` / `assessment_failed`)
* [PLATFORM\_INTEGRATION\_LAYERS.md](/architecture/integrations/PLATFORM_INTEGRATION_LAYERS) — PIL conventions for `@/platform/training`

***

## Integration Summary

| Integration                             | Pattern                     | From → To                                                                                                                                                                                                                     | Status      |
| --------------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| GR-02 completion creation on pass       | Same-core RPC (intra-GR)    | GR-18 → `gr_complete_training` (GR-02) — production signature `(_enrollment_id UUID, _score INTEGER, _verification_method TEXT, _verifier_id UUID, _time_spent_minutes INTEGER) RETURNS JSONB` per migration `20260211182655` | 📝 Planned  |
| GR-02 completion revocation on void     | Same-core RPC (intra-GR)    | GR-18 → `gr_revoke_training_completion` (GR-02; **stub added by GR-18 Phase A — function does NOT exist in current production schema**)                                                                                       | 📝 Planned  |
| GR-02-EN-01 event bus                   | Event Publisher             | GR-18 publishes `assessment_passed` / `assessment_failed` on `gr_events`                                                                                                                                                      | 📝 Planned  |
| GR-02-EN-02 Platform Integration Layer  | PIL exports                 | GR-18 → `@/platform/training` (`useStartAssessment`, `useSubmitAssessment`, `useAssessmentAttempt`, `useCompetencyEvidence`)                                                                                                  | 📝 Planned  |
| GR-02-EN-03 picklist value `assessment` | Picklist coordination       | GR-18 depends on `gr.training.verification_method` enum value `assessment` (PF-15 picklist)                                                                                                                                   | 📝 Planned  |
| PF-30 permissions                       | Platform Layer              | GR-18 reads `gr.training.assessments.author / review / take`                                                                                                                                                                  | 📝 Planned  |
| PF-08 forms                             | Platform Layer              | GR-18 author UI uses `ConfigurableForm` primitives                                                                                                                                                                            | 📝 Planned  |
| PF-11 templated PDFs                    | Platform Layer              | GR-18 surveyor evidence export via `useGenerateTemplatedPdf` (template `competency_evidence`)                                                                                                                                 | 📝 Planned  |
| GR-19 in-service matrix consumer        | Event Consumer (downstream) | GR-19 subscribes to `assessment_passed` to color compliance cells                                                                                                                                                             | 📝 Planned  |
| HR-22 LMS overlay reader                | PIL read-only               | HR-22 reads attempt evidence via `@/platform/training` (per ADR-014)                                                                                                                                                          | 📝 Planned  |
| PF-96 jurisdiction profiles             | Future enhancement          | Not used in v1 (jurisdiction-neutral schema); future EN may resolve passing-score / max-attempt overrides                                                                                                                     | 📝 Deferred |

***

## Events Published by GR-18

### `assessment_passed`

* **Channel:** `gr_events` (PF event bus; same pattern as `GR-02-EN-01.training_completed`)
* **Publisher:** GR-18 — `gr_submit_assessment_attempt` SECURITY DEFINER RPC, on the same DB transaction as the grade write (at-least-once delivery; consumers MUST dedupe on `attempt_id`).
* **Subscribers:**
  * GR-02 completion-gating logic (intra-RPC; creates `gr_training_completions` row + CEU credit when linked course `verification_method = 'assessment'`)
  * GR-19 In-Service Compliance Monitor (optional; flips cell to "compliant" when course is assessment-gated)
  * HR-22 LMS transcript surface (optional; refreshes employee transcript view)
* **Purpose:** Notify downstream consumers that an employee passed an assessment and (if applicable) a GR-02 completion has been created.
* **Status:** 📝 Planned

**Payload Schema:**

```typescript theme={null}
{
  organization_id: string;     // UUID — required for tenant routing
  attempt_id: string;          // UUID — gr_assessment_attempts.id (idempotency key)
  assessment_id: string;       // UUID — gr_assessments.id
  course_id: string;           // UUID — gr_training_courses.id (linked course)
  enrollment_id: string;       // UUID — gr_training_enrollments.id
  employee_id: string;         // UUID — hr_employees.id at attempt start
  attempt_number: number;      // 1-based; excludes voided priors
  score_pct: number;           // 0.00–100.00
  passed: true;                // always true for this event
  submitted_at: string;        // ISO 8601 timestamp
}
```

**Security note:** No PHI in payload. Question stems, response text, and answer keys are NEVER included. Payload is metadata only.

***

### `assessment_failed`

* **Channel:** `gr_events`
* **Publisher:** GR-18 — `gr_submit_assessment_attempt`, same transaction as the grade write.
* **Subscribers:**
  * GR-02 enrollment status updater (sets `status = 'failed'` when `attempt_number = max_attempts` and last attempt failed)
  * PF-10 notifications (TODO: register notification key for "retry available" / "no attempts remaining" messages)
  * GR-19 (optional; surfaces failure in compliance monitor)
* **Purpose:** Notify downstream consumers that an employee failed an attempt; on final failure, the enrollment is marked `failed` and an admin reset is required.
* **Status:** 📝 Planned

**Payload Schema:**

```typescript theme={null}
{
  organization_id: string;
  attempt_id: string;
  assessment_id: string;
  course_id: string;
  enrollment_id: string;
  employee_id: string;
  attempt_number: number;
  score_pct: number;
  passed: false;               // always false for this event
  submitted_at: string;
  attempts_remaining: number;  // 0 when this was the final allowed attempt
  cooldown_until: string | null; // ISO 8601 — when the next attempt becomes available
}
```

**Security note:** Same as `assessment_passed`. No PHI; no question content.

***

## Events Consumed by GR-18

*None.* GR-18 publishes events; it does not consume events from other cores. (Future EN: may consume `enrollment_overdue` from `GR-02-EN-01` to surface "due soon" CTAs in the assessment runtime.)

***

## API Contracts

### SECURITY DEFINER RPCs (Internal)

All GR-18 mutations go through SECURITY DEFINER functions; no direct table writes from clients.

| Function                                                                                                           | Auth                                                                                          | Purpose                                                                                                                                                                                                                                               |
| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gr_start_assessment_attempt(_assessment_id UUID, _enrollment_id UUID) RETURNS UUID`                               | Caller must have `gr.training.assessments.take` AND own the enrollment (RLS-validated)        | Create a new attempt; validate `attempt_number < max_attempts` against non-voided priors and cooldown elapsed against most recent non-voided submitted attempt; raise if `random_count > active item count`                                           |
| `gr_submit_assessment_attempt(_attempt_id UUID, _responses JSONB) RETURNS JSONB`                                   | Caller owns the attempt (RLS)                                                                 | Server-side grade via `gr_grade_response`; reject if `now() > expires_at` (sets `status='expired'`); publish `assessment_passed` / `assessment_failed` in same transaction; on pass + `verification_method='assessment'`, call `gr_complete_training` |
| `gr_grade_response(_question_id UUID, _response JSONB) RETURNS BOOLEAN`                                            | Internal helper (called by `gr_submit_assessment_attempt`); never exposed to clients via REST | Compare response to `correct_answer` per item type without returning the answer to caller                                                                                                                                                             |
| `gr_void_assessment_attempt(_attempt_id UUID, _reason TEXT, _revoke_completion BOOLEAN DEFAULT true) RETURNS VOID` | Caller must have `gr.training.assessments.author` (admin-only)                                | Set `status='voided'`; void does NOT count toward `attempt_number` or cooldown; if `_revoke_completion = true` and the voided attempt produced a completion, call `gr_revoke_training_completion`; write `gr_assessment_reset_log` row                |
| `gr_reset_assessment_attempts(_enrollment_id UUID, _reason TEXT) RETURNS VOID`                                     | Caller must have `gr.training.assessments.author`                                             | Allow employee to retry beyond `max_attempts`; write audit row in `gr_assessment_reset_log`                                                                                                                                                           |
| `gr_revoke_training_completion(_attempt_id UUID) RETURNS VOID`                                                     | Internal helper called by `gr_void_assessment_attempt` (stub if not yet present in GR-02)     | Reverse the GR-02 completion + CEU credit row created by the now-voided attempt                                                                                                                                                                       |

**Pattern:** All RPCs use `SECURITY DEFINER SET search_path = public`. Tenant isolation enforced inside each function via `pf_current_organization_id()` check; never trust client-supplied `organization_id`.

### REST/HTTP APIs

*None.* GR-18 has no Edge Functions or external HTTP endpoints in v1. All client interaction is via Supabase `rpc()` calls to the SECURITY DEFINER functions above, plus Supabase `select()` against the `gr_assessment_questions_safe` view (employee role) or full `gr_assessment_*` tables (author / review roles, RLS-gated).

***

## Platform Integration Layers Used

GR-18 exposes its own surface through `@/platform/training` so cross-core consumers (HR-22, GR-19) never import directly from `@/cores/gr/...`.

| PIL hook                                      | Source                                            | Consumers                                                                        |
| --------------------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------- |
| `useStartAssessment(enrollmentId)`            | `@/platform/training` (re-export from GR-18 hook) | Employee runtime UI; HR-22 transcript "Take assessment" CTA                      |
| `useSubmitAssessment(attemptId)`              | `@/platform/training`                             | Employee runtime UI                                                              |
| `useAssessmentAttempt(attemptId)`             | `@/platform/training`                             | Employee runtime UI; admin attempt detail page                                   |
| `useCompetencyEvidence(employeeId, courseId)` | `@/platform/training`                             | Surveyor evidence view; HR-22 transcript drawer; GR-19 in-service matrix tooltip |
| `useEmployeeAssessmentAttempts(employeeId)`   | `@/platform/training`                             | HR-22 transcript; GR-19 monitor                                                  |

| Other PIL                          | Usage                                                                                                             |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `@/platform/forms` (PF-08)         | `ConfigurableForm` primitives for question authoring UI                                                           |
| `@/platform/documents` (PF-11)     | `useGenerateTemplatedPdf({ template: 'competency_evidence', data })` for surveyor PDF export                      |
| `@/platform/permissions` (PF-30)   | `useHasPermission('gr.training.assessments.author' \| 'review' \| 'take')` and `<RequirePermission>` route guards |
| `@/platform/notifications` (PF-10) | (TODO: confirm whether assessment retry-available notifications are in v1 scope)                                  |
| `@/platform/wizards` (PF-41)       | `WizardShell` for the 4-step "Create assessment" flow (steps: details, bank link, items + scoring, course link)   |

***

## Permissions

Three new permission keys, registered in PF-30 via the Phase A seed migration AND added to `src/platform/permissions/constants.ts`:

| Permission                       | Format                         | Purpose                                                                                                                                            |
| -------------------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gr.training.assessments.author` | `{module}.{entity}.{action}` ✅ | Create / edit / retire question banks, items, assessments; admin reset/void; full-row SELECT on `gr_assessment_questions` (incl. `correct_answer`) |
| `gr.training.assessments.review` | `{module}.{entity}.{action}` ✅ | Read attempt evidence; export surveyor PDF; full-row SELECT on `gr_assessment_questions` (incl. `correct_answer`) for preview only                 |
| `gr.training.assessments.take`   | `{module}.{entity}.{action}` ✅ | Start/submit attempts; SELECT only on `gr_assessment_questions_safe` view (no `correct_answer` column exposed)                                     |

**Audit:** `npm run audit:permissions` MUST pass (typed entries match seed migration).

***

## Picklists

Depends on **`GR-02-EN-03`** which adds the value `assessment` to the `gr.training.verification_method` picklist.

| Picklist                          | Value        | Owner       | Coordination                                                                                                                                |
| --------------------------------- | ------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `gr.training.verification_method` | `assessment` | GR-02-EN-03 | Coordinated landing — if EN-03 slips, GR-18 Phase A migration adds the value inline via `ALTER TYPE … ADD VALUE IF NOT EXISTS 'assessment'` |

***

## Cross-Core Reads (via PIL only — no direct imports)

GR-18 reads no other core's tables directly. Cross-core touch points:

* **HR (`hr_employees`)** — referenced by `gr_assessment_attempts.employee_id` FK. Read via existing GR-02 patterns (already established as same-core dependency for GR core's training tables). No new HR PIL hook required.
* **GR-02 tables (`gr_training_courses`, `gr_training_enrollments`)** — same core; FK references. Not cross-core.

***

## Tenant Isolation & Security

* Every GR-18 table includes `organization_id` (defense-in-depth).
* RLS enabled on all 7 tables (6 entity + 1 audit log) with SECURITY DEFINER helpers `pf_current_organization_id()`, `pf_user_has_permission()`, `pf_current_employee_id()` (no recursive RLS — constitution §5.7).
* Answer-key isolation pattern: employee role has NO direct SELECT policy on `gr_assessment_questions`; reads go through `gr_assessment_questions_safe` view (omits `correct_answer` + `rationale`).
* Server-side time enforcement on submit; client timer is advisory only.
* Attempts + responses are immutable after submission (no UPDATE policy); admin void via SECURITY DEFINER + audit-log row.
* Event payloads contain UUIDs and metadata only — no question stems, no response text, no PHI.

***

## Failure Modes

| Failure                                                  | Behavior                                                                                                                                                                                                                                                                                                                                                                                                                       |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Submit after `expires_at`                                | RPC marks attempt `status='expired'`; no event published; UI shows "time elapsed — please contact your administrator"                                                                                                                                                                                                                                                                                                          |
| `random_count > active item count` at start              | `gr_start_assessment_attempt` raises with deterministic message: "Assessment requires N items but only M are active in the linked bank."                                                                                                                                                                                                                                                                                       |
| GR-02 `gr_complete_training` fails on pass               | Inspect the JSONB return: if `success = false AND error = 'Training already completed'`, treat as non-error retry (do not re-publish `assessment_passed`). For any other failure, the whole `gr_submit_assessment_attempt` transaction rolls back via `RAISE EXCEPTION`; attempt remains `in_progress`; client receives error; retry idempotent on `attempt_id` (protected by `SELECT ... FOR UPDATE` row lock on the attempt) |
| Event publish fails                                      | Wrapped in same DB transaction as the grade write — if publish fails, grade write rolls back; at-least-once delivery means consumers MUST dedupe on `attempt_id`                                                                                                                                                                                                                                                               |
| `gr_revoke_training_completion` not yet shipped in GR-02 | Phase A includes a stub with `RAISE NOTICE 'GR-02 revoke RPC not present; completion will need manual revoke'` and writes the `gr_assessment_reset_log` row regardless                                                                                                                                                                                                                                                         |

***

## Validation Checklist

Per [`docs/architecture/integrations/CONTRACT_VALIDATION_CHECKLIST.md`](/architecture/integrations/CONTRACT_VALIDATION_CHECKLIST):

* [ ] Event payloads documented with TypeScript schema (above)
* [ ] No PHI in event payloads (verified above)
* [ ] All RPCs marked `SECURITY DEFINER SET search_path = public`
* [ ] Tenant isolation pattern documented (above)
* [ ] Permission keys registered in PF-30 seed migration AND `src/platform/permissions/constants.ts`
* [ ] PIL hooks exported from `@/platform/training` (no direct cross-core imports)
* [ ] Cross-core matrix entry added to [CROSS\_CORE\_INTEGRATIONS.md](/architecture/integrations/CROSS_CORE_INTEGRATIONS) — **TODO**
* [ ] Event contracts registered in [EVENT\_CONTRACTS.md](/architecture/integrations/EVENT_CONTRACTS) — **TODO**

***

## References

* **Spec:** [GR-18 Competency Validation & Assessment Engine](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/specs/GR-18-competency-assessment-engine.md)
* **Plan:** [GR-18 Implementation Plan](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/plans/GR-18-competency-assessment-engine-PLAN.md)
* **Parent substrate:** [GR-02 Training & CEU Tracking](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/specs/GR-02-training-ceu-tracking.md)
* **Sibling enhancements:** GR-02-EN-01 (events), GR-02-EN-02 (PIL), GR-02-EN-03 (picklists)
* **Downstream consumers:** [GR-19 In-Service Compliance Monitor](https://github.com/Encore-OS/encoreos/blob/development/specs/gr/specs/GR-19-mandatory-inservice-compliance-monitor.md), HR-22 (LMS transcript per ADR-014)
* **Constitution:** §1 (Architecture), §4 (Security & PHI), §5.7 (No recursive RLS)
* **Templates:** `docs/architecture/integrations/INTEGRATION_CONTRACT_TEMPLATE.md`
