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

# Job Descriptions & Agreements – Integration Contract

> This document formalizes every integration point for HR-21 (Job Descriptions & Agreements): platform dependencies (PF-64, PF-10, PF-17, Platform Signatures), H…

**Version:** 1.0.0\
**Created:** 2026-02-05\
**Last Updated:** 2026-02-05\
**Status:** ✅ Phase 1 Complete; Phase 2 Complete\
**Owner:** HR (Workforce & HRIS)\
**Spec Reference:** [HR-21-job-descriptions-agreements.md](https://github.com/Encore-OS/encoreos/blob/development/specs/hr/specs/HR-21-job-descriptions-agreements.md)

***

## Overview

This document formalizes every integration point for HR-21 (Job Descriptions & Agreements): platform dependencies (PF-64, PF-10, PF-17, Platform Signatures), HR-internal (HR-01, HR-12, hr\_positions), and downstream (HR-09). It defines request/response schemas, event payloads, authentication/authorization and tenant isolation, retry/idempotency, and PDF generation and document-signing security.

**Constitution Reference:** Section 1.3 (Integration Patterns). HR-21 integrates via Platform Integration Layer and platform capabilities only; no direct cross-core dependencies.

***

## 1. PF-64 (Document Templates / Letterhead)

### Purpose

Job description PDFs use organization letterhead and optional PF-64 document template for layout. Content comes from `hr_job_description_agreements.content_snapshot` and position/employee context.

### Contract Type

Consumption of PF-64 letterhead and optional template; no new PF-64 APIs. PDF generation Edge Function uses existing patterns (e.g. `generate-templated-pdf`, `generate-signed-pdf`).

### Request/Response (PDF generation Edge Function → letterhead/template)

**Input (internal to Edge Function):**

* `organization_id` (UUID) – tenant; used to resolve default letterhead and template.
* `content_snapshot` (JSONB) – from `hr_job_description_agreements.content_snapshot`; structure: `{ summary, responsibilities, qualifications, reporting?, physical? }`.
* Optional: `template_id` (UUID) – PF-64 template id when org uses a job-description template; otherwise letterhead-only layout.

**Letterhead resolution:**

* Query letterhead by `organization_id` and `is_default = true` (or equivalent); no direct RLS-protected table access from Edge Function – use service role or SECURITY DEFINER API that returns letterhead for org.

**Success flow:**

* PDF bytes generated; upload to storage; return `storage_path` and metadata for `pf_documents` insert.

**Error flow:**

* 403 if org has no letterhead/template access; 500 with sanitized message for rendering failures; no PHI in error payload.

### Authentication / Tenant isolation

* Edge Function invoked with JWT or service role. Resolve `organization_id` from agreement row (or from validated request body); never trust client-supplied `organization_id` without verifying user has access. All letterhead/template reads scoped by `organization_id`.

### Retry / Idempotency

* PDF generation: idempotent for same `agreement_id` + `content_snapshot`; caller can retry with same inputs. Store result in `pf_documents` and set `hr_job_description_agreements.generated_document_id` once; duplicate calls can overwrite or skip if document already exists (documented in implementation).

### PDF generation and storage security

* Generated PDF must be stored in a bucket and record created in `pf_documents` with correct `organization_id`, `custom_fields.employee_id`, and `document_kind: 'job_description'`. Access to the file must follow RLS on `pf_documents` and storage policies; only the employee (self) and users with org access and appropriate permissions can read.

### Migration / compatibility (multi-tenant)

* New orgs: ensure default letterhead exists or PDF generation degrades gracefully (e.g. no letterhead). Existing orgs: no schema change to PF-64; optional Phase 2 job-description template per org.

***

## 2. PF-10 (Notifications)

### Purpose

Notify manager when a job description is ready for signature; notify employee when ready to sign and when the signed PDF has been added to their file.

### Contract Type

Event-driven or direct call to platform notification API (PF-10). No new event channels required if PF-10 accepts in-app or email notifications by user/role.

### Request (HR-21 → PF-10)

**Notify manager – “Job description ready for your signature”:**

```typescript theme={null}
interface JobDescriptionReadyForManagerNotification {
  organization_id: string;
  recipient_user_id: string;  // manager's pf_profiles.id or auth user
  agreement_id: string;
  employee_id: string;
  position_title: string;
  document_kind: 'job_description_pending_manager';
}
```

**Notify employee – “Your job description is ready to sign”:**

```typescript theme={null}
interface JobDescriptionReadyForEmployeeNotification {
  organization_id: string;
  recipient_user_id: string;
  agreement_id: string;
  document_kind: 'job_description_pending_employee';
}
```

**Notify employee – “Your job description has been added to your file”:**

```typescript theme={null}
interface JobDescriptionAddedToFileNotification {
  organization_id: string;
  recipient_user_id: string;
  agreement_id: string;
  document_id: string;  // pf_documents.id
  document_kind: 'job_description_signed';
}
```

### Response / Success and error flows

* Success: notification queued or sent (per PF-10 contract).
* Error: 429 rate limit → retry with backoff; 4xx → do not retry; log and surface sanitized message.

### Authentication / Tenant isolation

* Caller (HR app or Edge Function) must have validated `organization_id` and that the recipient belongs to that org. No cross-tenant notification.

### Retry / Idempotency

* Notifications are best-effort; idempotency key by (agreement\_id, notification\_type, recipient) avoids duplicate in-app toasts; email may still duplicate on retry unless PF-10 supports idempotency keys.

### Migration / compatibility

* PF-10 must support targeting by user id and optional template keys. No change to PF-10 schema for Phase 1 if current API accepts recipient + message payload.

***

## 3. PF-17 (Field Configuration)

### Purpose

Add `position_id` to `hr_employees` field config (STANDARD\_FIELDS or org-specific) so the employee form shows a position dropdown.

### Contract Type

Configuration only. HR-21 consumes PF-17 field config; no new API. Field config drives which fields appear on the employee create/edit form.

### API / Data contract

* **Entity:** `hr_employees`.
* **Field:** `position_id` (UUID, FK to `hr_positions.id`).
* **Config:** Include in STANDARD\_FIELDS or in org-specific override so that the field is visible and editable where appropriate.
* **Lookup:** Position dropdown options from `hr_positions` filtered by `organization_id` and active status; list includes at least `id`, `title` (or equivalent), and optionally `job_description_full` for display.

### Request/Response

* No direct request/response; form reads field config and loads positions via existing data hooks (e.g. `usePositionList()` or equivalent) with `organization_id` from context.

### Authentication / Tenant isolation

* Field config and position list scoped by `organization_id`; RLS and defense-in-depth on all reads.

### Migration / compatibility

* New deployments: add `position_id` to default field config for `hr_employees`. Existing orgs: optional migration or admin UI to add field to config; existing employee records have `position_id` NULL until set.

***

## 4. Platform Signatures (SignatureDialog, useSignature)

### Purpose

Manager and employee sign the job description using the same pattern as offer letter and oversight session: SignatureDialog and useSignature for capture and storage of signature (e.g. signature record id and timestamp).

### Contract Type

UI and hook consumption from platform. No new platform API; HR-21 uses existing signature capture and optional storage of signature id in `hr_job_description_agreements` (manager\_signature\_id, employee\_signature\_id).

### Signature dialog flow

1. User opens sign page for agreement (manager or employee role).
2. App loads agreement (read-only) and shows content + SignatureDialog.
3. User signs; platform records signature (e.g. in signatures table or storage) and returns signature id/token.
4. App updates agreement: sets manager\_signed\_at / manager\_signature\_id or employee\_signed\_at / employee\_signature\_id and advances status (e.g. pending\_manager\_signature → pending\_employee\_signature → signed).

### Signature token exchange and storage

* **Token exchange:** If the platform uses a short-lived token for the signing session, it must be exchanged server-side or via a secure hook; no long-lived signature secrets in client.
* **Storage of signed docs:** Signed PDF is generated only after both signatures; PDF stored in `pf_documents` with link to employee; signature IDs stored on `hr_job_description_agreements` for audit.
* **Security:** Only the manager of the employee (or HR admin) can record manager signature; only the employee (self) can record employee signature; enforced by RLS and application checks before calling signature API.

### Request/Response (conceptual)

* **Open dialog:** No formal API; component receives agreement id and role.
* **On sign complete:** Callback receives signature id (and optionally timestamp); app then PATCHes agreement row with signature fields and status.

### Authentication / Authorization scopes

* User must be authenticated; authorization: manager for direct-report agreements or employee for own agreement (and HR admin for manager path). Scopes align with RLS on `hr_job_description_agreements`.

### Tenant isolation

* Agreement and signature actions scoped by `organization_id`; no cross-org access.

### Migration / compatibility

* No platform schema change; ensure SignatureDialog and useSignature accept org/tenant context if needed for multi-tenant isolation.

***

## 5. HR-01 (Employee Directory)

### Purpose

Employee and manager resolution; `hr_employees.position_id` and `job_title`; used when creating/editing agreements and when resolving “manager” for notifications and signature workflow.

### Contract Type

Data dependency on HR-01 tables and APIs. HR-21 reads `hr_employees` (and related profile/organization) for employee list, manager resolution, and position assignment.

### Request/Response (data shape)

* **Employee record (relevant fields):**\
  `id`, `organization_id`, `position_id`, `job_title`, `reports_to_id` (or equivalent for manager), and profile/user linkage for notifications and RLS.

* **Position record (from hr\_positions):**\
  `id`, `organization_id`, `title`, `description`, `job_description_full` (Phase 1). Used in position dropdown and as source for job description content when creating an agreement.

### Example (agreement creation)

* User selects employee and position.
* App loads `hr_employees` row for employee (ensure `position_id` and `job_title` can be updated).
* App loads `hr_positions` row for selected position to get `job_description_full` (and optional sections) for pre-filling `content_snapshot` or for display.
* Create `hr_job_description_agreements` with `employee_id`, `position_id`, `content_snapshot`, `status = 'draft'` or `'pending_manager_signature'`.

### Authentication / Tenant isolation

* All reads and writes scoped by `organization_id`; RLS on `hr_employees` and `hr_positions`; defense-in-depth in mutations.

### Migration / compatibility

* `hr_positions.job_description_full` added in HR-21 migration; existing positions have NULL until edited. `hr_employees.position_id` may already exist; PF-17 config makes it visible on form.

***

## 6. HR-12 (My Documents)

### Purpose

Extend useMyDocuments (and UI) so documents with `document_kind: 'job_description'` and link to current employee appear in My Documents (personnel file).

### Contract Type

Consumption of HR-12 My Documents list and `pf_documents` query pattern. HR-21 writes `pf_documents` rows when a job description PDF is generated; HR-12 (or shared hook) includes those in the employee’s document list.

### Request/Response (useMyDocuments extension)

* **Query:** Same as existing My Documents: filter by current user’s employee id (or profile) and optionally by `document_kind`.
* **Include:** `pf_documents` rows where `custom_fields.employee_id` = current employee id and `custom_fields.document_kind` = `'job_description'` (or equivalent tag).
* **Record shape (pf\_documents):**\
  `id`, `organization_id`, `name`, `storage_path`, `file_type`, `custom_fields` (e.g. `{ employee_id, document_kind: 'job_description', agreement_id?, position_title? }`), `created_at`, etc. RLS on `pf_documents` enforces tenant and, if applicable, employee-scoped access.

### Success / Error flows

* List returns combined personal uploads and job description PDFs; errors from Supabase follow standard error handling; sanitize user-facing messages.

### Authentication / Tenant isolation

* User sees only documents for their org and (for employee self-service) their own employee\_id in custom\_fields. RLS and application filters enforce this.

### Migration / compatibility

* Existing My Documents unchanged; extension is additive (new document\_kind and optional filters). Multi-tenant: each org’s documents isolated by `organization_id`; employee-scoped by `custom_fields.employee_id`.

***

## 7. HR-09 (ATS – Consumer / Downstream)

### Purpose

Future “Copy from position job description” when creating a job posting. Out of scope for Phase 1; contract defined for compatibility.

### Contract Type

Downstream consumption of position job description data. HR-09 (or ATS feature) will read `hr_positions.job_description_full` (and optionally agreement content) when creating a job posting.

### Request/Response (future)

* **Read:** `hr_positions.id`, `job_description_full`, `organization_id` for a given position.
* **Authorization:** Same org and ATS permission; no new API in Phase 1.

### Event payload (if used later)

* Not required for Phase 1. If ATS subscribes to “position updated” events, payload could include `position_id`, `organization_id`, `job_description_full` (or a flag that it changed). Version and compatibility notes: when HR-09 implements “copy from position”, use `hr_positions.job_description_full` and document any versioning of that field.

### Migration / compatibility

* Phase 1 adds `job_description_full`; ATS can adopt when ready. Multi-tenant: ATS already scoped by org; no additional isolation required.

***

### Phase 2: HR-09 Copy from position

**Spec Reference:** [HR-21-PHASE-2-EXPANSION.md](https://github.com/Encore-OS/encoreos/blob/development/specs/hr/specs/HR-21-PHASE-2-EXPANSION.md)

**Purpose:** When creating or editing a job posting in HR-09, the user can choose “Copy from position” to pre-fill the posting description from a selected position’s full job description. Same-core read; no new API or event.

**Data source:** `hr_positions.job_description_full` (and optionally `hr_positions.title` for display). Phase 2 may also add `job_description_updated_at` / `job_description_version`; consumer may use these for “last updated” display.

**Consumer:** HR-09 job posting create/edit form. User selects a position (from active `hr_positions` for the org); client or hook reads `hr_positions` row by `id` and `organization_id`; description field is pre-filled from `job_description_full`. No server-to-server call; same Supabase RLS and `organization_id` context as rest of HR.

**Pattern:** Same-core read. HR-09 code (or shared HR data layer) queries `hr_positions` with `organization_id` filter; RLS enforces tenant isolation. No direct cross-core import; both HR-21 and HR-09 are HR core. No new event contract; no new API endpoint.

**Authentication / Tenant isolation:** Caller must have org access; query uses `organization_id` from context. RLS on `hr_positions` enforces isolation. No PHI in job description content beyond role titles; document access logging per HR-12 pattern applies to generated PDFs only, not this read path.

***

## 8. Cross-cutting: PDF generation and document-signing security

### PDF generation

* **Current (Phase 1/2):** Client-triggered via `useJobDescriptionPdf` hook using `useGenerateTemplatedPdf` from `@/platform/templates`. Letterhead resolved from org default or `hr_module_settings.job_description_pdf_template_id` (Phase 2 T11).
* **Trigger:** When agreement status becomes `signed` (both manager and employee signed), the client generates the PDF.
* **Input:** `agreement_id`, `content_snapshot`, `organization_id`, `employee_id`, position title (for display).
* **Steps:** Resolve letterhead (PF-64); render PDF; upload to storage; insert `pf_documents` with `organization_id`, `custom_fields.employee_id`, `document_kind: 'job_description'`, and optional `agreement_id`; update `hr_job_description_agreements.generated_document_id`.
* **Security:** Edge Function or backend only for server-side path; validate agreement belongs to org and user has access; never expose internal paths or keys to client.

### Async PDF generation (Future — Documented per T10)

Two options are documented for future server-side PDF generation when the client-triggered path is insufficient:

**Option A: Database trigger (recommended future default)**

* A `AFTER UPDATE` trigger on `hr_job_description_agreements` fires when `NEW.status = 'signed' AND OLD.status <> 'signed'`.
* Trigger calls a SECURITY DEFINER function that invokes an edge function (e.g., `generate-job-description-pdf`) via `pg_net` HTTP extension.
* **Prerequisite:** A server-side edge function that replicates `buildJobDescriptionContent` + templated PDF generation must be created first. This edge function does not yet exist.
* **Error handling:** Log server-side; show "PDF could not be generated" in agreement view; optional retry/support hint.
* **UX:** Help text "Signed PDF will appear in My Documents shortly." on agreement detail page.

**Option B: Background job / queue**

* A scheduled edge function (cron) polls for agreements with `status = 'signed'` and `generated_document_id IS NULL`.
* Generates PDF for each, updates `generated_document_id`.
* **Advantage:** No `pg_net` dependency; simpler error handling and retry.
* **Disadvantage:** Latency (depends on cron interval).

**Current status:** Neither option is implemented. Phase 1/2 uses client-triggered PDF generation, which is sufficient for the current scale. The async path will be implemented when a server-side PDF edge function is created.

### Signature dialog flow and signature token exchange

* **Flow:** Manager signs first → status to pending\_employee\_signature; employee signs → status to signed.
* **Token exchange:** If platform uses tokens for signing, exchange server-side; store only signature id (and timestamp) on agreement.
* **Storage of signed docs:** Final PDF generated after both signatures; stored in `pf_documents` linked to employee; access via RLS and HR-12 My Documents.

### Tenant isolation (all integrations)

* Every integration point must enforce `organization_id` and, where applicable, employee or manager scope. RLS on `hr_job_description_agreements`, `hr_employees`, `hr_positions`, and `pf_documents` is the baseline; application code must also filter by `organization_id` for mutations (defense-in-depth).

***

## 9. Summary table

| Dependency          | Type          | Request/Response / Event                                                  | Auth / Tenant       | Retry / Idempotency                   |
| ------------------- | ------------- | ------------------------------------------------------------------------- | ------------------- | ------------------------------------- |
| PF-64               | Consumption   | Letterhead/template by org                                                | Org-scoped          | Idempotent by agreement + content     |
| PF-10               | Notifications | Payloads above                                                            | Org + recipient     | Best-effort; optional idempotency key |
| PF-17               | Config        | Field config for position\_id                                             | Org-scoped          | N/A                                   |
| Platform Signatures | UI/hook       | Dialog + callback                                                         | Manager/employee/HR | N/A                                   |
| HR-01               | Data          | hr\_employees, hr\_positions                                              | Org + RLS           | Standard CRUD                         |
| HR-12               | Data/UI       | useMyDocuments + pf\_documents                                            | Org + employee      | Standard query                        |
| HR-09               | Downstream    | Read job\_description\_full; Phase 2: Copy from position (same-core read) | Org-scoped          | N/A Phase 1; Phase 2 same-core        |

***

## See also

* [HR-21 Spec](https://github.com/Encore-OS/encoreos/blob/development/specs/hr/specs/HR-21-job-descriptions-agreements.md)
* [HR-21 Phase 2 Expansion](https://github.com/Encore-OS/encoreos/blob/development/specs/hr/specs/HR-21-PHASE-2-EXPANSION.md)
* [Integration Contract Template](https://github.com/Encore-OS/encoreos/blob/development/specs/_templates/INTEGRATION_CONTRACT_TEMPLATE.md)
* [EVENT\_CONTRACTS.md](/architecture/integrations/EVENT_CONTRACTS), [API\_CONTRACTS.md](/architecture/integrations/API_CONTRACTS)
* [PLATFORM\_INTEGRATION\_LAYERS.md](/architecture/integrations/PLATFORM_INTEGRATION_LAYERS)
