Skip to main content

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.

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

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”:
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”:
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”:
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 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

DependencyTypeRequest/Response / EventAuth / TenantRetry / Idempotency
PF-64ConsumptionLetterhead/template by orgOrg-scopedIdempotent by agreement + content
PF-10NotificationsPayloads aboveOrg + recipientBest-effort; optional idempotency key
PF-17ConfigField config for position_idOrg-scopedN/A
Platform SignaturesUI/hookDialog + callbackManager/employee/HRN/A
HR-01Datahr_employees, hr_positionsOrg + RLSStandard CRUD
HR-12Data/UIuseMyDocuments + pf_documentsOrg + employeeStandard query
HR-09DownstreamRead job_description_full; Phase 2: Copy from position (same-core read)Org-scopedN/A Phase 1; Phase 2 same-core

See also