{
  "openapi": "3.1.0",
  "info": {
    "title": "GroundIT — People",
    "version": "0.1.0",
    "description": "The employee master and its lifecycle (`employees`), the guided staged onboarding of a new hire (`onboarding_flows`), the self-service profile (`employee_profiles`, `personal_info`, `contacts`, `dependents`, `bank_accounts`), the company directory & Profile 360 (`org_directory`), the Virtual ID offline credential (`virtual_ids`), app settings & DPDP/PDPL privacy (`app_settings`, `privacy_requests`), and the engagement layer (`engagement_moments`). Grounded in fsd-docs/02-people-fsd.md (PPL-S01-S20), db-docs/03-people.md, and features-docs/02-people-features.md.\n",
    "license": {
      "name": "Proprietary — GroundIT/Sysmedac internal"
    }
  },
  "servers": [
    {
      "url": "/api/v1"
    }
  ],
  "security": [
    {
      "bearerJWT": []
    }
  ],
  "tags": [
    {
      "name": "people",
      "description": "Cross-cutting People module tag."
    },
    {
      "name": "employee",
      "description": "Employee master & lifecycle (PPL-F01)."
    },
    {
      "name": "employee-import",
      "description": "Bulk employee import — upload, validate, preview, commit (PPL-F01, #631)."
    },
    {
      "name": "pending-hire",
      "description": "Recruit handovers parked for admin review at the plan cap (PPL-F01)."
    },
    {
      "name": "onboarding",
      "description": "Guided, resumable staged onboarding of a new hire (PPL-F07)."
    },
    {
      "name": "employee-profile",
      "description": "Self-maintained profile facet (PPL-F01/F02)."
    },
    {
      "name": "personal-info",
      "description": "PII / personal & statutory identity (PPL-F02)."
    },
    {
      "name": "identity-document",
      "description": "Market-gated statutory identity documents (PPL-F02, XC-F01)."
    },
    {
      "name": "contact",
      "description": "Emergency contacts (PPL-F02)."
    },
    {
      "name": "dependent",
      "description": "Dependents (PPL-F02)."
    },
    {
      "name": "bank-account",
      "description": "Salary bank accounts (PPL-F02)."
    },
    {
      "name": "org-directory",
      "description": "Company directory & Profile 360 (PPL-F03)."
    },
    {
      "name": "virtual-id",
      "description": "Virtual ID QR+NFC credential (PPL-F04)."
    },
    {
      "name": "app-setting",
      "description": "App settings control panel (PPL-F05)."
    },
    {
      "name": "privacy-request",
      "description": "DPDP/PDPL data-subject-rights requests (PPL-F05)."
    },
    {
      "name": "engagement-moment",
      "description": "Birthdays / anniversaries / milestones (PPL-F06)."
    }
  ],
  "paths": {
    "/employees/me": {
      "get": {
        "operationId": "people.employee.get_me",
        "summary": "Get my own employment record",
        "description": "The employee's at-a-glance employment record (fsd 02 PPL-S01; hosted also on the Profile-tab identity card, XC-S10). It also carries the caller's own `date_of_birth` and `mobile_number`, resolved from `people.personal_info`; those self-only PII fields are deliberately absent from directory/admin employee projections. Read-only — lifecycle and org placement are HR-driven (`people.employee.update` and the lifecycle actions below), never editable here. db 03 §1 `people.employees`, plus the `_name` display projections (`designation_name`/`department_name`/`manager_name`/`work_location_name`/`grade_name`/ `org_unit_name`/`legal_entity_name`) resolved by a service-side join so the card never has to make a second round trip per FK. The response also carries the computed, read-only `mobile_location_attestation_required` policy result. It is `false` when `employees.allow_mobile_attendance_anywhere` is true, when an explicit ALLOWED_LOCATIONS assignment is unrestricted, or when shared attendance policy exempts `REMOTE`, `HYBRID` or `FIELD` work modes, or a `REMOTE` work location; otherwise it is `true`. The value is evaluated fresh for this read and is never writable by an employee.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.get_me",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S01",
          "XC-S10"
        ],
        "x-touches-entities": [
          "people.employees",
          "people.personal_info",
          "org.designations",
          "org.departments",
          "org.grades",
          "org.org_units",
          "org.work_locations",
          "org.legal_entities"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's own employee record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MyEmployee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees": {
      "get": {
        "operationId": "people.employee.list",
        "summary": "List employees (HR master grid)",
        "description": "HR's master employee list (fsd 02 PPL-S13) with search, org-placement filters, and status chips. Sort whitelist: `employee_no`, `full_name`, `date_of_joining`, `status`. db 03 §1 `people.employees`.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.list",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employees",
          "org.departments",
          "org.designations",
          "org.work_locations",
          "work.team_members"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Free-text match over `full_name` / `employee_no`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/EmployeeStatus"
            }
          },
          {
            "name": "employment_type",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/EmploymentType"
            }
          },
          {
            "name": "work_mode",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/WorkMode"
            }
          },
          {
            "name": "department_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "name": "designation_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "name": "manager_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "name": "legal_entity_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "name": "work_location_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "name": "team_id",
            "in": "query",
            "required": false,
            "description": "Squad filter (#1885). A `work.teams` uuid narrows to that squad's live members (`work.team_members` with no `left_at`); the literal `none` narrows to people on no ACTIVE squad. Anything else is a 422.\n",
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/Uuid"
                },
                {
                  "type": "string",
                  "enum": [
                    "none"
                  ]
                }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of employees.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Employee"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "people.employee.create",
        "summary": "Create an employee directly (HR direct-hire)",
        "description": "The **direct-create variant** of PPL-S13 — HR's \"Add employee\" wizard (design-docs/02 §3, recorded on the PPL-S13 Figma line). **Create-from-handover stays the canonical path**: `recruit.onboarding_checklist.complete` finishes the hire on the recruiter's own screen through `people`'s in-process completion seam, stamping `candidate_id` (#1659; it was `recruit.onboarding_checklist.provision`, which only opened a guided flow for somebody else to finish). This operation is the second, explicitly HR-authored door for hires that never passed through recruiting (founders, TUPE/acquisition intake, back-dated regularisation) — it carries **no `candidate_id`**, which is exactly how the two provenances stay distinguishable in the master.\n\nMints `people.employees` **plus** its 1:1 `people.employee_profiles` row (`work_anniversary` seeded from `date_of_joining`) in one transaction, append-only audited (XC-F06), and emits `people.employee.activated` — the same lifecycle event the handover path emits, so every downstream module (attend, leave, work, pay, tax, comply) reacts identically regardless of provenance.\n\n**`employee_no`** may be supplied by the caller (HR importing an existing code); when omitted it is generated per tenant as `<prefix><zero-padded sequence>` — prefix/width read from the `people.employee_no` key of `admin.tenant_config`, defaulting to `EMP-` and width 4 (db 03 §1). Uniqueness of `(tenant_id, employee_no)` and of `(tenant_id, work_email)` is enforced by the database and surfaced as a `422` `uniqueness-business` field error, never a 500.\n\n**Plan limits:** this is the operation the `maxEmployees` numeric limit gates (ADR 0009 §e); it therefore declares `402`. The limit check itself is per-resource and evaluated by the handler against the platform-fed `TenantCache` — never decided in-product.\n\n**This operation produces a usable self-service login** (issue #1411, completed by #1653). It mints the `xc.identities` `EMPLOYEE` principal (`INVITED`) from `work_email` and issues the tenant's baseline, `is_system`, `SELF`-scoped `EMPLOYEE` role through admin's own writer — the same two acts guided onboarding runs inside its hire transaction, so the two doors no longer diverge on permissions.\n\n**`work_email` is therefore REQUIRED**, and its absence is a `422` naming `/work_email` rather than a silent skip: a create that recorded `NO_WORK_EMAIL` answered `201`, produced a normal-looking directory row and left a joiner with no way into the product that nobody discovered for days. A caller that genuinely must create without an address — a bulk roster load whose corporate mailboxes are not cut yet — sends **`provision_ess: false`**; the `null` → value resume seam on `people.employee.update` finishes those records later.\n\n**`provision_ess: false` suppresses the `422` and NOTHING ELSE.** It is not \"do not provision\": when the body carries an address, the account is minted regardless of the flag. The address always decides. Anything else is a dead end rather than a deferral — a roster row born WITH an address and no principal can never be repaired by the resume seam's original arm, which is gated on the address *arriving*, and that arrival has already happened.\n\n**No sign-in is created for somebody the workspace has stood down.** Provisioning requires `people.employees.status` in `{JOINING, ACTIVE, ON_LEAVE}` — the predicate the retired invitation seam applied before it would issue anybody a credential. Unreachable through this door today (a hire is born `ACTIVE` or `JOINING`) and enforced anyway, because the guard's job is to hold when a later birth status is added. On `people.employee.update` the same rule is a `422` on `/work_email`.\n\n**The Keycloak subject stays ASYNCHRONOUS, deliberately** (owner ruling, #1653): it is written by the `people.ess_keycloak_provision` outbox job plus its daily sweep, and provisioned lazily by `POST /auth/identify` on first contact. So the response's `ess_access` is normally `PENDING_REALM`, which is a healthy hire and NOT a defect — the employee can request a sign-in code immediately. `BLOCKED` is the state that needs somebody, and it names its `reason`.\n\n**No invitation is issued.** `POST /employees/{id}/ess-invitation` and its `people.employee .invite_to_ess` token were removed in #1638: an `EMPLOYEE` principal is adopted by a PROVEN OTP sign-in against the address on the identity, so the claim link was a second, weaker credential for a door that already opened without one.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.create",
        "x-realizes-features": [
          "PPL-F01",
          "PPL-F07"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employees",
          "people.employee_profiles",
          "xc.identities",
          "admin.member_grants"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee.activated",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeCreateInput"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimum viable hire — employee_no generated",
                  "value": {
                    "full_name": "Aanya Kapoor",
                    "employment_type": "FULL_TIME",
                    "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a34",
                    "date_of_joining": "2026-08-03"
                  }
                },
                "placed": {
                  "summary": "Fully placed hire with an HR-supplied code",
                  "value": {
                    "employee_no": "EMP-0421",
                    "full_name": "Aanya Kapoor",
                    "work_email": "aanya.kapoor@groundit.example",
                    "employment_type": "FULL_TIME",
                    "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a34",
                    "department_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a35",
                    "designation_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a36",
                    "grade_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a37",
                    "org_unit_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a38",
                    "work_location_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a39",
                    "manager_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a3a",
                    "date_of_joining": "2026-08-03",
                    "date_of_confirmation": "2027-02-03"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created employee record, plus `ess_account` (what provisioning did) and `ess_access` (the three-state verdict the hire computed from it — `READY` · `PENDING_REALM` · `BLOCKED`, #1653).\n",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeCreated"
                },
                "examples": {
                  "created": {
                    "summary": "Created with a generated employee_no",
                    "value": {
                      "id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a40",
                      "employee_no": "EMP-0421",
                      "full_name": "Aanya Kapoor",
                      "work_email": "aanya.kapoor@groundit.example",
                      "status": "ACTIVE",
                      "status_reason": null,
                      "status_changed_at": "2026-07-25T09:14:22.318Z",
                      "employment_type": "FULL_TIME",
                      "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a34",
                      "department_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a35",
                      "designation_id": null,
                      "grade_id": null,
                      "org_unit_id": null,
                      "work_location_id": null,
                      "manager_id": null,
                      "candidate_id": null,
                      "rehired_from_employee_id": null,
                      "date_of_joining": "2026-08-03",
                      "date_of_confirmation": null,
                      "date_of_exit": null,
                      "version": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "description": "Field validation failed. Since #1653 this includes the ESS gate: a create that requests provisioning (the default) and carries no `work_email` is refused with `code: VALIDATION_FAILED` and `errors[0].pointer: /work_email`, so the wizard can put the message back on the field that produced it. `PATCH /employees/{id}` answers the same shape with `rule: state` when the address is supplied for an employee who is suspended, exited or an alumnus. Also the existing uniqueness failures on `employee_no` / `work_email`.\n",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationProblem"
                }
              }
            }
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/status-counts": {
      "get": {
        "operationId": "people.employee.status_counts",
        "summary": "Get employee counts grouped by status",
        "description": "Returns exact counts for each employee status (ACTIVE, ON_LEAVE, SUSPENDED, EXITED, ALUMNI) for the directory status chips. No pagination — this is a lightweight grouped count query so chip counts are never a floor.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.list",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Employee counts by status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "required": [
                    "ACTIVE",
                    "ON_LEAVE",
                    "SUSPENDED",
                    "EXITED",
                    "ALUMNI"
                  ],
                  "properties": {
                    "ACTIVE": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "ON_LEAVE": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "SUSPENDED": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "EXITED": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "ALUMNI": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}": {
      "get": {
        "operationId": "people.employee.get",
        "summary": "Get an employee's employment record (HR/manager)",
        "description": "Base employment-record read for HR and the reporting manager (fsd 02 PPL-S13 row detail; the Work tab of PPL-S14). db 03 §1 `people.employees`, plus the same `_name` display projections as `people.employee.get_me` (`designation_name`/`department_name`/`manager_name`/`work_location_name`/ `grade_name`/`org_unit_name`/`legal_entity_name`) — one shared read path.\n\n**Carries the ESS sign-in facet** (`ess_account`, issue #1411, widened by #1638), which is what `PPL-S14`'s read-only access card renders. Additive and under the existing token: the facts it holds — does a principal exist, does the realm know them, have they signed in — are about the employee `people.employee.get` already grants, and gating them behind a separate token would put the section behind a permission the page rendering it does not need. It is deliberately NOT on `people.employee.get_me`: an employee reading their own record is signed in, so the question only arises when looking at somebody else's.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.get",
        "x-realizes-features": [
          "PPL-F01",
          "PPL-F07"
        ],
        "x-screens": [
          "PPL-S13",
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.employees",
          "org.designations",
          "org.departments",
          "org.grades",
          "org.org_units",
          "org.work_locations",
          "org.legal_entities",
          "xc.identities"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The employee record, plus its ESS sign-in facet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeWithEssAccount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "people.employee.update",
        "summary": "Update an employee's org placement / employment details",
        "description": "HR edits designation/department/grade/org-unit/work-location/manager/employment_type/work_mode and the `allow_mobile_attendance_anywhere` attendance exemption (fsd 02 PPL-S13). Does **not** change `status` — use the lifecycle actions below.\n\n**This is also the transfer operation.** A department / org-unit / work-location / manager move is an org-placement edit, not a separate verb: there is deliberately no `people.employee.transfer` token. When an approval workflow must govern the move, it is authored as an `engage.promotions` case (`PROMOTION` · `LATERAL` · `ROLE_CHANGE`, db 11 §-promotions) whose `EFFECTED` step reaches `people` **by event** — `engage` never writes a `people` table. This endpoint is the unmediated HR edit.\n\nEmployee rows are minted either by the recruitment onboarding handover (`REC-F07`, the canonical path) or by `people.employee.create` (the HR direct-hire door above). `manager_id <> id` is enforced (no self-report, db 03 §1 check constraint), as is the absence of a reporting cycle. Emits `people.employee.updated` carrying the changed field names, which feeds the `org_directory` projection.\n\n**`work_email` is editable here, and setting it for the first time provisions the ESS account (#800).** Before that issue this operation could not touch the corporate address at all: `people.employee.create` and the guided flow's `PLACEMENT` stage wrote it and nothing could ever change it, so an employee hired before IT cut their mailbox had no address for good — and, since #800 made the address the key an ESS principal is minted on, no way to sign in either. A hire that completed with `ess_account.skipped_reason: NO_WORK_EMAIL` is repaired **here**: when `work_email` goes from `null` to a value, the same transaction mints the `xc.identities` `EMPLOYEE`-class principal `people.onboarding.complete` would have minted, `INVITED` and claimable by that address, and emits `people.employee.ess_account_provisioned` alongside the ordinary `people.employee.updated`. Since #1653 the same arrival also issues the tenant's baseline `EMPLOYEE` role, because an account with no grant signs in to an empty portal. No extra permission token is required for either, for the same reason neither needs one on the completion path: an ESS account carries no authority of its own, and the baseline role is the `SELF`-scoped one ADR 0064 §(b) governs. **`people.employee.invite_to_ess` was resolved here until #1638** so the resume could also mail a claim link; there is no link to mail.\n\n**The seam has TWO arms** (#1638). The first is the original `null` → value TRANSITION. The second keys on the ABSENCE OF A PRINCIPAL — an employee who has an address and no `xc.identities` row is provisioned on the next save of the record, whatever the PATCH touched. Without it, a row born with an address and no principal (a `provision_ess: false` roster load before this fix, a backfill gap, a provisioning failure) is permanently unreachable: the address never arrives again, so a transition-only seam can never fire. The common case short-circuits on the first arm and pays nothing.\n\n**Neither arm creates a sign-in for somebody who has left.** `people.employees.status` must be in `{JOINING, ACTIVE, ON_LEAVE}`. A PATCH that SUPPLIES an address for anybody else is refused with a `422` on `/work_email` — it is the \"Grant access\" fix-up and deserves an answer — while the absence-keyed arm simply stays silent, because refusing an ordinary transfer of an exited employee over a sign-in nobody asked about would be a `422` on an unrelated edit.\n\n**A CORRECTION deliberately does NOT re-point a live identity.** Neither arm can: `provisionWithinTx` resumes an existing principal and never rewrites its address. An employee whose address is being fixed already has a principal, and `xc.identities.email` is the login key the first-sign-in path adopts on. Moving it because somebody edited a profile field would let `people.employee.update` re-address a live account to a mailbox its holder may not control — an account takeover in the shape of a directory tidy-up. So the two facts are allowed to diverge and re-pointing stays with the deliberate identity-lifecycle operations that can demand their own authority for it (security 03 §4).\n\n`(tenant_id, work_email)` uniqueness is enforced by the database and is reachable from this UPDATE, not only from the INSERT: a colleague's address surfaces as a `422` `uniqueness-business` field error pointing at `/work_email`, never a bare `23505`.\n\n**`employee_no` is editable here since #1652, and it is the one field on this door that needs a token of its own.** Before that issue the employee CODE was accepted on `POST /employees` and absent from the update allow-list, so a code typed wrongly at hire — a typo, a duplicated series, a code from the wrong legal entity — was permanent: a PATCH carrying it was refused `422` *\"this property is not allowed\"*, and no other door could move it (`POST /employees/{id}/complete-pending-hire` takes only `decision_note`). The only remedy was to soft-delete the person and re-hire them, which severed their history AND burnt the correct code, because the unique index had no liveness predicate.\n\n**Gated on `people.employee.create`, not on `people.employee.update`.** A request carrying `employee_no` from a principal who does not hold the MINT token is refused `403 TOKEN_DENIED` naming it, whole-request, before anything is written — never silently dropped, because a discarded code and an accepted one are indistinguishable to the person who typed it until the next payslip. Every other field on this door stays with `people.employee.update` alone, so a transfer-only role is unaffected. **No new token was catalogued: on the seeded role catalogue `people.employee.create` is held by exactly `HR_ADMIN` and `OWNER`, which is the intended audience, so no tenant needs an RBAC re-seed for this.**\n\n**Prospective only, and audited both ways.** Payslips, bank-advice files and an already-signed Virtual ID payload keep the code they were minted with — the change is not retroactive and nothing already issued is rewritten. The `people.employee.updated` audit row carries the old and the new code, and the same transaction opens a dated `people.position_history` period reasoned `CORRECTION` whose `notes` state old → new in words, so a payslip issued under the previous code stays explicable from the relation payroll already reads. A code correction moves no placement axis, so the period is `CORRECTION` rather than `TRANSFER`; a PATCH that moves a placement axis AS WELL keeps the placement reason and carries the note.\n\n`(tenant_id, employee_no)` uniqueness is enforced by the database and reachable from this UPDATE exactly as `work_email` is: a clash surfaces as a `422` `uniqueness-business` field error pointing at `/employee_no`. **Since migration `0224` that index carries `WHERE deleted_at IS NULL`**, matching its work-email sibling — so the clash is only ever with a LIVE employee, and a code released by a soft-deleted record is free to reuse. The pre-#1652 error copy (\"…and can never be reused\") described the index as it then was and is no longer accurate.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.update",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employees",
          "people.position_history",
          "xc.identities"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee.updated",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated employee record.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}/mark-on-leave": {
      "post": {
        "operationId": "people.employee.mark_on_leave",
        "summary": "Put an employee on long leave (ACTIVE → ON_LEAVE)",
        "description": "Lifecycle transition `ACTIVE → ON_LEAVE` (still employed, db 03 §1 Lifecycle). **Employment status only.** `people.employees.status = 'ON_LEAVE'` is the HR-set *long leave* state — sabbatical, extended medical, unpaid absence — and is displayed as \"On long leave\". It is NOT \"on approved leave today\": that fact lives in `attend.attendance_records.status` / `attend.leave_day_marks`, is written by the leave-approval → day-materializer chain, and is surfaced by the Attendance module. This operation therefore files no leave application, moves no leave balance and marks no attendance day, and it takes no date range — the state lasts until `people.employee.reactivate`. Approving leave never sets this status. `status_reason` is **required and non-empty** (422 `/status_reason` otherwise): the audit row is the only record of why the employee is away. `date_of_exit` is exit-only and is rejected here (422 `/date_of_exit`). Append-only audited (XC-F06); the emitted lifecycle event is a notification with no local jobs-tier consumer by design.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.mark_on_leave",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee.marked.on.leave",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeLongLeaveInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Employee moved to ON_LEAVE (long leave).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}/reactivate": {
      "post": {
        "operationId": "people.employee.reactivate",
        "summary": "Reactivate an employee (from ON_LEAVE or SUSPENDED)",
        "description": "Lifecycle transition `ON_LEAVE → ACTIVE` or `SUSPENDED → ACTIVE` (db 03 §1 Lifecycle). Emits `people.employee.activated`, the same event named in db 03 §1. Append-only audited (XC-F06).\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.reactivate",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee.activated",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeStatusActionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Employee reactivated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}/suspend": {
      "post": {
        "operationId": "people.employee.suspend",
        "summary": "Suspend an employee",
        "description": "Lifecycle transition `ACTIVE → SUSPENDED` — access withheld pending review (db 03 §1). Cascades a Virtual ID revoke (`people.virtual_ids`, PPL-F04) in the same transaction (same schema — no cross-schema write). Append-only audited (XC-F06); emits `people.employee.suspended`.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.suspend",
        "x-realizes-features": [
          "PPL-F01",
          "PPL-F04"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employees",
          "people.virtual_ids"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee.suspended",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeStatusActionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Employee suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}/exit": {
      "post": {
        "operationId": "people.employee.exit",
        "summary": "Exit an employee",
        "description": "Lifecycle transition `ACTIVE → EXITED` — separated, record retained (db 03 §1). This console owns the people-master status only; exit-process detail (clearance, F&F) lives in `10-engage-exit` (`exit.*`). Cascades a Virtual ID revoke. Append-only audited (XC-F06); emits `people.employee.exited`.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.exit",
        "x-realizes-features": [
          "PPL-F01",
          "PPL-F04"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employees",
          "people.virtual_ids"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee.exited",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeExitInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Employee exited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}/convert-to-alumni": {
      "post": {
        "operationId": "people.employee.convert_to_alumni",
        "summary": "Convert an exited employee to alumni (post full-&-final)",
        "description": "Lifecycle transition `EXITED → ALUMNI` — ex-employee joins the alumni network (`engage`, db 03 §1 Lifecycle), performed after full-&-final settlement completes. Append-only audited (XC-F06).\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.convert_to_alumni",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee.alumni.activated",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeStatusActionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Employee converted to ALUMNI.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/pending-hires": {
      "get": {
        "operationId": "people.pending_hire.list",
        "summary": "List parked recruit handovers awaiting review",
        "description": "The admin review queue for recruit→employee handovers that could **not** create an employee because the workspace was at or over its `maxEmployees` plan limit (GAP-08, closed by the 2026-07-26 \"queue for review\" product decision). Hosted on PPL-S13's *Parked handovers* region, beside the master grid that owns both create paths.\n\nA parked row consumes **no seat** — the `people.employees` row was never written, so `platform.tenant.get_usage` keeps reporting the true headcount and the workspace is not silently over plan. The row retains the whole handover payload (`employee_no`, `full_name`, `employment_type`, `legal_entity_id`, `date_of_joining`, `work_email`, `candidate_id`) so the hire can be completed later without re-fetching anything from `recruit`. `parked_headcount`/`parked_limit` snapshot the numbers the refusal was decided on, which may differ from today's.\n\nSort whitelist: `created_at` (default, descending), `date_of_joining`, `full_name`. Resolved rows (`COMPLETED`/`CANCELLED`) stay listable so the decision remains visible from the surface that took it. db 03 §1.1 `people.pending_hires`.\n",
        "tags": [
          "people",
          "pending-hire"
        ],
        "x-token": "people.pending_hire.list",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.pending_hires"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/PendingHireStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of parked hires.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PendingHire"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/pending-hires/{id}/complete": {
      "post": {
        "operationId": "people.pending_hire.complete",
        "summary": "Complete a parked hire (create the employee)",
        "description": "Finish a handover that was parked at the plan cap: mints `people.employees` **plus** its 1:1 `people.employee_profiles` row from the retained payload, emits the same `people.employee.activated` every other hire path emits, stamps the parked row `COMPLETED` with `employee_id`/`decided_by`/ `decided_at`, and writes the append-only audit record (XC-F06) — all in one transaction.\n\n**The `maxEmployees` check is re-run here**, inside that transaction, through the same kernel `people.employee.create` uses. A parked row proves nothing about today — the freed seat may have been taken by another hire in the meantime — so a workspace still at its cap gets `402 PLAN_LIMIT_EXCEEDED` with the current count and cap in `detail`, and the row stays `PENDING`. That is what makes the parking bay a **queue and not a bypass**.\n\nAlready-resolved rows answer `409 STATE_TRANSITION_INVALID`; concurrent completers serialize on the row so only one can mint. db 03 §1.1 `people.pending_hires`.\n",
        "tags": [
          "people",
          "pending-hire"
        ],
        "x-token": "people.pending_hire.complete",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.pending_hires",
          "people.employees",
          "people.employee_profiles"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee.activated",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PendingHireDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The resolved parked hire, carrying the `employee_id` it created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingHire"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/pending-hires/{id}/cancel": {
      "post": {
        "operationId": "people.pending_hire.cancel",
        "summary": "Cancel a parked hire (no employee is created)",
        "description": "Close a parked handover that is not going ahead — offer withdrawn, candidate declined, or a duplicate handover. Stamps the row `CANCELLED` with `decided_by`/`decided_at` and an optional note, creates **no** employee, and consumes **no** seat. Append-only audited (XC-F06).\n\nBecause the partial-unique `(tenant_id, candidate_id)` index covers only live rows, a cancelled candidate can be handed over again later on a fresh row. Already-resolved rows answer `409 STATE_TRANSITION_INVALID`. db 03 §1.1 `people.pending_hires`.\n",
        "tags": [
          "people",
          "pending-hire"
        ],
        "x-token": "people.pending_hire.cancel",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.pending_hires"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PendingHireDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled parked hire.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingHire"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/me/attendance-locations": {
      "get": {
        "operationId": "people.employee.get_my_attendance_locations",
        "summary": "Read my effective mobile attendance location policy and boundaries",
        "description": "Self-scoped mobile setup read. The response combines the employee's configured named location assignments with the effective policy used for mobile IN and OUT. `allow_mobile_attendance_anywhere` is the HR/Admin toggle; when it is `true`, the mobile client must not request location permission, read GPS, or run a geofence check. The assignments remain returned so the HR configuration can be restored when the toggle is turned off. `assigned_geofences` is geometry for the handset's optional pre-check, never an attendance verdict; the server recomputes the punch at sync time.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.get_me",
        "x-realizes-features": [
          "PPL-F01",
          "ATT-F01"
        ],
        "x-screens": [
          "PPL-S01",
          "ATT-S01",
          "ATT-S02"
        ],
        "x-touches-entities": [
          "people.employees",
          "people.employee_attendance_locations",
          "org.work_locations",
          "org.geofences"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's named assignments, effective mobile policy, and resolved boundaries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MyEmployeeAttendanceLocations"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}/attendance-locations": {
      "get": {
        "operationId": "people.employee.get_attendance_locations",
        "summary": "Read an employee's allowed attendance locations",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.get_360",
        "x-rls-scope": "tenant",
        "x-market": "both",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Explicit assignments and current employee version; personal coordinates require tenant HR access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeAttendanceLocations"
                }
              }
            }
          },
          "403": {
            "description": "Employee profile permission or tenant scope denied."
          },
          "404": {
            "description": "Employee not found."
          }
        }
      },
      "put": {
        "operationId": "people.employee.update_attendance_locations",
        "summary": "Replace an employee's allowed attendance locations",
        "description": "Atomically replaces 1–100 assignments and activates ALLOWED_LOCATIONS. The employee's primary work_location_id is unchanged. Referenced locations must be active and in this tenant. EMPLOYEE_SPECIFIC templates require personal coordinates; SHARED references forbid them. Explicit REMOTE/SHARED permits attendance anywhere. Otherwise attendance inside any assigned boundary succeeds and outside all boundaries follows the existing out-of-zone approval flow. Existing employee versions start at zero. Supplied assignment IDs must belong to this employee.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.update",
        "x-rls-scope": "tenant",
        "x-market": "both",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "version",
                  "locations"
                ],
                "properties": {
                  "version": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "locations": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "$ref": "#/components/schemas/EmployeeAttendanceLocation"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved assignments and incremented employee version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeAttendanceLocations"
                }
              }
            }
          },
          "403": {
            "description": "Employee update permission or tenant scope denied."
          },
          "404": {
            "description": "Employee or referenced active location not found."
          },
          "409": {
            "description": "Stale employee version; reload before saving."
          },
          "422": {
            "description": "Invalid coordinates",
            "assignment ownership": null,
            "empty set": null,
            "or duplicate shared location.": null
          }
        }
      }
    },
    "/employees/{id}/profile-360": {
      "get": {
        "operationId": "people.employee.get_360",
        "summary": "Get the full employee 360 record (HR)",
        "description": "Every People facet on one aggregate read model for the HR tabbed view (fsd 02 PPL-S14): Personal (`personal_info`, `contacts`, `dependents`), Work (`employees` placement + `org_directory`), Payroll/Bank (`bank_accounts`, statutory identity — **masked**), Documents (delegated to `docs.*`), Access (`virtual_ids`, `app_settings`). All tables are same-schema (`people`), composed directly per db-docs/00 §12 (no cross-schema join). **All nine statutory identifiers** (`pan`, `aadhaar_ref`, `uan`, `pf_no`, `esic_no`, `iqama_no`, `national_id`, `gosi_no`, `border_no`) and the bank account number / IBAN are display-masked; use `people.employee.reveal_pii` for an audited unmask of any of them.\n\n**Carries the ESS sign-in facet** (`ess_account`, issue #1411), projected by the same helper as `people.employee.get` — the Access region's \"ESS sign-in\" section reads it off this payload rather than issuing a second request for a fact about the employee this token already serves.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.get_360",
        "x-realizes-features": [
          "PPL-F01",
          "PPL-F02",
          "PPL-F03"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.employees",
          "people.employee_profiles",
          "people.personal_info",
          "people.contacts",
          "people.dependents",
          "people.bank_accounts",
          "people.virtual_ids",
          "people.app_settings",
          "people.org_directory",
          "xc.identities"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The employee's full 360 record, masked PII.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeProfile360"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}/position-history": {
      "get": {
        "operationId": "people.position_history.list",
        "summary": "List an employee's effective-dated position history (HR)",
        "description": "The `PPL-S14` **Position history** card (#1155). One row per placement PERIOD — designation, department, grade, org unit, work location, reporting manager, legal entity and employment type as they stood for that period — newest first. db 03 §1.4 `people.position_history`.\n\nEffective-dated the way `pay.employee_compensation` is: a placement change closes the open period (`effective_to = effective_from - 1 day`) and opens the next in the same transaction, so at most one period per employee has `effective_to: null` — the placement in force today.\n\nPeriods are written by `people.employee.create` (the opening `HIRE` period, effective from the joining date), by `people.employee.update` (`TRANSFER`, or `MANAGER_CHANGE` when the reporting line is the only axis that moved), and by `engage.promotion.approve` (`PROMOTION`). `people.employee.exit` closes the open period at the last working day. Every `*_name` field is a **projection** of today's name for the id the period recorded — the ids are the snapshot, the names are a convenience.\n\nHistory earlier than this table cannot be reconstructed: the opening period of an employee who predates it was backfilled from their current placement, so it states the placement, not a claim that nothing moved before it. `audit.audit_log` remains the record for those moves.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.position_history.list",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.position_history",
          "people.employees",
          "org.designations",
          "org.departments",
          "org.grades",
          "org.org_units",
          "org.work_locations",
          "org.legal_entities"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of placement periods, newest effective date first.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PositionHistoryEntry"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}/reveal-pii": {
      "post": {
        "operationId": "people.employee.reveal_pii",
        "summary": "Reveal masked PII for an employee (audited)",
        "description": "Unmasks any of the nine statutory identifiers (`pan`, `aadhaar_ref`, `uan`, `pf_no`, `esic_no`, `iqama_no`, `national_id`, `gosi_no`, `border_no`) plus the bank account number / IBAN, for HR viewing on `PPL-S14`. Requires the admin to re-authenticate (password/2FA) in the request; the reveal is **logged** (XC-F06) — an `audit.access_log` `DATA_ACCESS` row per touched record, plus a value-free `audit.audit_log` entry naming only which fields were returned. Returns the unmasked values transiently — never persisted or cached client-side beyond the session. `fields` is validated against exactly this set; anything else is a `422`.\n",
        "tags": [
          "people",
          "employee"
        ],
        "x-token": "people.employee.reveal_pii",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.personal_info",
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RevealPiiInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Unmasked PII fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevealPiiResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/imports/uploads": {
      "post": {
        "operationId": "people.employee_import.upload_url",
        "summary": "Mint a presigned PUT URL for a large employee-import file",
        "description": "The ASYNC half of preview (#631, migration 0190). A file at/over the sync caps (2 MB / 500 rows) never rides a JSON body: the wizard asks for a presigned PUT URL, uploads the raw bytes to object storage verbatim (including the signed `headers`), then calls `/employees/imports/preview` with the returned `key` as `storage_key`. Nothing is persisted here — the import row is created on the subsequent preview call. Files over the 32 MB cap are refused by the client before it starts.\n",
        "tags": [
          "people",
          "employee-import"
        ],
        "x-token": "people.employee_import.preview",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employee_imports",
          "xc.object_refs"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename"
                ],
                "additionalProperties": false,
                "properties": {
                  "filename": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "The file's name — its `.csv` / `.xlsx` extension selects the parser and the signed content type."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The presigned PUT target. The client PUTs the raw bytes (with `headers`) and passes `key` back to `preview`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeImportUploadUrlResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/imports/preview": {
      "post": {
        "operationId": "people.employee_import.preview",
        "summary": "Validate an uploaded employee CSV or XLSX and keep its previewed snapshot",
        "description": "The first act of the bulk-import console (PPL-S13 Import, #631). The request shape selects the path: `{ filename, content }` (CSV text), `{ filename, content_base64, format: 'xlsx' }` (a small workbook, base64 — no multipart on this surface), or `{ filename, storage_key, format }` (ASYNC: the file was PUT via `/employees/imports/uploads`; this call creates the import row as `UPLOADED` and returns `{ import_id, filename, status: 'UPLOADED' }` immediately, and the jobs tier's `people.employee_import.preview` worker parses + validates it off the request thread, flipping the row to `PREVIEWED` — or `FAILED` with `structural_error` — which the wizard polls via `GET /employees/imports/{id}`). The header row must match the template the wizard downloads (the same 14 columns `people.employee.create` accepts); every data row is field-validated (required `full_name` / `employment_type` / `legal_entity_id` / `date_of_joining`, enum and UUID and date formats) and cross-checked against live employees (`legal_entity_id` exists; `employee_no` / `work_email` not already taken, and not duplicated within the file). The validated rows are persisted as a snapshot on `people.employee_imports` (status `PREVIEWED`) so the later commit inserts exactly what this preview showed — a tampered client cannot change what commits. Repeatable: re-running preview mints a fresh draft. Caps: 2 MB / 500 data rows inline; up to 32 MB async.\n",
        "tags": [
          "people",
          "employee-import"
        ],
        "x-token": "people.employee_import.preview",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employee_imports",
          "people.employees",
          "org.legal_entities"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee_import.preview_requested",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeImportPreviewInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The persisted preview — per-row verdicts and the snapshot's counts. On the async path the response is instead `{ import_id, filename, status: 'UPLOADED' }` and the caller polls `GET /employees/imports/{id}` until the row settles `PREVIEWED` or `FAILED`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/EmployeeImportPreviewResult"
                    },
                    {
                      "$ref": "#/components/schemas/EmployeeImportAsyncPreviewResult"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/imports/{id}/commit": {
      "post": {
        "operationId": "people.employee_import.commit",
        "summary": "Commit a previewed employee import",
        "description": "The second act of the bulk-import console. Inserts the previewed snapshot through the SAME kernel the single-row doors use (`people.employee.create` / onboarding completion), one SAVEPOINT per row, so a bad row fails alone and the batch settles as `COMMITTED` (all rows), `PARTIAL` (some rows — the failures are listed per row), or `FAILED` (none). The plan `maxEmployees` limit is re-checked per row inside the insert transaction, so the import never bypasses the seat cap. Every failed row is PARKED in `people.pending_hires` (source `IMPORT`, migration 0190) for the same admin review queue recruit parks use — the count surfaces as `counts.parked` and the reviewer completes them (employee_no auto-generated when the row omitted one) once the obstruction clears. Idempotent: a retried `Idempotency-Key` replays the stored outcome instead of double-inserting (and never double-parks).\n",
        "tags": [
          "people",
          "employee-import"
        ],
        "x-token": "people.employee_import.commit",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employee_imports",
          "people.employees",
          "people.employee_profiles",
          "people.pending_hires"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee_import.completed",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The settled outcome — created/failed/parked counts and the per-row failures.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeImportCommitResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/imports/{id}": {
      "get": {
        "operationId": "people.employee_import.get",
        "summary": "Get one employee import (preview, async poll target, or settled outcome)",
        "description": "Re-read a preview's snapshot counts, the async path's poll target (status stays `UPLOADED` until the background worker settles it to `PREVIEWED` or `FAILED` + `structural_error`), or a settled commit's outcome including the parked count. History and per-row failure detail for the audit trail behind an Import button.\n",
        "tags": [
          "people",
          "employee-import"
        ],
        "x-token": "people.employee_import.get",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employee_imports"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The import row.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeImportSummary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/imports": {
      "get": {
        "operationId": "people.employee_import.list",
        "summary": "List employee imports (newest first)",
        "description": "The import console's history — one entry per uploaded file, with the preview counts and the settled commit summary where committed. Cursor-paged like every list on this surface.\n",
        "tags": [
          "people",
          "employee-import"
        ],
        "x-token": "people.employee_import.list",
        "x-realizes-features": [
          "PPL-F01"
        ],
        "x-screens": [
          "PPL-S13"
        ],
        "x-touches-entities": [
          "people.employee_imports"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of import history.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/EmployeeImportSummary"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/onboarding": {
      "get": {
        "operationId": "people.onboarding.list",
        "summary": "List onboarding flows (in-progress and resolved)",
        "description": "The onboarding workspace's in-progress list (fsd 02 PPL-S19). A guided hire is a **case, not a form**: an offer letter, a personal/statutory capture, a compensation assignment, an org placement and an access decision — five acts by two or three people over several days. This is the operation that makes those cases visible to a colleague and resumable after a refresh, which the three-step `people.employee.create` modal never could.\n\nCursor-paged over `people.onboarding_flows` and filterable by `stage`, so the same operation serves the default *in progress* view (`stage` omitted, or one of the five working stages) and the audit-facing *resolved* view (`COMPLETED`/`CANCELLED`). Terminal flows stay listable on purpose — the decision remains visible from the surface that took it, exactly as `people.pending_hire.list` keeps its resolved rows.\n\n**The projection is deliberately payload-free.** Only the flow's identity, its stage and its resolution timestamps are returned; the five stage payloads are not, because while a flow is live they hold the hire's date of birth, address, statutory identifiers and salary. A list is read on a shared screen by whoever holds the list token, so it must not be a bulk PII read — the payloads are reachable only one flow at a time through the audited `people.onboarding.get`.\n\nA live flow consumes **no plan seat**: `people.employees` is not written until `people.onboarding.complete`, so `platform.tenant.get_usage` keeps reporting the true headcount. Sort whitelist: `created_at` (default, descending), `updated_at`, `full_name`. db 03 `people.onboarding_flows` (migration `0064`).\n",
        "tags": [
          "people",
          "onboarding"
        ],
        "x-token": "people.onboarding.list",
        "x-realizes-features": [
          "PPL-F07"
        ],
        "x-screens": [
          "PPL-S19"
        ],
        "x-touches-entities": [
          "people.onboarding_flows"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "name": "stage",
            "in": "query",
            "required": false,
            "description": "Restrict to one stage. The five working stages name where the flow currently rests (i.e. the stage whose form is open); `COMPLETED`/`CANCELLED` are the two terminal stages. Omit for every flow.\n",
            "schema": {
              "$ref": "#/components/schemas/OnboardingStage"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of onboarding flows, without stage payloads.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/OnboardingFlowSummary"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "in_progress": {
                    "summary": "Two live flows resting at different stages",
                    "value": {
                      "data": [
                        {
                          "id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d01",
                          "stage": "COMPENSATION",
                          "full_name": "Rohit Verma",
                          "work_email": "rohit.verma@groundit.example",
                          "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a34",
                          "candidate_id": null,
                          "employee_id": null,
                          "offer_render_id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d0a",
                          "created_at": "2026-07-27T06:12:09.114Z",
                          "updated_at": "2026-07-29T11:41:52.660Z",
                          "completed_at": null,
                          "cancelled_at": null,
                          "version": 3
                        },
                        {
                          "id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d02",
                          "stage": "OFFER",
                          "full_name": "Layla Al-Harbi",
                          "work_email": null,
                          "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a44",
                          "candidate_id": null,
                          "employee_id": null,
                          "offer_render_id": null,
                          "created_at": "2026-07-29T09:03:44.201Z",
                          "updated_at": "2026-07-29T09:03:44.201Z",
                          "completed_at": null,
                          "cancelled_at": null,
                          "version": 0
                        }
                      ],
                      "page": {
                        "next_cursor": null,
                        "prev_cursor": null,
                        "has_more": false
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "people.onboarding.create",
        "summary": "Open an onboarding flow",
        "description": "Open a guided hire (fsd 02 PPL-S19 → PPL-S20). The flow is created at stage `OFFER` with five empty payloads and carries only the two facts that cannot be deferred — `full_name`, because the in-progress list must be readable before any stage has been completed, and `legal_entity_id`, because it is what binds the hire's **market/compliance pack**: which statutory identifiers the `PERSONAL_DATA` stage will ask for, which currency its compensation is denominated in, and which calendar its dates are read against. Neither the offer letter nor the eventual employee row can be written without them.\n\n**Nothing is minted here.** No `people.employees` row, no `pay.employee_compensation` row, no `admin.member_grants` row — those all appear in the single `people.onboarding.complete` transaction. A flow is therefore **not a seat**: it consumes no `maxEmployees` headroom while it is open, which is the whole reason a half-finished hire is safe to leave lying around. The `402` below is declared because the ADR 0009 entitlement middleware (subscription-status → feature-flag → numeric-limit) precedes every mutation, not because opening a flow charges a seat — it does not.\n\n`candidate_id` is accepted by the public HR door and is also stamped by REC-S11's in-process Provision seam. Recruit supplies accepted-offer prefill through People's own writer; People does not read a recruit table. A partial-unique index over live, non-terminal rows enforces **one open flow per candidate**, so a replay resumes instead of opening two; a completed or cancelled flow never blocks a genuine second attempt on the same candidate (a declined offer, re-offered later). db 03 `people.onboarding_flows` (migration `0064`).\n",
        "tags": [
          "people",
          "onboarding"
        ],
        "x-token": "people.onboarding.create",
        "x-realizes-features": [
          "PPL-F07"
        ],
        "x-screens": [
          "PPL-S19",
          "PPL-S20"
        ],
        "x-touches-entities": [
          "people.onboarding_flows"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OnboardingFlowCreateInput"
              },
              "examples": {
                "minimal": {
                  "summary": "The two required facts only",
                  "value": {
                    "full_name": "Rohit Verma",
                    "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a34"
                  }
                },
                "with_work_email": {
                  "summary": "Work email known up front",
                  "value": {
                    "full_name": "Layla Al-Harbi",
                    "work_email": "layla.alharbi@groundit.example",
                    "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a44",
                    "candidate_id": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The opened flow, resting at stage `OFFER` with five empty payloads.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingFlowDetail"
                },
                "examples": {
                  "created": {
                    "summary": "A freshly opened flow",
                    "value": {
                      "id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d01",
                      "stage": "OFFER",
                      "full_name": "Rohit Verma",
                      "work_email": null,
                      "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a34",
                      "candidate_id": null,
                      "employee_id": null,
                      "offer_payload": {},
                      "personal_payload": {},
                      "compensation_payload": {},
                      "placement_payload": {},
                      "access_payload": {},
                      "offer_template_id": null,
                      "offer_template_version_no": null,
                      "offer_render_id": null,
                      "offer_skipped_reason": null,
                      "offer_document": null,
                      "cancel_reason": null,
                      "created_at": "2026-07-27T06:12:09.114Z",
                      "updated_at": "2026-07-27T06:12:09.114Z",
                      "completed_at": null,
                      "cancelled_at": null,
                      "version": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/onboarding/{id}": {
      "get": {
        "operationId": "people.onboarding.get",
        "summary": "Get one onboarding flow with its stage payloads (privileged)",
        "description": "The read behind the guided stage flow (fsd 02 PPL-S20) — the whole case, including every stage payload captured so far, so a colleague can resume the hire exactly where it was left. `ETag` carries the row `version` and is the `If-Match` value the three mutating actions below require.\n\n**This is a privileged read.** While a flow is live its payloads are a draft of another table's row, and they hold the most ordinary and most sensitive personal data there is: a date of birth, a home address, a PAN or an Iqama number, an annual CTC. The operation is therefore classified `AADHAAR` / `NATIONAL_ID` / `IQAMA` / `PII_OTHER` (security-docs/04 §4 read-side sensitivity tags, reusing `audit.access_log.data_class` verbatim) and writes an `audit.access_log` `DATA_ACCESS` row **whenever the personal payload actually carries a statutory identifier** — the log records the access that happened, so a flow that has not reached `PERSONAL_DATA` yet does not manufacture an access record for data it does not hold.\n\n**A terminal flow carries only a redacted manifest.** On `COMPLETED` the truth has moved to `people.personal_info` / `people.contacts` / `pay.employee_compensation`, which is where the DPDP/PDPL erasure and retention machinery looks — a jsonb shadow copy would be invisible to a column-oriented erasure and quietly survive it. On `CANCELLED` the hire is not happening at all, so there is no employment relationship to justify retaining a stranger's identifiers. Either terminal transition replaces every payload with `{ \"captured\": [ …field names… ] }`, which is enough for an auditor to see what the flow collected and carries none of the values; a database CHECK (`onboarding_flows_terminal_payloads_redacted`) makes that a table invariant rather than a promise the service makes.\n",
        "tags": [
          "people",
          "onboarding"
        ],
        "x-token": "people.onboarding.get",
        "x-realizes-features": [
          "PPL-F07"
        ],
        "x-screens": [
          "PPL-S19",
          "PPL-S20"
        ],
        "x-touches-entities": [
          "people.onboarding_flows",
          "org.template_renders"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The flow with its stage payloads (a redacted manifest once terminal).",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingFlowDetail"
                },
                "examples": {
                  "live": {
                    "summary": "A live flow resting at COMPENSATION, offer letter rendered",
                    "value": {
                      "id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d01",
                      "stage": "COMPENSATION",
                      "full_name": "Rohit Verma",
                      "work_email": "rohit.verma@groundit.example",
                      "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a34",
                      "candidate_id": null,
                      "employee_id": null,
                      "offer_payload": {
                        "template_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a70",
                        "locale": "en"
                      },
                      "personal_payload": {
                        "legal_first_name": "Rohit",
                        "legal_last_name": "Verma",
                        "date_of_birth": "1994-04-18",
                        "gender": "MALE",
                        "marital_status": "SINGLE",
                        "nationality": "Indian",
                        "personal_email": "rohit.verma.personal@example.com",
                        "personal_phone": "+91-98000-00001",
                        "identity": {
                          "pan": "ABCDE1234F",
                          "uan": "100000000001"
                        },
                        "emergency_contact": {
                          "name": "Meera Verma",
                          "relationship": "PARENT",
                          "phone": "+91-98000-00002"
                        }
                      },
                      "compensation_payload": {},
                      "placement_payload": {},
                      "access_payload": {},
                      "offer_template_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a70",
                      "offer_template_version_no": 3,
                      "offer_render_id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d0a",
                      "offer_skipped_reason": null,
                      "offer_document": {
                        "render_id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d0a",
                        "status": "COMPLETED",
                        "template_version_no": 3,
                        "completed_at": "2026-07-27T06:14:31.502Z",
                        "error": null
                      },
                      "cancel_reason": null,
                      "created_at": "2026-07-27T06:12:09.114Z",
                      "updated_at": "2026-07-29T11:41:52.660Z",
                      "completed_at": null,
                      "cancelled_at": null,
                      "version": 3
                    }
                  },
                  "redacted": {
                    "summary": "A COMPLETED flow — payloads reduced to a field-name manifest",
                    "value": {
                      "id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d03",
                      "stage": "COMPLETED",
                      "full_name": "Aanya Kapoor",
                      "work_email": "aanya.kapoor@groundit.example",
                      "legal_entity_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a34",
                      "candidate_id": null,
                      "employee_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a40",
                      "offer_payload": {
                        "captured": [
                          "template_id",
                          "locale",
                          "variable_values"
                        ]
                      },
                      "personal_payload": {
                        "captured": [
                          "legal_first_name",
                          "legal_last_name",
                          "date_of_birth",
                          "identity",
                          "emergency_contact"
                        ]
                      },
                      "compensation_payload": {
                        "captured": [
                          "ctc_amount",
                          "basic_amount",
                          "effective_from",
                          "revision_reason"
                        ]
                      },
                      "placement_payload": {
                        "captured": [
                          "employment_type",
                          "work_mode",
                          "date_of_joining",
                          "department_id",
                          "manager_id"
                        ]
                      },
                      "access_payload": {
                        "captured": [
                          "no_role_reason"
                        ]
                      },
                      "offer_template_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a70",
                      "offer_template_version_no": 3,
                      "offer_render_id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d0b",
                      "offer_skipped_reason": null,
                      "offer_document": {
                        "render_id": "018f4c11-2a30-7d51-9b00-4c1e7a2b3d0b",
                        "status": "COMPLETED",
                        "template_version_no": 3,
                        "completed_at": "2026-07-28T04:22:10.900Z",
                        "error": null
                      },
                      "cancel_reason": null,
                      "created_at": "2026-07-26T05:00:00.000Z",
                      "updated_at": "2026-07-28T04:30:12.771Z",
                      "completed_at": "2026-07-28T04:30:12.771Z",
                      "cancelled_at": null,
                      "version": 7
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/onboarding/{id}/advance": {
      "post": {
        "operationId": "people.onboarding.advance",
        "summary": "Complete the current stage and advance the flow",
        "description": "The one write the guided stage flow makes (fsd 02 PPL-S20): validate the current stage's payload, persist it, and move the flow to the next stage. `stage` in the body names the stage being **COMPLETED**, not the stage being entered, and it **must equal the flow's current stage** — a mismatch is `409 STATE_TRANSITION_INVALID`. That equality check is what stops a stale UI (a tab left open while a colleague advanced the same flow) from writing the wrong step's payload over the right one; `If-Match` catches the same collision on the row `version`, and both are required because they fail on different things — the ETag on *any* concurrent edit, the stage on *this particular* wrong step. The stage order is strict and the service refuses skips and reversals.\n\n**Each payload is a draft of another table's row**, which is why they are jsonb here and not columns: `personal_payload` becomes `people.personal_info` + `people.contacts`, `compensation_payload` becomes `pay.employee_compensation`, `placement_payload` becomes columns of `people.employees`. The service validates each against the **target table's own rules** at the moment the stage advances, which is the only place the answer can be right — nothing is written to those tables until `people.onboarding.complete`.\n\n**Two stages require a second token, and both refusals are `403 TOKEN_DENIED`.** Rendering an offer letter needs `org.template.render`, and choosing a system role needs `admin.member_grant.create`. A stage must never become a side door to a privileged capability the caller was not granted directly: the flow is a convenience for the HR admin, not a way to escalate what they can do.\n",
        "tags": [
          "people",
          "onboarding"
        ],
        "x-token": "people.onboarding.advance",
        "x-realizes-features": [
          "PPL-F07"
        ],
        "x-screens": [
          "PPL-S20"
        ],
        "x-touches-entities": [
          "people.onboarding_flows",
          "org.template_renders"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OnboardingAdvanceInput"
              },
              "examples": {
                "offer_rendered": {
                  "summary": "OFFER — render a letter from a published template",
                  "value": {
                    "stage": "OFFER",
                    "payload": {
                      "template_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a70",
                      "locale": "en",
                      "variable_values": {
                        "candidate_name": "Rohit Verma",
                        "designation": "Senior Engineer",
                        "annual_ctc": "1800000.00",
                        "joining_date": "2026-08-03"
                      }
                    }
                  }
                },
                "offer_skipped": {
                  "summary": "OFFER — deliberately no letter (acquisition intake)",
                  "value": {
                    "stage": "OFFER",
                    "payload": {
                      "skipped_reason": "TUPE intake — offer terms carried over from the acquired entity's contract."
                    }
                  }
                },
                "personal_india": {
                  "summary": "PERSONAL_DATA — India identity set (pack-driven)",
                  "value": {
                    "stage": "PERSONAL_DATA",
                    "payload": {
                      "legal_first_name": "Rohit",
                      "legal_last_name": "Verma",
                      "date_of_birth": "1994-04-18",
                      "gender": "MALE",
                      "marital_status": "SINGLE",
                      "nationality": "Indian",
                      "personal_email": "rohit.verma.personal@example.com",
                      "personal_phone": "+91-98000-00001",
                      "current_address": {
                        "line1": "14 Nandi Durga Road",
                        "city": "Bengaluru",
                        "state": "Karnataka",
                        "country": "India",
                        "postal_code": "560046"
                      },
                      "identity": {
                        "pan": "ABCDE1234F",
                        "uan": "100000000001"
                      },
                      "emergency_contact": {
                        "name": "Meera Verma",
                        "relationship": "PARENT",
                        "phone": "+91-98000-00002"
                      }
                    }
                  }
                },
                "personal_ksa": {
                  "summary": "PERSONAL_DATA — KSA identity set (same operation, different pack)",
                  "value": {
                    "stage": "PERSONAL_DATA",
                    "payload": {
                      "legal_first_name": "Layla",
                      "legal_last_name": "Al-Harbi",
                      "date_of_birth": "1996-11-02",
                      "gender": "FEMALE",
                      "nationality": "Saudi",
                      "identity": {
                        "national_id": "1098765432",
                        "gosi_no": "5566778899"
                      },
                      "emergency_contact": {
                        "name": "Noura Al-Harbi",
                        "relationship": "SIBLING",
                        "phone": "+966-50-000-0002"
                      }
                    }
                  }
                },
                "compensation": {
                  "summary": "COMPENSATION — decimal-string money only",
                  "value": {
                    "stage": "COMPENSATION",
                    "payload": {
                      "ctc_amount": "1800000.00",
                      "basic_amount": "900000.00",
                      "effective_from": "2026-08-03",
                      "revision_reason": "HIRE",
                      "pay_structure_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a80",
                      "notes": "Offer-letter terms, band L4."
                    }
                  }
                },
                "placement": {
                  "summary": "PLACEMENT — org placement and dates",
                  "value": {
                    "stage": "PLACEMENT",
                    "payload": {
                      "employment_type": "FULL_TIME",
                      "work_mode": "DESK",
                      "date_of_joining": "2026-08-03",
                      "date_of_confirmation": "2027-02-03",
                      "department_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a35",
                      "designation_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a36",
                      "grade_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a37",
                      "work_location_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a39",
                      "manager_id": "018f3a2b-7c41-7c9e-8a10-2b6f5d9e1a3a"
                    }
                  }
                },
                "access_no_role": {
                  "summary": "ACCESS — the common HR path, no portal role issued",
                  "value": {
                    "stage": "ACCESS",
                    "payload": {
                      "no_role_reason": "Mobile self-service only; no back-office access required."
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The flow after the stage was completed, resting at the next stage.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingFlowDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/onboarding/{id}/cancel": {
      "post": {
        "operationId": "people.onboarding.cancel",
        "summary": "Cancel an onboarding flow (no employee is created)",
        "description": "Abandon a guided hire — offer declined, requisition withdrawn, or a duplicate flow opened by mistake. Terminal: the flow moves to `CANCELLED` with `cancelled_at` and the required `reason`, and it can be cancelled from **any** working stage, including `OFFER` before the offer question has been answered at all. It creates **no** employee and consumes **no** plan seat, because no `people.employees` row was ever written; `platform.tenant.get_usage` is unaffected. Already-terminal flows answer `409 STATE_TRANSITION_INVALID`.\n\n**Every stage payload is replaced by a redacted manifest of the field names captured.** An abandoned hire has no employment relationship to justify retaining a stranger's date of birth, address, PAN or Iqama number, and a jsonb draft is invisible to the column-oriented DPDP/PDPL erasure sweeps (migration `0054`) that would otherwise be the safety net — so the values are dropped at the moment the flow ends rather than left to age out. The database CHECK `onboarding_flows_terminal_payloads_redacted` rejects a terminal row that still holds anything but the manifest, so a code path that forgets to redact fails loudly at the write instead of leaving a shadow copy behind. The offer letter's merge inputs are governed the same way one layer down — `org.template_renders.variable_values` is emptied by the render worker (migration `0063`).\n\nBecause the one-open-flow-per-candidate index is partial over live rows, a cancelled candidate can be onboarded again later on a fresh flow.\n",
        "tags": [
          "people",
          "onboarding"
        ],
        "x-token": "people.onboarding.cancel",
        "x-realizes-features": [
          "PPL-F07"
        ],
        "x-screens": [
          "PPL-S19",
          "PPL-S20"
        ],
        "x-touches-entities": [
          "people.onboarding_flows"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OnboardingCancelInput"
              },
              "examples": {
                "declined": {
                  "summary": "Candidate declined the offer",
                  "value": {
                    "reason": "Candidate declined the offer on 2026-07-29; accepted a competing role."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled flow, payloads reduced to a field-name manifest.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingFlowDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/onboarding/{id}/complete": {
      "post": {
        "operationId": "people.onboarding.complete",
        "summary": "Complete an onboarding flow (mint the employee and everything the flow captured)",
        "description": "The one transaction that turns five drafts into real rows (fsd 02 PPL-S20, final step). In a single all-or-nothing unit of work it writes `people.employees` **plus** its 1:1 `people.employee_profiles` row through **the same limit-checked create path `people.employee.create` uses** (so there is exactly one place an employee is born, whatever door the hire came through), `people.personal_info` and `people.contacts` from the personal payload, `pay.employee_compensation` from the compensation payload, and — only when the `ACCESS` stage chose a role — the `EMPLOYEE`-subject `admin.member_grants` row (ADR 0024). It then stamps the flow `COMPLETED` with `employee_id` and `completed_at` and emits `people.employee.activated`, the same lifecycle event every other hire path emits, so every downstream module (attend, leave, work, pay, tax, comply) reacts identically regardless of provenance.\n\n**Since issue #800 the same transaction also provisions the hire's ESS account** — the `xc.identities` `EMPLOYEE`-class principal whose `subject_id` is the new employee and whose `email` is the work address. Until then a completed hire had an employee, a salary and possibly a role, and no way to sign in and use any of it. The principal is minted `INVITED`, not `ACTIVE`, because `identities_active_has_binding` requires an ACTIVE identity to carry a platform member id or a Keycloak subject and a fresh hire has neither; the employee's first successful sign-in binds the verified subject, writes the `people.employee_subjects` link and promotes the row to `ACTIVE`. **No additional permission token is required** — unlike the compensation write and the role grant, an ESS account carries no authority of its own; it is the address a role is delivered to, issued to the person this operation has just hired. **A hire with no work email is an honest skip, not a failure:** the corporate address is the only key the first-sign-in adoption path can work from, and a mailbox IT has not cut yet is ordinary — so the response's `ess_account.skipped_reason` reads `NO_WORK_EMAIL`, no identity is minted, and the hire completes. `x-emits-event` stays the scalar `people.employee.activated` by the convention in `00 §4` (no operation in the corpus declares an array); the second event this transaction now also writes, `people.employee.ess_account_provisioned`, is catalogued in `asyncapi/domain-events.asyncapi.yaml`.\n\n**A failure at any step rolls the whole thing back and the flow stays at `ACCESS`** — there is no half-created employee with no compensation, and no compensation row pointing at an employee that does not exist. A partial-unique index over `(tenant_id, employee_id)` means a replayed completion cannot mint a second employee for the same flow.\n\n**This is the operation the `maxEmployees` numeric limit gates** (ADR 0009 §e), which is why it declares `402` and `people.onboarding.create` does not charge a seat. The check runs **inside** the completion transaction, against the platform-fed `TenantCache`, because a flow proves nothing about today's headroom — the seat that was free when the flow was opened may have been taken by another hire in the meantime. A workspace at its cap gets `402 PLAN_LIMIT_EXCEEDED` with the current count and cap in `detail`, and the flow stays at `ACCESS` so it can be completed the moment a seat frees. That is what makes a guided onboarding a queue and not a bypass, exactly as `people.pending_hire.complete` re-runs the same check for the recruit parking bay.\n\n`employee_no` may be supplied (HR importing an existing code); when omitted it is generated per tenant as `<prefix><zero-padded sequence>` from the `people.employee_no` key of `admin.tenant_config`, defaulting to `EMP-` / width 4 (db 03 §1). Uniqueness of `(tenant_id, employee_no)` and `(tenant_id, work_email)` is enforced by the database and surfaced as a `422` `uniqueness-business` field error, never a 500. On success every stage payload is replaced by a redacted field-name manifest — see `people.onboarding.get` for why.\n",
        "tags": [
          "people",
          "onboarding"
        ],
        "x-token": "people.onboarding.complete",
        "x-realizes-features": [
          "PPL-F07"
        ],
        "x-screens": [
          "PPL-S20"
        ],
        "x-touches-entities": [
          "people.onboarding_flows",
          "people.employees",
          "people.employee_profiles",
          "people.personal_info",
          "people.contacts",
          "pay.employee_compensation",
          "admin.member_grants",
          "xc.identities"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee.activated",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OnboardingCompleteInput"
              },
              "examples": {
                "generated": {
                  "summary": "Let the tenant sequence generate the code",
                  "value": {}
                },
                "hr_supplied": {
                  "summary": "HR supplies an existing employee code",
                  "value": {
                    "employee_no": "EMP-0421"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The completed flow plus the employee it minted.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingCompletion"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employee-profiles/me": {
      "get": {
        "operationId": "people.employee_profile.get_me",
        "summary": "Get my profile facet (bio, skills, photo, engagement anchors)",
        "description": "The self-maintained profile facet (fsd 02 PPL-S01/PPL-S02 host; feeds the directory and Profile 360). db 03 §1 `people.employee_profiles`. Photo resolves via a presigned `FileDownload` (XC-F07); never a raw storage key.\n",
        "tags": [
          "people",
          "employee-profile"
        ],
        "x-token": "people.employee_profile.get_me",
        "x-realizes-features": [
          "PPL-F01",
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S01"
        ],
        "x-touches-entities": [
          "people.employee_profiles"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's profile facet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeProfile"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "people.employee_profile.update_me",
        "summary": "Update my profile facet",
        "description": "Self-service edit of `preferred_name`/`about`/`skills`/`photo_storage_key` (PPL-F02, db 03 §1 `people.employee_profiles`). Kept separate from the lifecycle-critical `employees` master so self-service edits never touch the spine. Feeds the `org_directory` projection by event.\n",
        "tags": [
          "people",
          "employee-profile"
        ],
        "x-token": "people.employee_profile.update_me",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S01"
        ],
        "x-touches-entities": [
          "people.employee_profiles"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.employee_profile.updated",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeProfileUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated profile facet.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeProfile"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/personal-info/me": {
      "get": {
        "operationId": "people.personal_info.get_me",
        "summary": "Get my personal information",
        "description": "Own PII/personal record (fsd 02 PPL-S02). Consent-gated, never logged (db 03 §2 `people.personal_info`). Statutory identity fields are on the `people.identity_document.*` sub-resource, not returned here.\n",
        "tags": [
          "people",
          "personal-info"
        ],
        "x-token": "people.personal_info.get_me",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S02"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's personal information.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonalInfo"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "people.personal_info.update_me",
        "summary": "Update my personal information",
        "description": "Self-service edit of legal name, contact, DOB/gender/marital status and addresses (fsd 02 PPL-S02, db 03 §2 `people.personal_info`). `full_name`/`work_email` on `employees` are HR-managed and read-only here. Consent-gated PII; audited (XC-F06); feeds payroll/tax (`pay.*`, `tax.*`) by event, never a cross-schema write. Upserts: `personal_info` is a 1:1 satellite created empty at hire, so the caller's first save creates the row's values rather than failing with a `404` for having nothing to update yet.\n",
        "tags": [
          "people",
          "personal-info"
        ],
        "x-token": "people.personal_info.update_me",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S02"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.personal_info.updated",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PersonalInfoUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated personal information.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonalInfo"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/personal-info/me/identity": {
      "get": {
        "operationId": "people.identity_document.get_me",
        "summary": "Get my statutory identity documents",
        "description": "Market-gated statutory identity numbers (fsd 02 PPL-S06) — India `pan`/`aadhaar_ref`/`uan`/`pf_no`/ `esic_no` or KSA `iqama_no`/`national_id`/`gosi_no`/`border_no`/`iqama_expiry` (db 03 §2 `people.personal_info`, same row as `people.personal_info.*`). PAN/Aadhaar/Iqama are **display-masked**; `iqama_expiry_hijri` is a read-only Umm al-Qura display string alongside the Gregorian value, never an operand (db-docs/00 §6). Only the active market's fields are populated per the legal entity's pack (XC-F01).\n",
        "tags": [
          "people",
          "identity-document"
        ],
        "x-token": "people.identity_document.get_me",
        "x-realizes-features": [
          "PPL-F02",
          "XC-F01"
        ],
        "x-screens": [
          "PPL-S06"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's statutory identity documents (masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityDocument"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "people.identity_document.update_me",
        "summary": "Update my statutory identity documents",
        "description": "Self-service edit of the market-gated statutory block (fsd 02 PPL-S06). KSA `iqama_no`/`national_id` are mutually exclusive (citizen vs expat), enforced at the service layer (db 03 §2). Async verification runs where the pack requires it (PAN/Aadhaar/Iqama). Consent-gated, audited (XC-F06); feeds `tax.*`/`comply.*` by event.\n",
        "tags": [
          "people",
          "identity-document"
        ],
        "x-token": "people.identity_document.update_me",
        "x-realizes-features": [
          "PPL-F02",
          "XC-F01"
        ],
        "x-screens": [
          "PPL-S06"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.identity_document.updated",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdentityDocumentUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated statutory identity documents (masked).",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityDocument"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/personal-info/for-approval": {
      "get": {
        "operationId": "people.personal_info.list_for_approval",
        "summary": "List statutory-identity changes awaiting a decision",
        "description": "The tenant-wide queue of `people.personal_info` rows whose `identity_verification_status = 'PENDING'` (#1643) — the evidence lane behind the `people.personal_info` chip on the unified approvals rail (`GET /approval-inbox?source_type=people.personal_info`).\n\n**Why this exists and `people.personal_info.get_admin` does not answer it.** That operation takes an employee id in the PATH, so it can only be used by somebody who already knows whose record is waiting — which is the defect #1643 is filed for: the people module had no inbox producer at all, so a statutory-identity change reached nobody and sat unverified for ever. The approvals drawer needs the pending set for a page of envelopes in one call.\n\nEvery statutory identifier is display-masked (`maskIdentity`), exactly as every other read of this table is; cleartext remains behind the step-up-gated `people.employee.reveal_pii`. `identity_change_summary` carries the MASKED old→new diff the producer snapshotted at the moment of the change — the only place an approver can see *what changed*, since the row itself holds only the new value.\n\nNewest first, capped at 200 rows; there is no cursor. The queue is a work list, not an archive, and a tenant with more than 200 statutory-identity changes awaiting HR has an operational problem this endpoint should not paginate around.\n",
        "tags": [
          "people",
          "personal-info"
        ],
        "x-token": "people.personal_info.list_for_approval",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "XC-S15"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The statutory-identity changes awaiting a decision (masked).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IdentityChangeForApproval"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/personal-info/{id}/identity/verify-decision": {
      "post": {
        "operationId": "people.identity_document.verify",
        "summary": "Approve or reject a statutory-identity change (HR maker-checker)",
        "description": "The HR/Finance decision on a pending statutory-identity change (fsd 02 PPL-S15 maker-checker queue; queue listing itself is `xc.approval_inbox.*` in 13-xc.openapi.yaml — this op is the decision that writes `people.personal_info`). **Maker ≠ checker** is enforced; on approval the change takes effect and feeds `pay.*`/`comply.*` (WPS) by event. Append-only audited (XC-F06). `id` is the `personal_info` row id.\n",
        "tags": [
          "people",
          "identity-document"
        ],
        "x-token": "people.identity_document.verify",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S15"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.identity_document.verification_decided",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decision recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityDocument"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/personal-info": {
      "get": {
        "operationId": "people.personal_info.get_admin",
        "summary": "Read an employee's personal information (HR)",
        "description": "The HR read of the named employee's personal record (PPL-S14 Overview, issue #1303). Same projection as `people.personal_info.get_me`: statutory identity lives on the `people.identity_document.*` sub-resource and is not returned here.\n",
        "tags": [
          "people",
          "personal-info"
        ],
        "x-token": "people.personal_info.get_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row whose personal information is read.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The named employee's personal information.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonalInfo"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "people.personal_info.update_admin",
        "summary": "Correct an employee's personal information (HR)",
        "description": "HR on-behalf correction for the named employee (PPL-S14 Overview, issue #1303). Exactly the mutable fields `people.personal_info.update_me` accepts — legal name, personal email/phone, date of birth, gender, marital status and the two addresses — shared with the SELF door so the two cannot drift. Statutory identifiers are NOT reachable here; they need `people.identity_document.update_admin`. Upserts, like the SELF door: the satellite row is created empty at hire, so the first HR save writes values rather than 404ing. Audited field-level with the acting actor and the target employee (XC-F06).\n",
        "tags": [
          "people",
          "personal-info"
        ],
        "x-token": "people.personal_info.update_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.personal_info.updated",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row being corrected.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PersonalInfoUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated personal information.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonalInfo"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/personal-info/identity": {
      "get": {
        "operationId": "people.identity_document.get_admin",
        "summary": "Read an employee's statutory identity documents (HR, masked)",
        "description": "The HR read of the named employee's statutory identity block (PPL-S14 Documents, issue #1303). **Display-masked identically to `people.identity_document.get_me`** — all nine identifiers are masked and the only path to a cleartext value remains the step-up-gated `people.employee.reveal_pii`. `iqama_expiry`/`iqama_expiry_hijri` are dates HR must act on, not identifiers, and are returned as-is.\n",
        "tags": [
          "people",
          "identity-document"
        ],
        "x-token": "people.identity_document.get_admin",
        "x-realizes-features": [
          "PPL-F02",
          "XC-F01"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row whose identity documents are read.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The named employee's statutory identity documents (masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityDocument"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "people.identity_document.update_admin",
        "summary": "Correct an employee's statutory identity documents (HR)",
        "description": "HR on-behalf correction of the market-gated statutory block (PPL-S14 Documents, issue #1303) — a PAN entered wrong at onboarding, an Iqama renewed, a UAN captured from a paper form. Same mutable columns as `people.identity_document.update_me`, and the same KSA `iqama_no`/`national_id` mutual exclusion. The response is **masked**, exactly like the SELF door: an edit door that echoed the value back in cleartext would be an unaudited reveal wearing a PATCH. Audited with the acting actor; the audit records WHICH identifiers changed and their MASKED before/after, never the identifiers themselves.\n",
        "tags": [
          "people",
          "identity-document"
        ],
        "x-token": "people.identity_document.update_admin",
        "x-realizes-features": [
          "PPL-F02",
          "XC-F01"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.personal_info"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.identity_document.updated",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row being corrected.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdentityDocumentUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated statutory identity documents (masked).",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityDocument"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/contacts": {
      "get": {
        "operationId": "people.contact.list",
        "summary": "List my emergency contacts",
        "description": "The caller's emergency/next-of-kin contacts, ordered by priority (fsd 02 PPL-S03; db 03 §2 `people.contacts`). Sort whitelist: `priority`, `name`.\n",
        "tags": [
          "people",
          "contact"
        ],
        "x-token": "people.contact.list",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S03"
        ],
        "x-touches-entities": [
          "people.contacts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of emergency contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Contact"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "people.contact.create",
        "summary": "Add an emergency contact",
        "description": "fsd 02 PPL-S03. db 03 §2 `people.contacts`; at most one `is_primary` per employee (partial-unique).",
        "tags": [
          "people",
          "contact"
        ],
        "x-token": "people.contact.create",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S03"
        ],
        "x-touches-entities": [
          "people.contacts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contact created.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/contacts/{id}": {
      "patch": {
        "operationId": "people.contact.update",
        "summary": "Update an emergency contact",
        "description": "fsd 02 PPL-S03. db 03 §2 `people.contacts`.",
        "tags": [
          "people",
          "contact"
        ],
        "x-token": "people.contact.update",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S03"
        ],
        "x-touches-entities": [
          "people.contacts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated contact.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "people.contact.delete",
        "summary": "Remove an emergency contact",
        "description": "fsd 02 PPL-S03. Soft-delete (`deleted_at`, db-docs/00 §5).",
        "tags": [
          "people",
          "contact"
        ],
        "x-token": "people.contact.delete",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S03"
        ],
        "x-touches-entities": [
          "people.contacts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Contact removed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/contacts/{id}/set-primary": {
      "post": {
        "operationId": "people.contact.set_primary",
        "summary": "Mark a contact as primary",
        "description": "Sets `is_primary` and re-ranks `priority` (fsd 02 PPL-S03); the prior primary is demoted in the same action (at most one primary per employee, db 03 §2 partial-unique constraint).\n",
        "tags": [
          "people",
          "contact"
        ],
        "x-token": "people.contact.set_primary",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S03"
        ],
        "x-touches-entities": [
          "people.contacts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Contact set primary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/contacts": {
      "post": {
        "operationId": "people.contact.create_admin",
        "summary": "Add an emergency contact on an employee's behalf",
        "description": "HR on-behalf capture for the named employee (PPL-S14 Overview, issue #1161). Same body and constraints as `people.contact.create`; at most one `is_primary` per employee (db 03 §2 partial-unique). Audited with the acting actor.\n",
        "tags": [
          "people",
          "contact"
        ],
        "x-token": "people.contact.create_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.contacts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row whose contact is being added.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contact created for the named employee.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/contacts/{id}": {
      "patch": {
        "operationId": "people.contact.update_admin",
        "summary": "Update an emergency contact on an employee's behalf",
        "description": "HR on-behalf correction for the named employee (PPL-S14 Overview, issue #1161). Same mutable fields as `people.contact.update`. Audited with the acting actor.\n",
        "tags": [
          "people",
          "contact"
        ],
        "x-token": "people.contact.update_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.contacts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row that owns the contact.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated contact.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "people.contact.delete_admin",
        "summary": "Remove an emergency contact on an employee's behalf",
        "description": "Soft-delete (`deleted_at`, db-docs/00 §5) for the named employee's contact (PPL-S14 Overview, issue #1161). Audited with the acting actor.\n",
        "tags": [
          "people",
          "contact"
        ],
        "x-token": "people.contact.delete_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.contacts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row that owns the contact.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Contact removed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/contacts/{id}/set-primary": {
      "post": {
        "operationId": "people.contact.set_primary_admin",
        "summary": "Mark an employee's contact as primary, on their behalf",
        "description": "Sets `is_primary` for the named employee's contact, demoting the prior primary in the same action (at most one primary per employee, db 03 §2 partial-unique). PPL-S14 Overview, issue #1161. Audited with the acting actor.\n",
        "tags": [
          "people",
          "contact"
        ],
        "x-token": "people.contact.set_primary_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.contacts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row that owns the contact.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Contact set primary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/dependents": {
      "get": {
        "operationId": "people.dependent.list",
        "summary": "List my dependents",
        "description": "fsd 02 PPL-S04; db 03 §2 `people.dependents`. Sort whitelist: `name`, `relationship`.\n",
        "tags": [
          "people",
          "dependent"
        ],
        "x-token": "people.dependent.list",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S04"
        ],
        "x-touches-entities": [
          "people.dependents"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of dependents.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Dependent"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "people.dependent.create",
        "summary": "Add a dependent",
        "description": "fsd 02 PPL-S04. db 03 §2 `people.dependents`; feeds benefits enrolment (`benefits.*`) and tax declarations by event.\n",
        "tags": [
          "people",
          "dependent"
        ],
        "x-token": "people.dependent.create",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S04"
        ],
        "x-touches-entities": [
          "people.dependents"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.dependent.changed",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DependentCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dependent created.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Dependent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/dependents/{id}": {
      "patch": {
        "operationId": "people.dependent.update",
        "summary": "Update a dependent",
        "description": "fsd 02 PPL-S04. db 03 §2 `people.dependents`.",
        "tags": [
          "people",
          "dependent"
        ],
        "x-token": "people.dependent.update",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S04"
        ],
        "x-touches-entities": [
          "people.dependents"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.dependent.changed",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DependentUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated dependent.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Dependent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "people.dependent.delete",
        "summary": "Remove a dependent",
        "description": "fsd 02 PPL-S04. Soft-delete (`deleted_at`, db-docs/00 §5).",
        "tags": [
          "people",
          "dependent"
        ],
        "x-token": "people.dependent.delete",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S04"
        ],
        "x-touches-entities": [
          "people.dependents"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.dependent.changed",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Dependent removed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/dependents": {
      "post": {
        "operationId": "people.dependent.create_admin",
        "summary": "Add a dependent on an employee's behalf",
        "description": "HR on-behalf capture for the named employee (PPL-S14 Overview, issue #1162). Same body and constraints as `people.dependent.create`. Audited with the acting actor.\n",
        "tags": [
          "people",
          "dependent"
        ],
        "x-token": "people.dependent.create_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.dependents"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row whose dependent is being added.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DependentCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dependent created for the named employee.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Dependent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/dependents/{id}": {
      "patch": {
        "operationId": "people.dependent.update_admin",
        "summary": "Update a dependent on an employee's behalf",
        "description": "HR on-behalf correction for the named employee (PPL-S14 Overview, issue #1162). Same mutable fields as `people.dependent.update`. Audited with the acting actor.\n",
        "tags": [
          "people",
          "dependent"
        ],
        "x-token": "people.dependent.update_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.dependents"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row that owns the dependent.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DependentUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated dependent.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Dependent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "people.dependent.delete_admin",
        "summary": "Remove a dependent on an employee's behalf",
        "description": "Soft-delete (`deleted_at`, db-docs/00 §5) for the named employee's dependent (PPL-S14 Overview, issue #1162). Audited with the acting actor.\n",
        "tags": [
          "people",
          "dependent"
        ],
        "x-token": "people.dependent.delete_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.dependents"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row that owns the dependent.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Dependent removed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bank-accounts": {
      "get": {
        "operationId": "people.bank_account.list",
        "summary": "List my salary bank accounts",
        "description": "fsd 02 PPL-S05; db 03 §2 `people.bank_accounts`. `account_number`/`iban` are display-masked. Sort whitelist: `is_primary`, `verification_status`, `bank_name`.\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.list",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S05"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of bank accounts (masked).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/BankAccount"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "people.bank_account.create",
        "summary": "Add a salary bank account",
        "description": "fsd 02 PPL-S05; db 03 §2 `people.bank_accounts`. Created `verification_status='PENDING'`; an async penny-drop/name-match verify sets `VERIFIED`/`FAILED` + `verified_at`. India requires `ifsc_code`, KSA requires `iban` (WPS-mandatory) — enforced at the service layer per market pack (XC-F01). Sensitive change → HR review (`people.bank_account.verify`), audited (XC-F06).\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.create",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S05"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankAccountCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bank account created, `PENDING` verification.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankAccount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bank-accounts/{id}": {
      "patch": {
        "operationId": "people.bank_account.update",
        "summary": "Update a salary bank account",
        "description": "fsd 02 PPL-S05. A material change (account number, IFSC/IBAN) resets `verification_status` to `PENDING` and re-triggers verification. Sensitive change → HR review, audited (XC-F06).\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.update",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S05"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankAccountUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated bank account.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankAccount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "people.bank_account.delete",
        "summary": "Remove a salary bank account",
        "description": "fsd 02 PPL-S05. Soft-delete (`deleted_at`, db-docs/00 §5); the primary account cannot be deleted directly — set another primary first.",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.delete",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S05"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Bank account removed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bank-accounts/{id}/set-primary": {
      "post": {
        "operationId": "people.bank_account.set_primary",
        "summary": "Mark a bank account as the salary-credit primary",
        "description": "fsd 02 PPL-S05. Only a `VERIFIED` account may be set primary (`FAILED` verification blocks this, db 03 §2 check). The primary account feeds payroll disbursement / WPS bank-file generation (`pay.*`, `comply.*`) by event.\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.set_primary",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S05"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.bank_account.primary_changed",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Bank account set primary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankAccount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bank-accounts/{id}/verify-decision": {
      "post": {
        "operationId": "people.bank_account.verify",
        "summary": "Approve or reject a bank-account verification (HR/Finance maker-checker)",
        "description": "fsd 02 PPL-S15. Sets `verification_status` (`PENDING`→`VERIFIED`/`FAILED`) and `verified_at`. **Maker ≠ checker**; routed via approvals inbox (`xc.approval_inbox.*`, 13-xc.openapi.yaml — this op is the decision write). A `FAILED` account can never be set primary. Append-only audited (XC-F06).\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.verify",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S15"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.bank_account.verification_decided",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decision recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankAccount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bank-accounts/for-approval": {
      "get": {
        "operationId": "people.bank_account.list_for_approval",
        "summary": "List bank changes awaiting a decision",
        "description": "The tenant-wide queue of `people.bank_accounts` rows whose `verification_status = 'PENDING'` (#1643) — the evidence lane behind the `people.bank_accounts` chip on the unified approvals rail (`GET /approval-inbox?source_type=people.bank_accounts`).\n\n**Why this exists and `people.bank_account.list_admin` does not answer it.** That operation takes an employee id in the PATH: before #1643 an HR admin could only clear a bank change if they already happened to be standing on that employee's profile, which is the defect the issue is filed for. It is also `hr_admin`'s alone, while the seeded `BANK_CHANGE` chain routes its SECOND step to `ROLE:finance` — so Finance would be handed an envelope it can decide (`people.bank_account.verify` is theirs) and cannot read. Widening `list_admin` to `finance` would hand Finance every employee's full account list as a side effect; this token grants exactly the pending set, to exactly the two roles that decide it.\n\n`account_number`/`iban` are display-masked (`maskBank`) like every other read of this table. That is the evidence, not a degradation of it: the approver is checking a masked tail, an account holder name and an IFSC/SWIFT against a cancelled cheque or bank letter. Cleartext remains behind the step-up-gated `people.employee.reveal_pii`.\n\nNewest first, capped at 200 rows; no cursor, for the reason `people.personal_info.list_for_approval` gives.\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.list_for_approval",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "XC-S15"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The bank changes awaiting a decision (masked).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BankAccountForApproval"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/bank-accounts": {
      "get": {
        "operationId": "people.bank_account.list_admin",
        "summary": "List an employee's salary bank accounts (HR)",
        "description": "The HR read of the named employee's accounts (PPL-S14 Documents, issue #1303). `account_number`/`iban` are display-masked; cleartext remains behind the step-up-gated `people.employee.reveal_pii`. Ordered primary first, then oldest first — the same order the 360 payload uses.\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.list_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row whose accounts are listed.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The named employee's bank accounts (masked).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BankAccount"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "people.bank_account.create_admin",
        "summary": "Add a salary bank account on an employee's behalf",
        "description": "HR on-behalf capture for the named employee (PPL-S14 Documents, issue #1303) — the onboarding paper form, or a device-less site worker who will never open ESS (ADR 0032). Same body and market rules as `people.bank_account.create`; created `PENDING`, so the account is not payable until `people.bank_account.verify` approves it under maker ≠ checker. Audited with the acting actor; the audit records WHICH fields were written, never their values.\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.create_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row the account is added to.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankAccountCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bank account created for the named employee, `PENDING` verification.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankAccount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/bank-accounts/{id}": {
      "patch": {
        "operationId": "people.bank_account.update_admin",
        "summary": "Correct an employee's salary bank account (HR)",
        "description": "HR on-behalf correction for the named employee (PPL-S14 Documents, issue #1303) — the misspelled holder name or wrong IFSC/IBAN that is about to fail a payroll run. Same mutable fields as `people.bank_account.update`, and the same consequence: the edit **resets `verification_status` to `PENDING` and clears `verified_at`**, so a corrected account must be re-verified before it can be paid into. Audited with the acting actor (field names and the status transition, never the account number).\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.update_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row that owns the account.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankAccountUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated bank account, back to `PENDING` verification.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankAccount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "people.bank_account.delete_admin",
        "summary": "Remove an employee's salary bank account (HR)",
        "description": "Soft-delete (`deleted_at`, db-docs/00 §5) of the named employee's account (PPL-S14 Documents, issue #1303). Same refusal as the SELF door: the primary account cannot be removed directly — set another verified account primary first, because \"which account do we pay into now\" is not a question a delete should open. Audited with the acting actor.\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.delete_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row that owns the account.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Bank account removed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{employee_id}/bank-accounts/{id}/set-primary": {
      "post": {
        "operationId": "people.bank_account.set_primary_admin",
        "summary": "Mark an employee's bank account as the salary-credit primary (HR)",
        "description": "Sets `is_primary` for the named employee's account, demoting the prior primary in the same transaction (PPL-S14 Documents, issue #1303). Only a `VERIFIED` account may be set primary — the admin door is no looser than the SELF one, because the primary feeds payroll disbursement and WPS bank-file generation. Audited with the acting actor.\n",
        "tags": [
          "people",
          "bank-account"
        ],
        "x-token": "people.bank_account.set_primary_admin",
        "x-realizes-features": [
          "PPL-F02"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.bank_accounts"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.bank_account.primary_changed",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "employee_id",
            "in": "path",
            "required": true,
            "description": "UUIDv7 of the `people.employees` row that owns the account.",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Bank account set primary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankAccount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/org-directory": {
      "get": {
        "operationId": "people.org_directory.list",
        "summary": "Search the company directory",
        "description": "Visibility-scoped colleague search (fsd 02 PPL-S07). Read-model projection — **may briefly lag** the source (db 03 §6, eventually consistent); non-`ACTIVE` employees are filtered from the default view. Sort whitelist: `display_name`, `department_name`, `location_name`.\n",
        "tags": [
          "people",
          "org-directory"
        ],
        "x-token": "people.org_directory.list",
        "x-realizes-features": [
          "PPL-F03",
          "XC-F13"
        ],
        "x-screens": [
          "PPL-S07"
        ],
        "x-touches-entities": [
          "people.org_directory"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Tokenised search over `search_vector` (name / department / location).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "department_name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "location_name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "work_location_id",
            "in": "query",
            "required": false,
            "description": "Filter to one site by its `org.work_locations` KEY rather than by the copied `location_name` label (#1318) — the axis ATT-S20's muster crew is derived on. A malformed uuid matches nothing; it never widens to the unfiltered roster.\n",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of directory entries, visibility-scoped.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/OrgDirectoryEntry"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/org-directory/admin": {
      "get": {
        "operationId": "people.org_directory.list_admin",
        "summary": "List the full-visibility admin directory (HR)",
        "description": "HR's unfiltered directory — unlike `people.org_directory.list`, includes non-`ACTIVE` rows and ignores subject-level `contact_visibility` (fsd 02 PPL-S16). Sort whitelist: `display_name`, `department_name`, `status`.\n",
        "tags": [
          "people",
          "org-directory"
        ],
        "x-token": "people.org_directory.list_admin",
        "x-realizes-features": [
          "PPL-F03"
        ],
        "x-screens": [
          "PPL-S16"
        ],
        "x-touches-entities": [
          "people.org_directory",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/EmployeeStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of directory entries, unfiltered.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AdminOrgDirectoryEntry"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/org-directory/{id}": {
      "get": {
        "operationId": "people.org_directory.get",
        "summary": "Get a colleague's Profile 360",
        "description": "Read-only colleague card — role, team, manager, tenure, skills, and contact where visibility allows (fsd 02 PPL-S08). Composes `org_directory` + `employee_profiles.skills` + the `employees.manager_id` chain (same schema, db-docs/00 §12 — not a cross-schema join). Contact fields are shown only per the subject's `contact_visibility` (`PUBLIC`/`COLLEAGUES`/`MANAGER_HR`/`PRIVATE`). `id` is the `org_directory` row id (= the subject's `employee_id`).\n",
        "tags": [
          "people",
          "org-directory"
        ],
        "x-token": "people.org_directory.get",
        "x-realizes-features": [
          "PPL-F03"
        ],
        "x-screens": [
          "PPL-S08"
        ],
        "x-touches-entities": [
          "people.org_directory",
          "people.employee_profiles",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The colleague's Profile 360.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ColleagueProfile"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/org-directory/org-chart": {
      "get": {
        "operationId": "people.org_directory.get_org_chart",
        "summary": "Get the reporting-line org chart",
        "description": "The org-tree view (fsd 02 PPL-S16) built entirely from the eventually-consistent `people.org_directory` projection, including reporting line, entity and org-unit placement. HR sees the full tree; a manager's session is restricted to their subtree.\n\nReturns EVERY reporting root in `roots`, not just one. A tenant can hold several disconnected clusters — two legal entities each with their own top exec, or an employee whose manager has left the company — and each cluster carries `legal_entity_id` so a client can label it. `root` is retained as `roots[0]` for existing consumers; it is the first cluster, not \"the\" chart.\n\nThe DRAWN set is everyone who is still employed — `ACTIVE`, `ON_LEAVE`, `SUSPENDED`. A manager on long leave or under suspension stays in the chart with their subtree attached, because they are still the manager; the chart used to filter to `ACTIVE`, so one \"Mark on long leave\" click detached a whole team into roots that looked exactly like genuine ones.\n\n`EXITED` and `ALUMNI` are not drawn, but their rows ARE read, and their reports are RE-PARENTED onto the nearest still-employed ancestor rather than promoted to roots (#1396). The walk climbs through any number of departed ancestors. Only a node with no employed ancestor at all becomes a root, and it carries `detached: true` so a client can tell a fragment from a genuine top of house.\n\n`status` is served only to a principal who also holds `people.org_directory.list_admin` (#1397). This one operation backs both the HR chart and the ESS colleague chart, and employment state — `SUSPENDED` in particular — is not something the ESS surface discloses about a colleague; the ESS roster read filters to `ACTIVE` for the same reason. For every other principal the field is ABSENT, not defaulted: a client must not read its absence as `ACTIVE`.\n\n`depth` is enforced: a node whose reports were cut by the cap returns `has_more_reports: true` with an empty `reports` array, and the response-level `truncated` flag is `true` when any node was cut. \"No reports\" and \"not expanded this far\" are never rendered as the same thing.\n",
        "tags": [
          "people",
          "org-directory"
        ],
        "x-token": "people.org_directory.get_org_chart",
        "x-realizes-features": [
          "PPL-F03"
        ],
        "x-screens": [
          "PPL-S16"
        ],
        "x-touches-entities": [
          "people.org_directory"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "name": "root_employee_id",
            "in": "query",
            "required": false,
            "description": "Root node of the subtree returned; defaults to the tenant root (HR) or the caller's own node (manager).",
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "name": "depth",
            "in": "query",
            "required": false,
            "description": "Maximum levels to expand below each root. Enforced server-side; a value outside 1..20 is a 422 naming `/depth` rather than a silently clamped answer.\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 3
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The org-chart clusters.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "roots",
                    "depth",
                    "truncated"
                  ],
                  "properties": {
                    "root": {
                      "description": "`roots[0]`, or `null` when the tenant has nobody on the books. Retained for existing consumers — read `roots` to see every cluster.\n",
                      "anyOf": [
                        {
                          "$ref": "#/components/schemas/OrgChartNode"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "roots": {
                      "description": "Every disconnected reporting cluster, ordered by the root's display name.",
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrgChartNode"
                      }
                    },
                    "depth": {
                      "description": "The depth cap actually applied to this response.",
                      "type": "integer"
                    },
                    "truncated": {
                      "description": "True when at least one node's reports were cut by `depth`.",
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/org-directory/exports": {
      "post": {
        "operationId": "people.org_directory.export",
        "summary": "Export the directory or org chart (Excel / PDF / PNG)",
        "description": "Async export job for the HR directory grid (PPL-S13) or the org-chart tree (Excel / PDF / org-chart PNG, PPL-S16). Runs on the jobs tier (XC-F08); the caller polls or is notified (XC-F05) when the file lands in object storage (XC-F07).\n",
        "tags": [
          "people",
          "org-directory"
        ],
        "x-token": "people.org_directory.export",
        "x-realizes-features": [
          "PPL-F03"
        ],
        "x-screens": [
          "PPL-S13",
          "PPL-S16"
        ],
        "x-touches-entities": [
          "people.org_directory",
          "people.org_directory_exports",
          "people.employees",
          "xc.object_refs"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "async",
        "x-emits-event": "people.org_directory.export_requested",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DirectoryExportInput"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Export accepted; poll `GET /org-directory/exports/{exportId}` for status and the completed download.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "export_id",
                    "status"
                  ],
                  "properties": {
                    "export_id": {
                      "$ref": "#/components/schemas/Uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "QUEUED"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/org-directory/exports/{exportId}": {
      "get": {
        "operationId": "people.org_directory.get_export",
        "summary": "Get a directory export's status and completed download",
        "description": "Polls the durable jobs-tier export run created by `people.org_directory.export`. The returned `file` is null until `status=COMPLETED`; it is a tenant-scoped, time-limited download handle, never an object-storage key. `FAILED` exposes a value-free failure message only.\n",
        "tags": [
          "people",
          "org-directory"
        ],
        "x-token": "people.org_directory.get_export",
        "x-realizes-features": [
          "PPL-F03"
        ],
        "x-screens": [
          "PPL-S13",
          "PPL-S16"
        ],
        "x-touches-entities": [
          "people.org_directory_exports",
          "xc.object_refs"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "exportId",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Current export state; `file` is present only when completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DirectoryExport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/virtual-ids/verify": {
      "post": {
        "operationId": "people.virtual_id.verify",
        "summary": "Verify a scanned Virtual ID QR and return its card",
        "description": "Verifies the HMAC-signed QR payload generated for a Virtual ID, confirms that it belongs to the caller's tenant and still maps to an ACTIVE, unexpired credential, then returns the same complete virtual-card projection as `people.virtual_id.get_me`. This is deliberately a same-tenant authenticated scan endpoint: the card includes employee contact and emergency-contact details, so a QR alone is not authorization to disclose them. Invalid, cross-tenant, revoked, suspended, expired, or stale/reissued credentials all return the same `404`, avoiding a credential-status oracle.\n",
        "tags": [
          "people",
          "virtual-id"
        ],
        "x-token": "people.virtual_id.verify",
        "x-realizes-features": [
          "PPL-F04",
          "XC-F10"
        ],
        "x-screens": [
          "PPL-S09"
        ],
        "x-touches-entities": [
          "people.virtual_ids",
          "people.employees",
          "people.employee_profiles",
          "people.personal_info",
          "people.contacts",
          "org.designations",
          "org.departments",
          "org.work_locations",
          "org.legal_entities"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VirtualIdVerifyInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A verified, current Virtual ID and its complete card details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VirtualId"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/virtual-ids/me": {
      "get": {
        "operationId": "people.virtual_id.get_me",
        "summary": "Get my active Virtual ID badge",
        "description": "The caller's active HMAC-signed QR+NFC credential (fsd 02 PPL-S09; db 03 §3 `people.virtual_ids`). The signed payload is cached on-device and verifies offline (XC-F10); the HMAC secret never transits the API (`key_id` references the signing key held in xc/KMS). Lazily auto-issues on first call — an employee with no `ACTIVE` badge yet gets one minted here, through the shared `@groundit/shared` mint every issuer uses, rather than a `404`; subsequent calls return that same badge (idempotent in effect). Since issue #1639 this is the LAST RESORT rather than the rule: the worker tier mints the badge on `people.employee.activated` and a daily sweep backfills anyone the event missed, so an employee opening this screen normally finds one already waiting. The response also carries card-display fields (name, employee number, designation, department, blood group, mobile number, work email, employee-since date, primary emergency contact, office details, and presigned `photo`) and a server-rendered `qr_png_base64` PNG of `signed_payload`, so the mobile badge screen needs no second round trip and no client-side QR renderer. `POST /virtual-ids/verify` returns this exact card shape after it verifies a scanned QR.\n",
        "tags": [
          "people",
          "virtual-id"
        ],
        "x-token": "people.virtual_id.get_me",
        "x-realizes-features": [
          "PPL-F04",
          "XC-F10"
        ],
        "x-screens": [
          "PPL-S09"
        ],
        "x-touches-entities": [
          "people.virtual_ids",
          "people.employees",
          "people.employee_profiles",
          "org.designations"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's active Virtual ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VirtualId"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/virtual-ids": {
      "get": {
        "operationId": "people.virtual_id.list",
        "summary": "List Virtual ID credentials (HR)",
        "description": "fsd 02 PPL-S17. Sort whitelist: `status`, `issued_at`, `valid_until`. Each row carries `employee_name` and `employee_no` from the API's own join (issue #1640), so a console does NOT need `people.org_directory.list_admin` to put a name beside a credential. There is no `POST /virtual-ids`: issuance was removed in issue #1639 and a badge is now minted by the worker tier on `people.employee.activated` (plus a daily backfill sweep), so this register is a read of credentials that already exist — `suspend`, `revoke` and `reissue` are the only writes.\n",
        "tags": [
          "people",
          "virtual-id"
        ],
        "x-token": "people.virtual_id.list",
        "x-realizes-features": [
          "PPL-F04"
        ],
        "x-screens": [
          "PPL-S17"
        ],
        "x-touches-entities": [
          "people.virtual_ids"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/VirtualIdStatus"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of Virtual ID credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/VirtualId"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/virtual-ids/{id}/suspend": {
      "post": {
        "operationId": "people.virtual_id.suspend",
        "summary": "Suspend a Virtual ID credential (HR)",
        "description": "fsd 02 PPL-S17. Forced outside the normal employee-lifecycle cascade (lost device / compromise). Must propagate to clients via event/notification; a cached credential may briefly still verify until the client refreshes (db 03 §3).\n",
        "tags": [
          "people",
          "virtual-id"
        ],
        "x-token": "people.virtual_id.suspend",
        "x-realizes-features": [
          "PPL-F04"
        ],
        "x-screens": [
          "PPL-S17"
        ],
        "x-touches-entities": [
          "people.virtual_ids"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.virtual_id.suspended",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VirtualIdRevokeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Virtual ID suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VirtualId"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/virtual-ids/{id}/revoke": {
      "post": {
        "operationId": "people.virtual_id.revoke",
        "summary": "Revoke a Virtual ID credential (HR)",
        "description": "fsd 02 PPL-S17; db 03 §3. Permanently invalidates the credential (exit, lost-device, or ahead of a re-issue). Must propagate to clients via event/notification (XC-F10).\n",
        "tags": [
          "people",
          "virtual-id"
        ],
        "x-token": "people.virtual_id.revoke",
        "x-realizes-features": [
          "PPL-F04"
        ],
        "x-screens": [
          "PPL-S17"
        ],
        "x-touches-entities": [
          "people.virtual_ids"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.virtual_id.revoked",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VirtualIdRevokeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Virtual ID revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VirtualId"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/virtual-ids/{id}/reissue": {
      "post": {
        "operationId": "people.virtual_id.reissue",
        "summary": "Re-issue a Virtual ID credential (HR)",
        "description": "fsd 02 PPL-S17; db 03 §3. Revokes the current row and mints a **new** row with an incremented `payload_version` so stale offline caches are detectable. Audited (XC-F06).\n",
        "tags": [
          "people",
          "virtual-id"
        ],
        "x-token": "people.virtual_id.reissue",
        "x-realizes-features": [
          "PPL-F04"
        ],
        "x-screens": [
          "PPL-S17"
        ],
        "x-touches-entities": [
          "people.virtual_ids"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.virtual_id.reissued",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "201": {
            "description": "New Virtual ID row issued (the prior row is revoked).",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VirtualId"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/app-settings/me": {
      "get": {
        "operationId": "people.app_setting.get_me",
        "summary": "Get my app settings",
        "description": "fsd 02 PPL-S10; db 03 §4 `people.app_settings`. No secrets here — MPIN/biometric credentials live in auth (XC-F03); this row carries only the employee's toggles.",
        "tags": [
          "people",
          "app-setting"
        ],
        "x-token": "people.app_setting.get_me",
        "x-realizes-features": [
          "PPL-F05"
        ],
        "x-screens": [
          "PPL-S10"
        ],
        "x-touches-entities": [
          "people.app_settings"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's app settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "people.app_setting.update_me",
        "summary": "Update my app settings",
        "description": "fsd 02 PPL-S10. `locale` change never stores an RTL flag (derived at render, XC-F11). `contact_visibility` change re-projects `org_directory.visibility`.\n",
        "tags": [
          "people",
          "app-setting"
        ],
        "x-token": "people.app_setting.update_me",
        "x-realizes-features": [
          "PPL-F05"
        ],
        "x-screens": [
          "PPL-S10"
        ],
        "x-touches-entities": [
          "people.app_settings"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.app_settings.updated",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppSettingsUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated app settings.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/employees/{id}/app-settings": {
      "get": {
        "operationId": "people.app_setting.get_admin",
        "summary": "Read an employee's access-relevant app settings (HR)",
        "description": "fsd 02 `PPL-S14` Access region; db 03 §4 `people.app_settings`. The **access-relevant** settings of ONE employee, read by an administrator — which language the product speaks to this person (`locale`) and who their contact details are visible to in the directory (`contact_visibility`). Closes the `app_settings` half of design-docs/04 `G-20` ③: `people.employee.get_360` answers `app_settings: null` and always has.\n\n**The projection is an allow-list, and the exclusion is the point.** `people.app_settings` also carries `notification_prefs`, `theme`, `timezone`, `biometric_login_enabled` and `mpin_enabled`. This door serves **`locale` and `contact_visibility` only** and will never serve the rest. `notification_prefs` in particular is deliberately excluded: which nudges a person has silenced is personal preference data that tells an administrator nothing about access, and becomes a performance conversation the moment it is on a screen HR reads. It stays reachable through the employee's own `people.app_setting.get_me` door and nowhere else. Adding a field here publishes it to HR — do it only with the same deliberation.\n\n**This read creates nothing.** `people.app_setting.get_me` lazily inserts a default row because the caller owns it and is about to edit it; a read of someone ELSE's settings must not have that side effect. So a missing row is an ordinary outcome answered as `is_set: false` with null values — \"this person has never set anything\" and \"this person chose the defaults\" are different facts and stay different here. An unknown or deleted employee id is a `404`, never an `is_set: false` `200`.\n",
        "tags": [
          "people",
          "app-setting"
        ],
        "x-token": "people.app_setting.get_admin",
        "x-realizes-features": [
          "PPL-F01",
          "PPL-F05"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "people.app_settings",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/Uuid"
            }
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The employee's access-relevant app settings. When no settings row exists the body is still a 200, with `is_set` false and the three values null — see the schema.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeAppSettingsAdmin"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/privacy-requests": {
      "get": {
        "operationId": "people.privacy_request.list",
        "summary": "List data-subject-rights requests",
        "description": "The caller's own DPDP/PDPL requests by default (fsd 02 PPL-S11); a caller holding the HR Admin/DPO grant sees the tenant-wide queue instead (fsd 02 PPL-S18) — the RESTRICTIVE `self` overlay is lifted for that role (db-docs/00 §4). Sort whitelist: `requested_at`, `status`.\n",
        "tags": [
          "people",
          "privacy-request"
        ],
        "x-token": "people.privacy_request.list",
        "x-realizes-features": [
          "PPL-F05",
          "XC-F08"
        ],
        "x-screens": [
          "PPL-S11",
          "PPL-S18"
        ],
        "x-touches-entities": [
          "people.privacy_requests"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "name": "request_type",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/PrivacyRequestType"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/PrivacyRequestStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of privacy requests.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PrivacyRequest"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "people.privacy_request.create",
        "summary": "Submit a data-subject-rights request",
        "description": "fsd 02 PPL-S11. Requires explicit consent (`CONSENT_REQUIRED`, 403, if the acknowledgement is missing — `_shared.yaml` `ErrorCode`). Created `status='RECEIVED'`; execution runs on the jobs tier (XC-F08) with a statutory `retention_hold_until` floor from the legal entity's pack (XC-F01). The record is a mutable case row (db 03 §4, `03 §4` reclassified 2026-07-02) but its consent trail **survives even after the underlying PII is erased**.\n",
        "tags": [
          "people",
          "privacy-request"
        ],
        "x-token": "people.privacy_request.create",
        "x-realizes-features": [
          "PPL-F05",
          "XC-F08"
        ],
        "x-screens": [
          "PPL-S11"
        ],
        "x-touches-entities": [
          "people.privacy_requests"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.privacy_request.received",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PrivacyRequestCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Privacy request received.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrivacyRequest"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/privacy-requests/{id}": {
      "get": {
        "operationId": "people.privacy_request.get",
        "summary": "Get a data-subject-rights request",
        "description": "fsd 02 PPL-S11 (own case) / PPL-S18 (HR/DPO case detail).",
        "tags": [
          "people",
          "privacy-request"
        ],
        "x-token": "people.privacy_request.get",
        "x-realizes-features": [
          "PPL-F05"
        ],
        "x-screens": [
          "PPL-S11",
          "PPL-S18"
        ],
        "x-touches-entities": [
          "people.privacy_requests"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The privacy request case.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrivacyRequest"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/privacy-requests/{id}/decisions": {
      "post": {
        "operationId": "people.privacy_request.progress",
        "summary": "Progress a data-subject-rights case (HR / DPO)",
        "description": "fsd 02 PPL-S18. Advances `status` (`RECEIVED → IN_PROGRESS → COMPLETED`/`PARTIALLY_FULFILLED`/ `REJECTED`), sets `handled_by`/`processed_at`/`outcome_note`/`export_storage_key`. A statutory `retention_hold_until` floor may force `PARTIALLY_FULFILLED` when some data is held. Append-only audited (XC-F06); the case record itself is retained even after the subject's PII is purged (db 03 §4).\n",
        "tags": [
          "people",
          "privacy-request"
        ],
        "x-token": "people.privacy_request.progress",
        "x-realizes-features": [
          "PPL-F05"
        ],
        "x-screens": [
          "PPL-S18"
        ],
        "x-touches-entities": [
          "people.privacy_requests"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "people.privacy_request.progressed",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PrivacyRequestProgressInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Case progressed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrivacyRequestDecision"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/engagement-moments": {
      "get": {
        "operationId": "people.engagement_moment.list",
        "summary": "List my moments (birthdays, anniversaries, milestones)",
        "description": "fsd 02 PPL-S12; db 03 §5 `people.engagement_moments`. A consume-and-emit projection — no source-of-truth data of its own; also projects to the home dashboard (`xc.dashboard_projections`, hosted on `XC-S07`) and notifications (`xc.notifications`) via the outbox.\n**Own moments only** (`x-rls-scope: self`): the caller's own rows, filtered to `is_visible = true`. A colleague's moment is never returned here — `occurs_on` on a `BIRTHDAY` or `WORK_ANNIVERSARY` row is the subject's date of birth or date of joining, and `years` is their age or tenure. The colleague-facing celebrations view is the Home \"up next\" row fed by the `xc.dashboard_projections` moments projection (design 03 §3.7, `XC-F09`), which is not built yet; when it lands it owes the `contact_visibility` gate that `is_visible` does not yet implement (`GAP-XRS-2`, api-docs 02 §4.1).\nSort whitelist: `occurs_on`.\n",
        "tags": [
          "people",
          "engagement-moment"
        ],
        "x-token": "people.engagement_moment.list",
        "x-realizes-features": [
          "PPL-F06"
        ],
        "x-screens": [
          "PPL-S12"
        ],
        "x-touches-entities": [
          "people.engagement_moments"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "self",
        "x-append-only": true,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "bearerJWT": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "$ref": "#/components/parameters/AcceptLanguage"
          },
          {
            "$ref": "#/components/parameters/CorrelationId"
          },
          {
            "name": "moment_type",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/MomentType"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of engagement moments.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/EngagementMoment"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "EmployeeAttendanceLocation": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "label"
        ],
        "description": "A shared reference or private circle. Coordinates must all be supplied together; existing IDs are retained.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "work_location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "work_location_name": {
            "type": [
              "string",
              "null"
            ],
            "readOnly": true,
            "description": "Resolved display name of the referenced work location."
          },
          "location_type": {
            "type": [
              "string",
              "null"
            ],
            "readOnly": true,
            "enum": [
              "OFFICE",
              "SITE",
              "CLIENT",
              "REMOTE",
              "FIELD",
              null
            ]
          },
          "latitude": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -90,
            "maximum": 90
          },
          "longitude": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -180,
            "maximum": 180
          },
          "radius_m": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 1,
            "maximum": 100000
          },
          "boundaries": {
            "type": "array",
            "readOnly": true,
            "description": "Resolved boundary geometry for this assignment; read-only and must be omitted from the PUT request because unknown fields are rejected. Empty means no boundary was resolved, not unrestricted attendance.",
            "items": {
              "$ref": "#/components/schemas/AttendanceLocationBoundary"
            }
          }
        }
      },
      "EmployeeAttendanceLocations": {
        "type": "object",
        "required": [
          "policy",
          "version",
          "locations"
        ],
        "properties": {
          "policy": {
            "type": "string",
            "enum": [
              "LEGACY",
              "ALLOWED_LOCATIONS"
            ]
          },
          "version": {
            "type": "integer",
            "minimum": 0
          },
          "locations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmployeeAttendanceLocation"
            }
          }
        }
      },
      "MyEmployeeAttendanceLocations": {
        "allOf": [
          {
            "$ref": "#/components/schemas/EmployeeAttendanceLocations"
          },
          {
            "type": "object",
            "required": [
              "allow_mobile_attendance_anywhere",
              "mobile_location_attestation_required",
              "attendance_location_unrestricted",
              "assigned_geofences"
            ],
            "properties": {
              "allow_mobile_attendance_anywhere": {
                "type": "boolean",
                "readOnly": true,
                "description": "HR/Admin-controlled exemption. When true, location assignments are retained but disabled in the HR editor and ignored for mobile location capture."
              },
              "mobile_location_attestation_required": {
                "type": "boolean",
                "readOnly": true,
                "description": "Effective mobile policy after the employee toggle, placement exemptions, and allowed-location policy are resolved."
              },
              "attendance_location_unrestricted": {
                "type": "boolean",
                "readOnly": true,
                "description": "True only when the effective allowed-location assignments include an explicit unrestricted Remote assignment."
              },
              "assigned_geofences": {
                "type": "array",
                "readOnly": true,
                "description": "Resolved boundary geometry for optional on-device pre-checks. Empty does not mean unrestricted unless the policy fields say so.",
                "items": {
                  "$ref": "#/components/schemas/AttendanceLocationBoundary"
                }
              }
            }
          }
        ]
      },
      "AttendanceLocationBoundary": {
        "type": "object",
        "readOnly": true,
        "additionalProperties": false,
        "required": [
          "id",
          "shape",
          "center",
          "radius_m",
          "polygon"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "employee_attendance_location_id": {
            "type": "string",
            "format": "uuid"
          },
          "shape": {
            "type": "string",
            "enum": [
              "CIRCLE",
              "POLYGON"
            ]
          },
          "center": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AttendanceGeoPoint"
              },
              {
                "type": "null"
              }
            ]
          },
          "radius_m": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 1
          },
          "polygon": {
            "anyOf": [
              {
                "type": "array",
                "minItems": 3,
                "items": {
                  "$ref": "#/components/schemas/AttendanceGeoPoint"
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "AttendanceGeoPoint": {
        "type": "object",
        "readOnly": true,
        "additionalProperties": false,
        "required": [
          "lat",
          "lng"
        ],
        "properties": {
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90
          },
          "lng": {
            "type": "number",
            "minimum": -180,
            "maximum": 180
          }
        }
      },
      "EmployeeStatus": {
        "type": "string",
        "enum": [
          "ACTIVE",
          "ON_LEAVE",
          "SUSPENDED",
          "EXITED",
          "ALUMNI"
        ],
        "description": "db 03 §1 `people.employee_status`."
      },
      "EmploymentType": {
        "type": "string",
        "enum": [
          "FULL_TIME",
          "PART_TIME",
          "CONTRACT",
          "INTERN",
          "CONSULTANT"
        ],
        "description": "db 03 §1 `people.employment_type`."
      },
      "WorkMode": {
        "type": "string",
        "enum": [
          "DESK",
          "FIELD",
          "MUSTER",
          "REMOTE",
          "HYBRID"
        ],
        "description": "db 03 §1 `people.work_mode` (migration 0196, issue #1316) — how this PERSON records attendance, and a third axis rather than a reuse: `EmploymentType` is the CONTRACT and `org.work_location_type` is a property of the PLACE. `DESK` web punch · `FIELD` geo/photo punch and a beat plan · `MUSTER` the site supervisor marks them and they punch nothing · `REMOTE` punches from anywhere · `HYBRID` desk or remote on any given day. Distinct from `recruit.work_mode` (`ONSITE`/`HYBRID`/`REMOTE`), which describes a job AD; the hand-off maps `ONSITE→DESK` and passes the other two through.\n"
      },
      "PendingHireStatus": {
        "type": "string",
        "enum": [
          "PENDING",
          "COMPLETED",
          "CANCELLED"
        ],
        "description": "db 03 §1.1 `people.pending_hire_status`. `PENDING` awaits an admin decision · `COMPLETED` the employee was created (`employee_id` is set) · `CANCELLED` the hire was abandoned and no employee exists.\n"
      },
      "OnboardingStage": {
        "type": "string",
        "enum": [
          "OFFER",
          "PERSONAL_DATA",
          "COMPENSATION",
          "PLACEMENT",
          "ACCESS",
          "COMPLETED",
          "CANCELLED"
        ],
        "description": "db 03 `people.onboarding_stage` (migration `0064`). The five working stages are **strictly ordered** and name where the flow currently rests (the stage whose form is open); the service refuses skips and reversals. `COMPLETED` and `CANCELLED` are terminal, and only a terminal flow may name an `employee_id` — `COMPLETED` must.\n"
      },
      "OnboardingAdvanceStage": {
        "type": "string",
        "enum": [
          "OFFER",
          "PERSONAL_DATA",
          "COMPENSATION",
          "PLACEMENT",
          "ACCESS"
        ],
        "description": "The subset of `OnboardingStage` a caller may declare on `people.onboarding.advance` — the stage being **completed**. The two terminal stages are reached through `.complete` / `.cancel`, never by advancing.\n"
      },
      "OnboardingCompensationRevisionReason": {
        "type": "string",
        "enum": [
          "HIRE",
          "PROMOTION",
          "ANNUAL_REVISION",
          "CORRECTION",
          "OTHER"
        ],
        "description": "Local read/write projection of `pay.compensation_revision_reason` (db 07 §1.1) — declared here rather than imported, per 00 §5 (\"a module surfacing another module's data declares its own projection\"). A guided onboarding always writes `HIRE`; the other values arrive from `engage` revisions.\n"
      },
      "OnboardingRenderStatus": {
        "type": "string",
        "enum": [
          "QUEUED",
          "RUNNING",
          "COMPLETED",
          "FAILED"
        ],
        "description": "Projection of `org.template_renders.status` (db 02, ADR 0023). The offer letter is rendered asynchronously on the jobs tier, so a flow can legitimately sit at `QUEUED`/`RUNNING` for a moment after the `OFFER` stage completes.\n"
      },
      "Gender": {
        "type": "string",
        "enum": [
          "MALE",
          "FEMALE",
          "OTHER",
          "UNDISCLOSED"
        ],
        "description": "db 03 §2 `people.gender`. `UNDISCLOSED` = declined to state, distinct from unset."
      },
      "MaritalStatus": {
        "type": "string",
        "enum": [
          "SINGLE",
          "MARRIED",
          "DIVORCED",
          "WIDOWED",
          "OTHER"
        ],
        "description": "db 03 §2 `people.marital_status`."
      },
      "ContactRelationship": {
        "type": "string",
        "enum": [
          "SPOUSE",
          "PARENT",
          "SIBLING",
          "CHILD",
          "FRIEND",
          "OTHER"
        ],
        "description": "db 03 §2 `people.contact_relationship`."
      },
      "DependentRelationship": {
        "type": "string",
        "enum": [
          "SPOUSE",
          "CHILD",
          "PARENT",
          "PARENT_IN_LAW",
          "OTHER"
        ],
        "description": "db 03 §2 `people.dependent_relationship`."
      },
      "BankVerificationStatus": {
        "type": "string",
        "enum": [
          "PENDING",
          "VERIFIED",
          "FAILED"
        ],
        "description": "db 03 §2 `people.bank_verification_status`."
      },
      "ContactVisibility": {
        "type": "string",
        "enum": [
          "PUBLIC",
          "COLLEAGUES",
          "MANAGER_HR",
          "PRIVATE"
        ],
        "description": "db 03 §4 `people.contact_visibility`."
      },
      "VirtualIdStatus": {
        "type": "string",
        "enum": [
          "ACTIVE",
          "SUSPENDED",
          "REVOKED",
          "EXPIRED"
        ],
        "description": "db 03 §3 `people.virtual_id_status`."
      },
      "PrivacyRequestType": {
        "type": "string",
        "enum": [
          "EXPORT",
          "ERASURE",
          "RECTIFICATION",
          "RESTRICTION"
        ],
        "description": "db 03 §4 `people.privacy_request_type`."
      },
      "PrivacyRegime": {
        "type": "string",
        "enum": [
          "DPDP",
          "PDPL"
        ],
        "description": "db 03 §4 `people.privacy_regime` — DPDP (India) / PDPL (KSA)."
      },
      "PrivacyRequestStatus": {
        "type": "string",
        "enum": [
          "RECEIVED",
          "IN_PROGRESS",
          "COMPLETED",
          "PARTIALLY_FULFILLED",
          "REJECTED"
        ],
        "description": "db 03 §4 `people.privacy_request_status`."
      },
      "MomentType": {
        "type": "string",
        "enum": [
          "BIRTHDAY",
          "WORK_ANNIVERSARY",
          "MILESTONE",
          "WELCOME",
          "PROMOTION"
        ],
        "description": "db 03 §5 `people.moment_type`."
      },
      "VerifyDecision": {
        "type": "string",
        "enum": [
          "APPROVE",
          "REJECT"
        ],
        "description": "Maker-checker decision outcome (fsd 02 PPL-S15)."
      },
      "EssAccountFacet": {
        "type": "object",
        "description": "The ESS (employee self-service) sign-in state of one employee (issue #1411, widened by #1638) — what `PPL-S14`'s access card renders. `null` on the employee record when no principal has ever been provisioned: the hire whose corporate mailbox is not cut yet (`NO_WORK_EMAIL`).\n\n**`invitation` and `intent` were removed in #1638**, with the invitation limb itself. They described a seven-day ADR 0061 claim link and its deferred delivery; an `EMPLOYEE` principal is now adopted by a PROVEN OTP sign-in against `email` below, so there is no link to report the expiry of.\n",
        "required": [
          "identity_id",
          "status",
          "realm_ready",
          "claimed",
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "identity_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "INVITED",
              "ACTIVE",
              "SUSPENDED",
              "DEACTIVATED"
            ],
            "description": "db 14 §1 `xc.identities.status`. `INVITED` = provisioned, never signed in. `ACTIVE` = signed in at least once. `SUSPENDED`/`DEACTIVATED` are administrative states in which sign-in is refused.\n"
          },
          "realm_ready": {
            "type": "boolean",
            "description": "**`keycloak_sub IS NOT NULL` (#1638).** The realm knows this person, so a sign-in code can be sent today — NOT that they have signed in. This is the field the facet was missing: `status` alone collapses \"the account is ready and they simply have not used it\" into the same `INVITED` as \"no account has been prepared\", and those are the two states HR most needs to tell apart. The subject is written asynchronously by `people.ess_keycloak_provision` and lazily by `POST /auth/identify`, so `false` means \"finishing\", not \"broken\".\n"
          },
          "claimed": {
            "type": "boolean",
            "description": "**A derived pair, not a column.** True only when the identity carries a `keycloak_sub` AND is `ACTIVE`. GroundIT writes the Keycloak subject at PROVISIONING time — weeks before anybody signs in — so the subject alone does not mean \"claimed\"; what does is the `ACTIVE` status that only a real first sign-in reaches (`AuthWriter.completeEmployeeEssLink`). A surface reading `keycloak_sub IS NOT NULL` as \"claimed\" would tell HR an employee had signed in on their hire date. That same column read as `realm_ready` above is a different question with a different answer.\n"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The address this principal signs in with — `xc.identities.email`, the login key."
          }
        }
      },
      "EssProvisionOutcome": {
        "type": "object",
        "description": "What ESS provisioning did inside a hire transaction (issue #1411; the same three facts `OnboardingService.complete` has surfaced since #800).\n",
        "required": [
          "identity_id",
          "created",
          "skipped_reason"
        ],
        "additionalProperties": false,
        "properties": {
          "identity_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The `EMPLOYEE` principal, or `null` when nothing was minted."
          },
          "created": {
            "type": "boolean",
            "description": "`true` only when THIS call inserted the row — a replayed or resumed hire reports `false`."
          },
          "skipped_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "NO_WORK_EMAIL",
              null
            ],
            "description": "Non-null exactly when `identity_id` is null."
          }
        }
      },
      "EssAccessOutcome": {
        "type": "object",
        "description": "**Can this newly-hired person sign in?** (#1653) — the three-state verdict the hire computes from what its own transaction produced, and the same vocabulary the directory row and the profile access card speak, so one hire reads the same on three surfaces.\n\n`PENDING_REALM` IS A HEALTHY OUTCOME, not a warning. The Keycloak subject is deliberately asynchronous (owner ruling, #1653: the hire transaction must not make a third-party HTTP call while holding employee row locks), written by the `people.ess_keycloak_provision` outbox job and its daily sweep, and provisioned lazily by `POST /auth/identify` on first contact — so an employee whose hire reported `PENDING_REALM` can request a sign-in code immediately. It is reported because it is indistinguishable from `BLOCKED` on a directory row otherwise, and it is NOT the same thing.\n",
        "required": [
          "state"
        ],
        "additionalProperties": false,
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "READY",
              "PENDING_REALM",
              "BLOCKED"
            ],
            "description": "`READY` — the principal exists and its realm subject is bound. `PENDING_REALM` — the principal exists, the subject has not been written yet (see above). `BLOCKED` — something a human must fix, named by `reason`.\n"
          },
          "reason": {
            "type": "string",
            "enum": [
              "NO_WORK_EMAIL",
              "NO_BASELINE_ROLE",
              "BASELINE_ROLE_UNSAFE"
            ],
            "description": "Present only with `BLOCKED`. `NO_WORK_EMAIL` — reachable through `provision_ess: false` with no address supplied (the create otherwise answers 422), and through an employment status in which no sign-in may be created. `NO_BASELINE_ROLE` — the tenant's RBAC catalogue has no seeded `EMPLOYEE` role, so the account would sign in to an empty portal. `BASELINE_ROLE_UNSAFE` — that role exists but is no longer `is_system` + `SELF`-scoped + `ACTIVE`, so ADR 0064's conditions do not hold and it is refused rather than issued.\n"
          }
        }
      },
      "EmployeeCreated": {
        "description": "The created employee, plus what the hire did about self-service access (#1411 / #1653). ADDITIVE: every field of `Employee` is unchanged and in its existing place. `ess_invitation` rode here until #1638 and is gone with the invitation limb.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Employee"
          },
          {
            "type": "object",
            "required": [
              "ess_account",
              "ess_access"
            ],
            "properties": {
              "ess_account": {
                "$ref": "#/components/schemas/EssProvisionOutcome"
              },
              "ess_access": {
                "$ref": "#/components/schemas/EssAccessOutcome"
              }
            }
          }
        ]
      },
      "EmployeeWithEssAccount": {
        "description": "The employee record plus its ESS sign-in facet (#1411) — what `people.employee.get` returns. ADDITIVE: `Employee` is unchanged, and `ess_account` is `null` when no principal exists.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Employee"
          },
          {
            "type": "object",
            "required": [
              "ess_account"
            ],
            "properties": {
              "ess_account": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/EssAccountFacet"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        ]
      },
      "MyEmployee": {
        "description": "The caller's own employment record. `date_of_birth` and `mobile_number` are self-only PII projections from `people.personal_info`; `mobile_number` is the stored `personal_phone` under the mobile contract's familiar name. Both are `null` until the employee supplies them.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Employee"
          },
          {
            "type": "object",
            "properties": {
              "date_of_birth": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "mobile_number": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "Employee": {
        "type": "object",
        "description": "db 03 §1 `people.employees` — the canonical employment record.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_no",
              "full_name",
              "status",
              "employment_type",
              "work_mode",
              "legal_entity_id",
              "date_of_joining"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employee_no": {
                "$ref": "#/components/schemas/BusinessNo"
              },
              "full_name": {
                "type": "string"
              },
              "work_email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "$ref": "#/components/schemas/EmployeeStatus"
              },
              "status_reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "status_changed_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Timestamp"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "employment_type": {
                "$ref": "#/components/schemas/EmploymentType"
              },
              "work_mode": {
                "$ref": "#/components/schemas/WorkMode"
              },
              "allow_mobile_attendance_anywhere": {
                "type": "boolean",
                "readOnly": true,
                "description": "HR/Admin-controlled employee attendance exemption. Stored default is `false`; exposed here for visibility only."
              },
              "mobile_location_attestation_required": {
                "type": "boolean",
                "readOnly": true,
                "description": "Returned by `GET /employees/me` only. Computed shared-policy result for mobile IN/OUT submission. `false` when the stored extra exemption is enabled, when work_mode is REMOTE/HYBRID/FIELD, or when the assigned work location is REMOTE, or an unrestricted ALLOWED_LOCATIONS assignment exists; `true` otherwise. Re-evaluated on every GET and never accepted by employee update.\n"
              },
              "attendance_location_unrestricted": {
                "type": "boolean",
                "readOnly": true,
                "description": "Returned by `GET /employees/me` only. True when the effective ALLOWED_LOCATIONS assignments contain an explicit unrestricted Remote assignment."
              },
              "legal_entity_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "department_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "designation_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "grade_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "org_unit_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "work_location_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "manager_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "fk→people.employees; manager_id <> id enforced."
              },
              "candidate_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "ref→recruit.candidates — onboarding handover origin."
              },
              "rehired_from_employee_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "fk→people.employees (self) — the prior stint this rehire continues."
              },
              "date_of_joining": {
                "$ref": "#/components/schemas/DateOnly"
              },
              "date_of_confirmation": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "date_of_exit": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "designation_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.designations, by projection/service (not a join)."
              },
              "department_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.departments, by projection/service."
              },
              "manager_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.employees (manager_id), by projection/service."
              },
              "work_location_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.work_locations, by projection/service."
              },
              "grade_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.grades, by projection/service."
              },
              "org_unit_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.org_units, by projection/service."
              },
              "legal_entity_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.legal_entities (display_name), by projection/service."
              },
              "pay_model": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "MONTHLY_SALARY",
                  "DAILY_WAGE",
                  "PIECE_RATE",
                  null
                ],
                "description": "ref→pay.employee_compensation.pay_model of the OPEN compensation row (`effective_to IS NULL`), by projection (not a join) — the ADR 0033 wage-class axis, added #1171 so the attendance tier can tell site crew (DAILY_WAGE / PIECE_RATE) from desk staff without a per-employee pay read. `null` = the employee has no compensation row yet — unclassified, NOT salaried. Deliberately the classification alone: no amount, structure, or pay-group detail rides on this read, so the `people.employee.list` grant does not become a door into pay data (the amounts stay behind `pay.employee_compensation.list`).\n"
              },
              "ess_access": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "READY",
                  "PENDING_REALM",
                  "BLOCKED",
                  null
                ],
                "description": "**Can this person sign in?** (#1653) — the same three-state verdict `people.employee.create` reports and `PPL-S14`'s access card renders, projected onto the grid so a hire that cannot use the product is visible without opening every profile in turn. Derived by projection from the employee's `xc.identities` `EMPLOYEE` principal: `READY` — the principal exists and its realm subject is bound; `PENDING_REALM` — the principal exists and the subject has not been written yet, which is a healthy fresh hire (the subject is asynchronous and `POST /auth/identify` provisions it lazily); `BLOCKED` — no principal, or one that is SUSPENDED/DEACTIVATED. DELIBERATELY NOT THE IDENTITY ROW: one enum rides out, so the directory grant does not become a projection of `xc.identities`. Computed by a **correlated scalar subselect**, not a lateral join — in the FROM of a keyset page a lateral is evaluated for every row in the tenant before the outer Sort→Limit, while a scalar in the target list runs once per RETURNED row (#1638 D2). `null` only on a build that predates this field.\n"
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "PositionChangeReason": {
        "type": "string",
        "enum": [
          "HIRE",
          "PROMOTION",
          "TRANSFER",
          "MANAGER_CHANGE",
          "CORRECTION",
          "OTHER"
        ],
        "description": "db 03 §1.4 `people.position_change_reason` — why the period was opened. `MANAGER_CHANGE` marks a period whose only moved axis is the reporting line, so a consumer can filter reporting-line churn from substantive placement moves without a second table.\n"
      },
      "PositionHistoryEntry": {
        "type": "object",
        "description": "db 03 §1.4 `people.position_history` — ONE effective-dated placement period. Every id is a snapshot of what was in force for that period; every `*_name` is a projection of that id's name TODAY (not a join, and not the name as it read then). `effective_to: null` marks the open period.\n",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "legal_entity_id",
              "employment_type",
              "work_mode",
              "effective_from",
              "change_reason"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "legal_entity_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employment_type": {
                "$ref": "#/components/schemas/EmploymentType"
              },
              "work_mode": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WorkMode"
                  }
                ],
                "description": "The work mode in force FOR THIS PERIOD — a snapshot, like every id beside it."
              },
              "department_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "designation_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "grade_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "org_unit_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "work_location_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "manager_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "ref→people.employees — the reporting manager DURING this period."
              },
              "effective_from": {
                "$ref": "#/components/schemas/DateOnly"
              },
              "effective_to": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "`null` = the OPEN period, the placement in force today. At most one per employee."
              },
              "change_reason": {
                "$ref": "#/components/schemas/PositionChangeReason"
              },
              "source_ref": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "ref→ the record that caused the move (`engage.promotions.id`); null for an unmediated HR edit."
              },
              "notes": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "designation_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.designations, by projection (not a join)."
              },
              "department_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.departments, by projection."
              },
              "grade_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.grades, by projection."
              },
              "org_unit_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.org_units, by projection."
              },
              "work_location_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.work_locations, by projection."
              },
              "legal_entity_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.legal_entities (display_name), by projection."
              },
              "manager_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.employees (manager_id), by projection."
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "PendingHire": {
        "type": "object",
        "description": "db 03 §1.1 `people.pending_hires` — a recruit handover parked at the `maxEmployees` plan cap. Carries the whole retained handover payload plus the refusal snapshot and, once resolved, the decision.\n",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "candidate_id",
              "employee_no",
              "full_name",
              "employment_type",
              "legal_entity_id",
              "date_of_joining",
              "status",
              "parked_reason"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "candidate_id": {
                "$ref": "#/components/schemas/Uuid",
                "description": "ref→recruit.candidates — the handover origin. One LIVE parked row per candidate."
              },
              "employee_no": {
                "$ref": "#/components/schemas/BusinessNo"
              },
              "full_name": {
                "type": "string"
              },
              "work_email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "employment_type": {
                "$ref": "#/components/schemas/EmploymentType"
              },
              "legal_entity_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "date_of_joining": {
                "$ref": "#/components/schemas/DateOnly"
              },
              "status": {
                "$ref": "#/components/schemas/PendingHireStatus"
              },
              "parked_reason": {
                "type": "string",
                "description": "Why the handover was parked. `PLAN_LIMIT_EXCEEDED` is the only reason today."
              },
              "parked_headcount": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Counted headcount at park time — the number the refusal was decided on, not today's."
              },
              "parked_limit": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The `maxEmployees` cap at park time."
              },
              "employee_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "fk→people.employees — set only on COMPLETED; null while PENDING and forever on CANCELLED."
              },
              "decided_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "decided_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Timestamp"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "decision_note": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "PendingHireDecisionInput": {
        "type": "object",
        "description": "Optional note recorded against a `complete`/`cancel` decision on a parked hire.",
        "additionalProperties": false,
        "properties": {
          "decision_note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          }
        }
      },
      "EmployeeCreateInput": {
        "type": "object",
        "description": "HR direct-hire input (db 03 §1 `people.employees`). `status` is not accepted — a new hire is always `ACTIVE`; use the lifecycle actions to move it. `candidate_id` is not accepted either: it is stamped only by the recruit handover path, so its absence is the marker of a direct hire. `tenant_id` is never on the wire (00 §5) — tenancy comes from the auth context.\n",
        "required": [
          "full_name",
          "employment_type",
          "legal_entity_id",
          "date_of_joining"
        ],
        "additionalProperties": false,
        "properties": {
          "employee_no": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BusinessNo"
              }
            ],
            "description": "Optional. Omit to have the tenant sequence generate it (`<prefix><padded seq>`, configured by the `people.employee_no` key of `admin.tenant_config`, default `EMP-` / width 4). Must be unique per tenant and is never reused (db 00 §3).\n"
          },
          "full_name": {
            "type": "string",
            "minLength": 1,
            "description": "Canonical display name on the master/directory."
          },
          "work_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Corporate email; tenant-unique where present. **Required unless `provision_ess` is `false`** (#1653): it is the key the self-service sign-in is built on, so omitting it is a `422` naming `/work_email` rather than a `201` that quietly produced an un-signable-in employee. **Supplying it provisions the account whatever `provision_ess` says** — the flag governs the refusal, never the provisioning.\n"
          },
          "provision_ess": {
            "type": "boolean",
            "default": true,
            "description": "Whether this create should produce a self-service login (#1653). **Defaults to `true`** — the overwhelmingly common case is a hire who needs to use the product, and a default that silently produced employees without a login is the defect this closes. Send `false` for the bulk/roster shape: a caller loading people whose corporate mailboxes are not cut yet. Such a record is created with `ess_access: BLOCKED (NO_WORK_EMAIL)` and finished later by the resume seam on `people.employee.update`. A typed opt-out rather than an absent field, so \"no sign-in\" is a decision somebody made and not an omission nobody noticed.\n\n**It suppresses the `422` and nothing else.** A row that DOES carry a `work_email` is provisioned normally even with the flag `false`: the alternative — honouring the flag over the address — records a false `NO_WORK_EMAIL` on a row that has one, and that row is then unreachable by every repair path in the product, because the resume seam's `null` → value arm can never fire for an address that was present from birth.\n"
          },
          "employment_type": {
            "$ref": "#/components/schemas/EmploymentType"
          },
          "work_mode": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WorkMode"
              }
            ],
            "description": "Optional. Omit and the column's `DESK` default stands — the mode every employee effectively had before #1316 — so an unclassified hire is never silently made something else.\n"
          },
          "legal_entity_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              }
            ],
            "description": "ref→org.legal_entities — binds the market/compliance pack, currency and calendar."
          },
          "department_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "designation_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "grade_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "org_unit_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "work_location_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "manager_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "fk→people.employees — must resolve to a live employee in the same tenant."
          },
          "rehired_from_employee_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The prior stint this rehire continues (db 03 §1, rev. 2026-07-02). A rehire mints a NEW row with a NEW `employee_no`; this only soft-links the stints.\n"
          },
          "date_of_joining": {
            "$ref": "#/components/schemas/DateOnly"
          },
          "date_of_confirmation": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ],
            "description": "Must be on or after `date_of_joining` (db 03 §1 check constraint)."
          }
        }
      },
      "EmployeeUpdateInput": {
        "type": "object",
        "description": "Org-placement/employment fields HR may edit, plus the two identity fields (`work_email`, `employee_no`) that carry consequences of their own. Status is changed only via the lifecycle actions.\n",
        "additionalProperties": false,
        "properties": {
          "employee_no": {
            "type": "string",
            "minLength": 1,
            "example": "SM-0421",
            "description": "The employee CODE, correctable since #1652 (it was a write-once field before, so a code typed wrongly at hire was permanent). **Requires `people.employee.create` in addition to `people.employee.update`** — a request carrying this field without the mint token is refused `403 TOKEN_DENIED`, never silently dropped. Trimmed exactly as on `EmployeeCreateInput`. Tenant-unique over LIVE rows (`WHERE deleted_at IS NULL`, migration `0224`): a clash with an employee on the books is a `422` `uniqueness-business` on `/employee_no`, while a code held only by a soft-deleted record is free. `null` is refused — the column is `NOT NULL` and \"clear the code\" is not an operation. The change is **prospective**: documents already issued keep the code they were minted with, and the old → new pair is recorded in `audit.audit_log` and in a `CORRECTION` `people.position_history` period.\n"
          },
          "work_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Corporate email; tenant-unique where present, exactly as on `EmployeeCreateInput` (a collision is a `422` `uniqueness-business` on `/work_email`, from this UPDATE as much as from the INSERT). Added by #800, which also made it consequential: supplying it for the FIRST time provisions the employee's ESS account, while correcting an address that already has one leaves `xc.identities.email` untouched — see the operation description for why that asymmetry is deliberate.\n"
          },
          "department_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "designation_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "grade_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "org_unit_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "work_location_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "manager_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "employment_type": {
            "$ref": "#/components/schemas/EmploymentType"
          },
          "work_mode": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WorkMode"
              }
            ],
            "description": "A PLACEMENT axis (#1316): moving it opens a new `people.position_history` period tagged `TRANSFER`, exactly as moving a department does, and rides the same audit row and `transfer` outbox hint. Not nullable — the column is `NOT NULL DEFAULT 'DESK'`.\n"
          },
          "allow_mobile_attendance_anywhere": {
            "type": "boolean",
            "description": "HR/Admin-only stored exemption for mobile attendance location enforcement. Defaults to `false`. Employee self-service cannot send this property; changing it does not backfill or rewrite attendance history or position-history periods.\n"
          },
          "date_of_confirmation": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "EmployeeStatusActionInput": {
        "type": "object",
        "description": "Common body for lifecycle actions that only need a free-text reason.",
        "additionalProperties": false,
        "properties": {
          "status_reason": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "EmployeeLongLeaveInput": {
        "type": "object",
        "description": "Body for `people.employee.mark_on_leave`. The reason is required — long leave is a sticky employment status with no dates, and the reason is the only record of why it was set.\n",
        "required": [
          "status_reason"
        ],
        "additionalProperties": false,
        "properties": {
          "status_reason": {
            "type": "string",
            "minLength": 1,
            "example": "Sabbatical — 6 months"
          }
        }
      },
      "EmployeeExitInput": {
        "type": "object",
        "required": [
          "date_of_exit"
        ],
        "additionalProperties": false,
        "properties": {
          "date_of_exit": {
            "$ref": "#/components/schemas/DateOnly"
          },
          "status_reason": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "EmployeeImportPreviewInput": {
        "type": "object",
        "description": "An uploaded employee CSV or XLSX for the bulk-import console (#631). The header row must equal the template the wizard downloads — the same 14 columns `people.employee.create` accepts; a file whose columns cannot be trusted is rejected whole. Exactly one of `content` (CSV text), `content_base64` (small XLSX), or `storage_key` (async, large file) is present alongside `filename`.\n",
        "required": [
          "filename"
        ],
        "additionalProperties": false,
        "properties": {
          "filename": {
            "type": "string",
            "maxLength": 255,
            "description": "The uploaded file's name — display and audit only; its `.csv` / `.xlsx` extension selects the parser when `format` is absent."
          },
          "content": {
            "type": "string",
            "description": "The CSV text (inline path, 2 MB / 500 data rows maximum)."
          },
          "content_base64": {
            "type": "string",
            "description": "A small XLSX workbook as base64 (inline path; must decode to ≤ 2 MB)."
          },
          "storage_key": {
            "type": "string",
            "description": "The tenant-prefixed object key of a file already PUT via `/employees/imports/uploads`. Selecting this routes to the ASYNC path: the row is created `UPLOADED` and the jobs tier parses it in the background.\n"
          },
          "format": {
            "type": "string",
            "enum": [
              "csv",
              "xlsx"
            ],
            "description": "Explicit source format; falls back to the filename extension when absent."
          }
        }
      },
      "EmployeeImportUploadUrlResult": {
        "type": "object",
        "description": "The presigned PUT target for the async path. The client PUTs the raw file bytes to `url`, includes every `headers` entry verbatim (they are part of the signature), then calls `preview` with `key` as `storage_key`.",
        "required": [
          "import_id",
          "url",
          "key"
        ],
        "additionalProperties": false,
        "properties": {
          "import_id": {
            "$ref": "#/components/schemas/Uuid",
            "description": "The import id this upload is destined for (used as the storage-key entity id)."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The presigned PUT URL (15-minute TTL)."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Headers that MUST accompany the PUT (e.g. the signed Content-Type)."
          },
          "key": {
            "type": "string",
            "description": "The generated tenant-prefixed storage key to send back as `storage_key`."
          }
        }
      },
      "EmployeeImportAsyncPreviewResult": {
        "type": "object",
        "description": "The async path's immediate answer — the wizard then polls `GET /employees/imports/{id}` until `status` settles.",
        "required": [
          "import_id",
          "filename",
          "status"
        ],
        "additionalProperties": false,
        "properties": {
          "import_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "filename": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "UPLOADED"
            ]
          }
        }
      },
      "EmployeeImportRowError": {
        "type": "object",
        "required": [
          "pointer",
          "rule",
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "pointer": {
            "type": "string",
            "description": "JSON Pointer within the row (e.g. `/full_name`)."
          },
          "rule": {
            "type": "string",
            "description": "The validation-field taxonomy rule (`required` / `format` / `length` / `cross-field` / `uniqueness-business`)."
          },
          "message": {
            "type": "string"
          }
        }
      },
      "EmployeeImportRow": {
        "type": "object",
        "required": [
          "row_no",
          "status",
          "errors"
        ],
        "additionalProperties": false,
        "properties": {
          "row_no": {
            "type": "integer",
            "description": "1-based spreadsheet row number (1 = header)."
          },
          "status": {
            "type": "string",
            "enum": [
              "OK",
              "ERROR"
            ]
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmployeeImportRowError"
            }
          }
        }
      },
      "EmployeeImportStatus": {
        "type": "string",
        "enum": [
          "UPLOADED",
          "PREVIEWED",
          "COMMITTED",
          "PARTIAL",
          "FAILED",
          "CANCELLED"
        ],
        "description": "The import-row state machine (`people.employee_imports.status`; `UPLOADED` added by migration 0190 for the async path)."
      },
      "EmployeeImportPreviewResult": {
        "type": "object",
        "required": [
          "import_id",
          "filename",
          "counts",
          "rows"
        ],
        "additionalProperties": false,
        "properties": {
          "import_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "filename": {
            "type": "string"
          },
          "counts": {
            "type": "object",
            "required": [
              "total",
              "valid",
              "invalid"
            ],
            "properties": {
              "total": {
                "type": "integer"
              },
              "valid": {
                "type": "integer",
                "description": "Rows that passed every check and will commit."
              },
              "invalid": {
                "type": "integer"
              }
            }
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmployeeImportRow"
            }
          }
        }
      },
      "EmployeeImportCommitResult": {
        "type": "object",
        "required": [
          "import_id",
          "status",
          "counts"
        ],
        "additionalProperties": false,
        "properties": {
          "import_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "COMMITTED",
              "PARTIAL",
              "FAILED"
            ]
          },
          "counts": {
            "type": "object",
            "required": [
              "created",
              "failed",
              "parked"
            ],
            "properties": {
              "created": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "parked": {
                "type": "integer",
                "description": "Rows the kernel refused that were parked in the pending-hires review queue (source IMPORT, migration 0190) rather than lost."
              }
            }
          },
          "errors": {
            "type": "array",
            "description": "The per-row failures, present when the outcome is `PARTIAL` or `FAILED`.",
            "items": {
              "$ref": "#/components/schemas/EmployeeImportRow"
            }
          },
          "created_employees": {
            "type": "array",
            "description": "The created employee rows, so the wizard can refresh the directory in place.",
            "items": {
              "$ref": "#/components/schemas/Employee"
            }
          }
        }
      },
      "EmployeeImportSummary": {
        "type": "object",
        "required": [
          "import_id",
          "filename",
          "status",
          "source_format"
        ],
        "additionalProperties": false,
        "properties": {
          "import_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "filename": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/EmployeeImportStatus"
          },
          "source_format": {
            "type": "string",
            "enum": [
              "csv",
              "xlsx"
            ],
            "description": "The source file's format (drives how the bytes were parsed)."
          },
          "structural_error": {
            "type": [
              "string",
              "null"
            ],
            "description": "The whole-file rejection from the async worker (bad header, unreadable workbook). Null when the file parsed."
          },
          "counts": {
            "type": "object",
            "required": [
              "total",
              "valid",
              "invalid"
            ],
            "properties": {
              "total": {
                "type": "integer"
              },
              "valid": {
                "type": "integer"
              },
              "invalid": {
                "type": "integer"
              }
            }
          },
          "rows": {
            "type": "array",
            "description": "Every preview verdict (row_no + status + errors), so an async preview renders the same review panel as a sync one.",
            "items": {
              "$ref": "#/components/schemas/EmployeeImportRow"
            }
          },
          "commit_errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmployeeImportRow"
            }
          },
          "commit_summary": {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "created",
                  "failed",
                  "parked"
                ],
                "properties": {
                  "created": {
                    "type": "integer"
                  },
                  "failed": {
                    "type": "integer"
                  },
                  "parked": {
                    "type": "integer"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "RevealPiiInput": {
        "type": "object",
        "required": [
          "reauth_token"
        ],
        "additionalProperties": false,
        "properties": {
          "reauth_token": {
            "type": "string",
            "description": "Fresh re-authentication assertion (password/2FA step-up) required to authorize the reveal."
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Subset of maskable fields to unmask, e.g. [\"pan\", \"bank_account_number\"]; omit for all."
          }
        }
      },
      "RevealPiiResult": {
        "type": "object",
        "description": "Transient unmasked values; never persisted or cached beyond the calling session. The property set is exactly the set `IdentityDocument` masks plus the two bank fields — the two are one contract, so an identifier the 360 masks always has an audited door to reach it through.\n",
        "properties": {
          "pan": {
            "type": [
              "string",
              "null"
            ]
          },
          "aadhaar_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "uan": {
            "type": [
              "string",
              "null"
            ]
          },
          "pf_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "esic_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "iqama_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "national_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "gosi_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "border_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "bank_account_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "iban": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "EmployeeProfile360": {
        "type": "object",
        "description": "Aggregate HR read model — every People facet for one employee, masked PII (fsd 02 PPL-S14).",
        "properties": {
          "employee": {
            "$ref": "#/components/schemas/Employee"
          },
          "profile": {
            "description": "The self-maintained facet, projected EXACTLY as `people.employee_profile.get_me` projects it: the photo is a presigned `FileDownload` (XC-F07) and `photo_storage_key` is never returned — an HR reader and the subject get the same answer about what a photo is. `null` when the employee has no profile row.\n",
            "anyOf": [
              {
                "$ref": "#/components/schemas/EmployeeProfile"
              },
              {
                "type": "null"
              }
            ]
          },
          "personal_info": {
            "$ref": "#/components/schemas/PersonalInfo"
          },
          "identity_document": {
            "$ref": "#/components/schemas/IdentityDocument"
          },
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contact"
            }
          },
          "dependents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Dependent"
            }
          },
          "bank_accounts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BankAccount"
            }
          },
          "virtual_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/VirtualId"
              },
              {
                "type": "null"
              }
            ]
          },
          "app_settings": {
            "$ref": "#/components/schemas/AppSettings"
          },
          "ess_account": {
            "description": "The ESS sign-in facet (issue #1411, ADR 0068 D8) — the SAME `EssAccountFacet` projection `people.employee.get` returns, from the same server helper, because `PPL-S14`'s \"ESS sign-in\" section is rendered off THIS aggregate and two reads of one employee must not disagree about whether that person can sign in. `null` when no principal has ever been provisioned. Additive and under the existing token: whether a principal exists, has been claimed, or has a claim link in flight are facts about the employee this operation already grants. (Absent from the response, and so from the section, until issue #1520.)\n",
            "anyOf": [
              {
                "$ref": "#/components/schemas/EssAccountFacet"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "OnboardingFlowSummary": {
        "type": "object",
        "description": "db 03 `people.onboarding_flows` (migration `0064`) — the **list projection** of a guided hire. Deliberately payload-free: the five stage payloads hold the hire's personal, statutory and salary data while the flow is live, and a list is read on a shared workspace screen, so the grid must not be a bulk PII read. Use `people.onboarding.get` (audited) for one flow's payloads.\n",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "stage",
              "full_name",
              "legal_entity_id"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "stage": {
                "$ref": "#/components/schemas/OnboardingStage"
              },
              "full_name": {
                "type": "string",
                "description": "Captured when the flow is opened so the in-progress list is readable before any stage has been completed."
              },
              "work_email": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Corporate email, if known this early; tenant-unique on the employee row it eventually becomes."
              },
              "legal_entity_id": {
                "$ref": "#/components/schemas/Uuid",
                "description": "ref→org.legal_entities — binds the market/compliance pack, currency and calendar for every later stage."
              },
              "candidate_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "ref→recruit.candidates — set only when the flow was opened from a candidate. No recruit table is read today; the column is what lets that feed attach later without a migration."
              },
              "employee_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "fk→people.employees — written ONLY at completion. Null on every live flow, which is why a live flow consumes no plan seat."
              },
              "offer_render_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "ref→org.template_renders — the offer letter render, owned by `org` (ADR 0023). Null when the offer was skipped or not yet answered."
              },
              "completed_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Timestamp"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "cancelled_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Timestamp"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "OnboardingFlowDetail": {
        "type": "object",
        "description": "The full guided-hire case — the summary projection plus every stage payload captured so far, the offer letter reference triple, and the cancellation reason. **Privileged**: while the flow is live the payloads are a draft of another table's row and carry statutory identifiers and salary. Once the flow is terminal each payload holds only `{ \"captured\": [ …field names… ] }` — a manifest with none of the values, enforced by the `onboarding_flows_terminal_payloads_redacted` CHECK.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/OnboardingFlowSummary"
          },
          {
            "type": "object",
            "properties": {
              "offer_payload": {
                "type": "object",
                "description": "Draft of the OFFER stage — a template/locale/variable-values triple, or a `skipped_reason`. Terminal flows hold only the `captured` manifest."
              },
              "personal_payload": {
                "type": "object",
                "description": "Draft of `people.personal_info` + `people.contacts`. Holds the statutory identifiers while live; the `captured` manifest once terminal."
              },
              "compensation_payload": {
                "type": "object",
                "description": "Draft of `pay.employee_compensation`. Money is a decimal string throughout."
              },
              "placement_payload": {
                "type": "object",
                "description": "Draft of the org-placement columns of `people.employees`."
              },
              "access_payload": {
                "type": "object",
                "description": "Draft of the `EMPLOYEE`-subject `admin.member_grants` row (ADR 0024), or the recorded `no_role_reason`."
              },
              "offer_template_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "ref→org.templates — the LETTER template the offer was rendered from."
              },
              "offer_template_version_no": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 1,
                "description": "The template version the render was stamped at. Template + version + render is a triple; any one alone is a dangling reference nothing can reproduce, so a DB CHECK requires all three or none."
              },
              "offer_skipped_reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Why there is deliberately no letter (founder, TUPE/acquisition intake, no published LETTER template yet). Past the OFFER stage exactly one of `offer_render_id` / `offer_skipped_reason` is set — never both, never neither."
              },
              "cancel_reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Set only on a CANCELLED flow."
              },
              "offer_document": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/OnboardingOfferDocument"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Read-only projection of the offer letter's `org.template_renders` row. **The download URL is NOT minted here** — the client polls `org.template.render_status` for a presigned link, so the artifact stays owned by `org` (ADR 0023) and this side never becomes a second, unaudited door to a rendered document. Null when the offer was skipped or has not been answered yet.\n"
              }
            }
          }
        ]
      },
      "OnboardingOfferDocument": {
        "type": "object",
        "description": "The offer letter's render state, projected from `org.template_renders` (db 02, ADR 0023) by the owning module's in-process service seam — not a cross-schema join (db-docs/00 §12).\n",
        "required": [
          "render_id",
          "status"
        ],
        "additionalProperties": false,
        "properties": {
          "render_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "status": {
            "$ref": "#/components/schemas/OnboardingRenderStatus"
          },
          "template_version_no": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          },
          "completed_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Machine-prefixed failure reason on a `FAILED` render (`UNSUPPORTED_LOCALE`, `TIMEOUT`, `OUTPUT_TOO_LARGE`, `ENGINE_FAILURE`, `INVALID_INPUT`). A failed render never reports success with nothing attached.\n"
          }
        }
      },
      "OnboardingFlowCreateInput": {
        "type": "object",
        "description": "Opens a flow at stage `OFFER` with five empty payloads. `stage` is not accepted — a new flow always starts at `OFFER`; `employee_id` is not accepted either, because it is written only by `people.onboarding.complete`. `tenant_id` is never on the wire (00 §5).\n",
        "required": [
          "full_name",
          "legal_entity_id"
        ],
        "additionalProperties": false,
        "properties": {
          "full_name": {
            "type": "string",
            "minLength": 1,
            "description": "The hire's display name — the one fact the in-progress list needs before any stage has been completed."
          },
          "legal_entity_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              }
            ],
            "description": "`ref→org.legal_entities`. Binds the market/compliance pack that decides which statutory identifiers the `PERSONAL_DATA` stage asks for and which currency the compensation is denominated in. Cannot be deferred: neither the offer letter nor the eventual employee row can be written without it.\n"
          },
          "work_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Optional this early; must be tenant-unique by the time the employee row is minted."
          },
          "candidate_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "`ref→recruit.candidates`. The Recruit-owned Provision seam supplies this id without People reading a Recruit table. A partial-unique index over live rows enforces one open flow per candidate.\n"
          }
        }
      },
      "OnboardingAdvanceInput": {
        "type": "object",
        "description": "Completes the named stage and moves the flow forward. `stage` must equal the flow's **current** stage — a mismatch is `409 STATE_TRANSITION_INVALID`, which is what stops a stale UI advancing the wrong step.\n",
        "required": [
          "stage",
          "payload"
        ],
        "additionalProperties": false,
        "properties": {
          "stage": {
            "$ref": "#/components/schemas/OnboardingAdvanceStage"
          },
          "payload": {
            "description": "The stage's captured data. **`stage` is the discriminator** — the server selects the variant from it and validates the body against that variant only, so an OFFER body sent with `stage: COMPENSATION` is a `422`, not a silently-accepted union match. The five variants are listed below in stage order.\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/OnboardingOfferPayload"
              },
              {
                "$ref": "#/components/schemas/OnboardingPersonalPayload"
              },
              {
                "$ref": "#/components/schemas/OnboardingCompensationPayload"
              },
              {
                "$ref": "#/components/schemas/OnboardingPlacementPayload"
              },
              {
                "$ref": "#/components/schemas/OnboardingAccessPayload"
              }
            ]
          }
        }
      },
      "OnboardingOfferPayload": {
        "type": "object",
        "description": "`stage: OFFER`. **Exactly one of the two shapes**: name a `template_id` to have the letter rendered, or record a `skipped_reason`. \"We sent a letter and also skipped it\" is not a state, and neither is \"we answered nothing\" once the stage is past — a DB CHECK (`onboarding_flows_offer_settled`) enforces the exclusive-or on the stored row.\n\nAdvancing with a template triggers **`org.template.render` in the same transaction** and stamps `offer_template_id` / `offer_template_version_no` / `offer_render_id` onto the flow. The caller must therefore **also hold the `org.template.render` token**, or the advance is `403 TOKEN_DENIED`: this stage must not become a side door to a privileged render. The render itself is asynchronous (ADR 0023) — the flow advances immediately and `offer_document.status` walks `QUEUED → RUNNING → COMPLETED | FAILED`; the download link is minted only by `org.template.render_status`.\n",
        "additionalProperties": false,
        "properties": {
          "template_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "ref→org.templates — a PUBLISHED LETTER template. Mutually exclusive with `skipped_reason`."
          },
          "locale": {
            "type": "string",
            "enum": [
              "en",
              "ar"
            ],
            "default": "en",
            "description": "Locale body to render. Restricted to the renderer's supported set (`en` today — the in-product renderer draws with the PDF standard-14 fonts, which cannot represent Arabic, so an `ar` render is refused rather than typeset incorrectly; ADR 0023 §Consequences).\n"
          },
          "variable_values": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Values merged into the template's declared `{{variables}}`. These are the contents of a letter — a name, a designation, a salary — so they are persisted only on the FORCE-RLS'd `org.template_renders` row, never echoed in a response, never carried in the emitted event, and cleared by the render worker when the render completes (migration `0063`).\n"
          },
          "skipped_reason": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "description": "Why there is deliberately no offer letter — a founder, a TUPE/acquisition intake, or a tenant that has not authored a LETTER template yet. Recording the reason keeps that an explicit, auditable choice instead of a silently empty column, and it is what stops \"no published template\" from being a hard dead end for every hire. Mutually exclusive with `template_id`.\n"
          }
        }
      },
      "OnboardingPersonalPayload": {
        "type": "object",
        "description": "`stage: PERSONAL_DATA`. The draft that becomes `people.personal_info` (1:1) plus one `people.contacts` emergency row at completion. Every top-level field is optional — a real capture is filled in over several sittings — **except** within `emergency_contact`, which is all-or-nothing because a contact without a name and a phone number is not a contact.\n\n**Market note.** Which identifiers are asked for is driven by the flow's legal entity and its compliance pack (ADR 0005), **never hardcoded in the client**: India collects `pan` / `aadhaar_ref` / `uan` (with `pf_no` / `esic_no` where already allotted), KSA collects `iqama_no` / `national_id` / `gosi_no` / `border_no`. The wire shape is one superset for both markets so a tenant operating in both needs no second contract; the pack decides which subset a given hire's form renders and validates.\n",
        "additionalProperties": false,
        "properties": {
          "legal_first_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "legal_last_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "date_of_birth": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ]
          },
          "gender": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Gender"
              },
              {
                "type": "null"
              }
            ]
          },
          "marital_status": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/MaritalStatus"
              },
              {
                "type": "null"
              }
            ]
          },
          "nationality": {
            "type": [
              "string",
              "null"
            ]
          },
          "personal_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "personal_phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Personal contact phone. HR updates use E.164 and provision phone-only ESS when no work email/identity exists; a phone-login change revokes sessions and queues a Keycloak update. Self-service contact edits never change the login identifier.\n"
          },
          "current_address": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Address"
              },
              {
                "type": "null"
              }
            ]
          },
          "permanent_address": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Address"
              },
              {
                "type": "null"
              }
            ]
          },
          "identity": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/OnboardingIdentityCapture"
              },
              {
                "type": "null"
              }
            ]
          },
          "emergency_contact": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/OnboardingEmergencyContact"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "OnboardingIdentityCapture": {
        "type": "object",
        "description": "Statutory identity captured during onboarding — the same nine identifiers `people.personal_info` stores and `IdentityDocument` display-masks, plus the two Iqama dates. **Pack-driven, not client-driven** (see `OnboardingPersonalPayload`): the India set is `pan`/`aadhaar_ref`/`uan`, the KSA set is `iqama_no`/`national_id`/`gosi_no`/`border_no`. Values are write-only through this path; reading them back after completion goes through the audited `people.employee.reveal_pii` step-up.\n",
        "additionalProperties": false,
        "properties": {
          "pan": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — Permanent Account Number."
          },
          "aadhaar_ref": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — Aadhaar reference/VID, never the raw number where a reference is available."
          },
          "uan": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — EPFO Universal Account Number."
          },
          "pf_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — EPF member id, where already allotted."
          },
          "esic_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — ESIC insurance number, where already allotted."
          },
          "iqama_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "KSA — Iqama (residency permit) number."
          },
          "national_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "KSA — national id for Saudi nationals."
          },
          "gosi_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "KSA — GOSI registration number."
          },
          "border_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "KSA — border/entry number."
          },
          "iqama_expiry": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ],
            "description": "Iqama expiry. Deliberately **not** treated as an identifier: it is a deadline HR must act on, and it is not masked on read for the same reason.\n"
          }
        }
      },
      "OnboardingEmergencyContact": {
        "type": "object",
        "description": "The one emergency contact captured during onboarding; becomes a `people.contacts` row (primary) at completion. `name` and `phone` are required — a contact you cannot name or call is not a contact — which is the only required-field island inside an otherwise all-optional personal payload.\n",
        "required": [
          "name",
          "phone"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "relationship": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ContactRelationship"
              },
              {
                "type": "null"
              }
            ]
          },
          "phone": {
            "type": "string",
            "minLength": 1
          },
          "alternate_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          }
        }
      },
      "OnboardingCompensationPayload": {
        "type": "object",
        "description": "`stage: COMPENSATION`. The draft that becomes the hire's opening `pay.employee_compensation` row at completion. **Money is a decimal STRING, never a number** — a JSON float cannot represent a rupee or a halala exactly, and a payroll figure that has been through a binary double is not the figure anyone agreed to (db-docs/00 §6). `currency_code` is **not accepted**: it is derived from the flow's legal entity, so an INR/SAR mix-up is impossible by construction.\n\nThe caller must **also hold `pay.employee_compensation.create`**, the token that owns this write in the `pay` module — an onboarding stage does not grant HR the ability to set salaries they could not set directly.\n",
        "required": [
          "ctc_amount",
          "basic_amount",
          "effective_from"
        ],
        "additionalProperties": false,
        "properties": {
          "ctc_amount": {
            "type": "string",
            "pattern": "^\\d+(\\.\\d{1,2})?$",
            "description": "Annual cost-to-company as a decimal string, e.g. \"1800000.00\". Must be >= 0."
          },
          "basic_amount": {
            "type": "string",
            "pattern": "^\\d+(\\.\\d{1,2})?$",
            "description": "Annual basic component as a decimal string — the statutory wage anchor the pack-driven math builds on (PF wage in India, GOSI wage in KSA). Must be >= 0 and <= `ctc_amount`; a violation is a `422` with rule `cross-field`.\n"
          },
          "effective_from": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              }
            ],
            "description": "First day this compensation applies — normally the hire's `date_of_joining`."
          },
          "revision_reason": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OnboardingCompensationRevisionReason"
              }
            ],
            "default": "HIRE",
            "description": "Always `HIRE` on a guided onboarding; the other values arrive from `engage` salary revisions."
          },
          "pay_structure_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "`ref→org.pay_structures`. Optional. When supplied it must name a `PUBLISHED` structure belonging to the same legal entity as the hire, and its `version_no` is stamped onto the compensation row so a later structure version cannot silently re-interpret the assignment.\n"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          }
        }
      },
      "OnboardingPlacementPayload": {
        "type": "object",
        "description": "`stage: PLACEMENT`. The draft that becomes the org-placement columns of `people.employees` at completion. `date_of_confirmation`, when present, must be on or after `date_of_joining` (db 03 §1 check constraint); every `*_id` must resolve inside the same tenant.\n\n**Shift and leave-policy assignment are NOT part of this stage.** `people.employees` has no column for either, and no per-employee write path exists for them on this route yet — so rather than accept fields that would be silently dropped, the stage omits them and the residue is recorded as an as-built note in the FSD. Faking the capture would be worse than the gap: an HR admin would believe a roster and a leave policy had been assigned when nothing had been written.\n",
        "required": [
          "employment_type",
          "date_of_joining"
        ],
        "additionalProperties": false,
        "properties": {
          "employment_type": {
            "$ref": "#/components/schemas/EmploymentType"
          },
          "work_mode": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WorkMode"
              }
            ],
            "description": "Optional (#1316). Prefilled from the opening's `recruit.work_mode` when the flow was born of a recruit hand-off (`ONSITE→DESK`, `HYBRID→HYBRID`, `REMOTE→REMOTE`); absent for a talent-pool card, which has no opening, and then the employee column's `DESK` default stands at completion.\n"
          },
          "date_of_joining": {
            "$ref": "#/components/schemas/DateOnly"
          },
          "date_of_confirmation": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ]
          },
          "department_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "designation_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "grade_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "org_unit_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "work_location_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "manager_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "fk→people.employees — must resolve to a live employee in the same tenant."
          }
        }
      },
      "OnboardingAccessPayload": {
        "type": "object",
        "description": "`stage: ACCESS`. **Exactly one of the two shapes**: name a `role_id` to have an `EMPLOYEE`-subject `admin.member_grants` row minted at completion, or record `no_role_reason`.\n\nChoosing a role requires the caller to **also hold `admin.member_grant.create`**, which is a `tenant_admin`-only token per ADR 0024. The common HR path therefore records a `no_role_reason` and leaves **portal-issued** access to Sysmedac One, which remains the system of record for workspace members and role-grants (ADR 0009) — GroundIT grants only on the `EMPLOYEE` partition it owns.\n",
        "additionalProperties": false,
        "properties": {
          "role_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "ref→admin.roles — the in-product role to grant on the EMPLOYEE subject partition. Mutually exclusive with `no_role_reason`."
          },
          "no_role_reason": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "description": "Why no in-product role is being granted (mobile self-service only; access to be issued from the One portal; not yet decided). Mutually exclusive with `role_id` — an unanswered access question is not a completed stage.\n"
          }
        }
      },
      "OnboardingCancelInput": {
        "type": "object",
        "description": "Terminates the flow. `reason` is **required**, not optional: an abandoned hire whose payloads are about to be redacted leaves the reason as the only remaining explanation of what happened, so it is the one thing the operation insists on.\n",
        "required": [
          "reason"
        ],
        "additionalProperties": false,
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          }
        }
      },
      "OnboardingCompleteInput": {
        "type": "object",
        "description": "Optional body for the completion transaction. Only the business code is negotiable here — every other value the employee is minted from was captured by the five stages.\n",
        "additionalProperties": false,
        "properties": {
          "employee_no": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BusinessNo"
              }
            ],
            "description": "Optional. Omit to have the tenant sequence generate it (`<prefix><padded seq>`, configured by the `people.employee_no` key of `admin.tenant_config`, default `EMP-` / width 4). Must be unique per tenant and is never reused (db 00 §3).\n"
          }
        }
      },
      "OnboardingCompletion": {
        "type": "object",
        "description": "The result of the one all-or-nothing completion transaction: the now-`COMPLETED` flow (payloads reduced to a field-name manifest) **plus** the employee it minted, so the caller can navigate straight to the new record without a second round trip.\n**Known under-declaration, recorded rather than guessed at (#800):** the running handler also returns `compensation` (the assigned `pay.employee_compensation` record) and `role_grant` (the `admin.member_grants` row, or `null`) at this level, and neither has ever been declared here. #800 declares the third such object, `ess_account`, because it added it; it does not invent schemas for the two older ones, whose shapes belong to `pay` and `admin` and would be a guess from this file. Tracked as `05` `GAP-53`.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/OnboardingFlowDetail"
          },
          {
            "type": "object",
            "required": [
              "employee",
              "ess_account",
              "ess_access"
            ],
            "properties": {
              "employee": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Employee"
                  }
                ],
                "description": "The employee minted by this call, exactly as `people.employee.create` and `people.pending_hire.complete` return it — one birth path, one read model, one `people.employee.activated` event.\n"
              },
              "ess_account": {
                "$ref": "#/components/schemas/EssAccountOutcome"
              },
              "ess_access": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/EssAccessOutcome"
                  }
                ],
                "description": "The same three-state verdict `people.employee.create` reports (#1653), so the guided door and the quick door are legible to one reader. Both now also issue the tenant's baseline `EMPLOYEE` role in the hire transaction, which is what `BLOCKED(NO_BASELINE_ROLE)` reports the absence of.\n"
              }
            }
          }
        ]
      },
      "EssAccountOutcome": {
        "type": "object",
        "description": "What the completion did about the hire's ESS (employee self-service) account (issue #800). Additive, so no existing consumer breaks. Three facts, always present, because a caller that only ever heard about the successes could not tell \"not provisioned\" from \"deliberately not provisioned\". The account is the `xc.identities` `EMPLOYEE` principal (db 14-xc §1); the `people.employee_subjects` link that completes it is written at the employee's first sign-in and is not reported here because it does not exist yet.\n\n**The four `invitation_*` members were removed in #1638** with the invitation limb itself. They described an ADR 0061 claim link, its seven-day expiry, its deferral to `joining_date - 3 days` and the reasons none was issued — a credential the sign-in rail has not needed since ADR 0068, because `AuthWriter.bindIdentity` adopts an `INVITED` principal on a PROVEN Keycloak subject with no invitation claimed at any point. The completion's ESS verdict now rides beside this object as `ess_access`.\n",
        "required": [
          "identity_id",
          "created",
          "skipped_reason"
        ],
        "properties": {
          "identity_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The ESS principal for this employee, or `null` when none was minted — in which case `skipped_reason` says why.\n"
          },
          "created": {
            "type": "boolean",
            "description": "`true` only when THIS call inserted the principal. A replayed or resumed hire that found one already minted reports `false` with a non-null `identity_id`; provisioning is idempotent by construction, because a hire is replayable.\n"
          },
          "skipped_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "NO_WORK_EMAIL",
              null
            ],
            "description": "Non-null exactly when `identity_id` is null. `NO_WORK_EMAIL` — the hire carries no corporate address, which is the only key the first-sign-in adoption path can resolve an unclaimed principal by; an identity minted without one would be a row nobody can ever claim. The account is not lost: assigning the address later through `people.employee.update` provisions it (#800), and re-running the hire once the address is present does too — provisioning is idempotent either way.\n"
          }
        }
      },
      "EmployeeProfile": {
        "type": "object",
        "description": "db 03 §1 `people.employee_profiles` (1:1 with `employees`).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "employee_id"
            ],
            "properties": {
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "preferred_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "about": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "skills": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "level": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              },
              "photo": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/FileDownload"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Presigned photo download (XC-F07); never a raw storage key."
              },
              "work_anniversary": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Derived from date_of_joining."
              },
              "birthday": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "tenure_months": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "EmployeeProfileUpdateInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "preferred_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "about": {
            "type": [
              "string",
              "null"
            ]
          },
          "skills": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "level": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "photo_storage_key": {
            "type": [
              "string",
              "null"
            ],
            "description": "Object-storage key returned by a prior presigned-upload flow (XC-F07); the API resolves this to a `FileDownload` on read."
          }
        }
      },
      "PersonalInfo": {
        "type": "object",
        "description": "db 03 §2 `people.personal_info` (1:1, PII, consent-gated, never logged). Statutory identity columns are exposed via `people.identity_document.*`, not here.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "employee_id"
            ],
            "properties": {
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "legal_first_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "legal_last_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "date_of_birth": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "gender": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Gender"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "marital_status": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/MaritalStatus"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "nationality": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "blood_group": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "personal_email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "personal_phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "current_address": {
                "$ref": "#/components/schemas/Address"
              },
              "permanent_address": {
                "$ref": "#/components/schemas/Address"
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "PersonalInfoUpdateInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "legal_first_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "legal_last_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "personal_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "personal_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "date_of_birth": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ]
          },
          "gender": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Gender"
              },
              {
                "type": "null"
              }
            ]
          },
          "marital_status": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/MaritalStatus"
              },
              {
                "type": "null"
              }
            ]
          },
          "current_address": {
            "$ref": "#/components/schemas/Address"
          },
          "permanent_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "Address": {
        "type": "object",
        "description": "Sparse JSONB address skeleton (db-docs/00 §9); not queried/joined.",
        "properties": {
          "line1": {
            "type": [
              "string",
              "null"
            ]
          },
          "line2": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "type": [
              "string",
              "null"
            ]
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "postal_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "geo": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              }
            }
          }
        }
      },
      "IdentityDocument": {
        "type": "object",
        "description": "Market-gated statutory identity block from `people.personal_info` (db 03 §2). **All nine statutory identifiers are display-masked** (last 4 characters) — `pan`, `aadhaar_ref`, `uan`, `pf_no`, `esic_no`, `iqama_no`, `national_id`, `gosi_no`, `border_no`. Until 2026-07-29 the last five were served RAW here while this schema described the block as masked; that is fixed, and each is now reachable through the audited `people.employee.reveal_pii` step-up like the other four. The two Iqama dates are deliberately NOT masked: `iqama_expiry` is a deadline HR must act on rather than an identifier, and `iqama_expiry_hijri` is a read-only Umm al-Qura display string, never an operand (db-docs/00 §6).\n",
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "pan": {
            "type": [
              "string",
              "null"
            ],
            "description": "India",
            "masked.": null
          },
          "aadhaar_ref": {
            "type": [
              "string",
              "null"
            ],
            "description": "India",
            "tokenized reference": null,
            "masked.": null
          },
          "uan": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — EPFO Universal Account Number",
            "masked.": null
          },
          "pf_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — Provident Fund account number",
            "masked.": null
          },
          "esic_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — ESIC insurance number",
            "masked.": null
          },
          "iqama_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "KSA",
            "masked.": null
          },
          "national_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "KSA",
            "masked; mutually exclusive with iqama_no.": null
          },
          "gosi_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "KSA — GOSI registration number",
            "masked.": null
          },
          "border_no": {
            "type": [
              "string",
              "null"
            ],
            "description": "KSA — border/entry number",
            "masked.": null
          },
          "iqama_expiry": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ],
            "description": "KSA."
          },
          "iqama_expiry_hijri": {
            "$ref": "#/components/schemas/HijriDisplay"
          },
          "identity_verification_status": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/BankVerificationStatus"
              },
              {
                "type": "null"
              }
            ],
            "description": "The statutory-identity verification state (#1643, migration `0232`). `null` means nothing has ever been submitted for verification, which is a DIFFERENT fact from `PENDING` and is the state of every row not touched since it was captured. Set to `PENDING` by any write that changes a statutory identifier (or `legal_first_name`, `legal_last_name`, `date_of_birth`), which also raises an `IDENTITY_CHANGE` approval envelope; moved to `VERIFIED`/`FAILED` by `people.identity_document.verify`. The enum is `people.bank_verification_status`, reused rather than cloned — the tri-state is identical.\n"
          },
          "identity_verified_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "identity_change_summary": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/IdentityChangeSummary"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "IdentityChangeSummary": {
        "type": "object",
        "description": "The MASKED old→new diff the producer snapshotted at the moment of a statutory-identity change (#1643). It exists because `xc.approval_inbox` carries no payload column and the `people.personal_info` row holds only the NEW value — so without it an approver would be shown a restatement of the record rather than the change they are being asked to accept. Statutory identifiers are masked here for the reason the audit log masks them: a durable snapshot in the clear would outlive the record it protects. `legal_first_name`, `legal_last_name` and `date_of_birth` are rendered plainly — the approver can already read them through `people.personal_info.get_admin`, and a masked date of birth is evidence of nothing.\n",
        "properties": {
          "fields": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field"
              ],
              "properties": {
                "field": {
                  "type": "string",
                  "description": "The `people.personal_info` column that changed."
                },
                "before": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Masked for a statutory identifier; `null` when the field was empty."
                },
                "after": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "IdentityChangeForApproval": {
        "description": "One row of `people.personal_info.list_for_approval` (#1643) — the masked identity block, plus the row id the approvals envelope's `source_id` points at and the subject's display name.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/IdentityDocument"
          },
          {
            "type": "object",
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employee_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "LEFT JOINed, so a soft-deleted employee still yields the envelope's evidence with a `null` name rather than dropping the row. The client falls back to the org directory and then to a stated absence — never to the uuid.\n"
              },
              "updated_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Timestamp"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        ]
      },
      "BankAccountForApproval": {
        "description": "One row of `people.bank_account.list_for_approval` (#1643) — the masked account, plus the subject's display name. `id` is what the approvals envelope's `source_id` points at.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/BankAccount"
          },
          {
            "type": "object",
            "properties": {
              "employee_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "LEFT JOINed; see `IdentityChangeForApproval.employee_name`."
              }
            }
          }
        ]
      },
      "IdentityDocumentUpdateInput": {
        "type": "object",
        "description": "Only the active market's fields apply, per the legal entity's pack (XC-F01); unmasked values accepted on write.",
        "additionalProperties": false,
        "properties": {
          "pan": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — pattern [A-Z]{5}[0-9]{4}[A-Z]."
          },
          "aadhaar_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "uan": {
            "type": [
              "string",
              "null"
            ]
          },
          "pf_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "esic_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "iqama_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "national_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "gosi_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "border_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "iqama_expiry": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "VerifyDecisionInput": {
        "type": "object",
        "required": [
          "decision"
        ],
        "additionalProperties": false,
        "description": "**`note`, not `reason` (#1643).** This schema published `reason` from the day it was written and the handlers never accepted it: both deciders run `assertKnownFields(body, ['decision'])`, so a client sending the documented field got a `422` naming it as unknown. The field is renamed to what the approvals rail calls it everywhere else, and the handlers now accept it — which is what lets a REJECTION reach the employee as a reason (\"we could not match this IFSC\") on their own bank card, instead of living in an approver's head. The inbox drawer's generic `note` is mapped onto this field by `ApprovalSourceActionRouter`, so both entrances to the decision carry the same sentence.\n",
        "properties": {
          "decision": {
            "$ref": "#/components/schemas/VerifyDecision"
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "The decision note",
            "shown to the employee.": null
          }
        }
      },
      "Contact": {
        "type": "object",
        "description": "db 03 §2 `people.contacts`.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "name",
              "phone",
              "is_primary",
              "priority"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "name": {
                "type": "string"
              },
              "relationship": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/ContactRelationship"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "phone": {
                "type": "string"
              },
              "alternate_phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "address": {
                "$ref": "#/components/schemas/Address"
              },
              "is_primary": {
                "type": "boolean"
              },
              "priority": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "ContactCreateInput": {
        "type": "object",
        "required": [
          "name",
          "phone"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "relationship": {
            "$ref": "#/components/schemas/ContactRelationship"
          },
          "phone": {
            "type": "string"
          },
          "alternate_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          },
          "is_primary": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "ContactUpdateInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "relationship": {
            "$ref": "#/components/schemas/ContactRelationship"
          },
          "phone": {
            "type": "string"
          },
          "alternate_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "Dependent": {
        "type": "object",
        "description": "db 03 §2 `people.dependents`.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "name",
              "relationship"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "name": {
                "type": "string"
              },
              "relationship": {
                "$ref": "#/components/schemas/DependentRelationship"
              },
              "date_of_birth": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "gender": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Gender"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "is_beneficiary": {
                "type": "boolean"
              },
              "is_dependent_for_insurance": {
                "type": "boolean"
              },
              "national_id_ref": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Tokenized reference",
                "pack-gated.": null
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "DependentCreateInput": {
        "type": "object",
        "required": [
          "name",
          "relationship"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "relationship": {
            "$ref": "#/components/schemas/DependentRelationship"
          },
          "date_of_birth": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ]
          },
          "gender": {
            "$ref": "#/components/schemas/Gender"
          },
          "is_beneficiary": {
            "type": "boolean",
            "default": false
          },
          "is_dependent_for_insurance": {
            "type": "boolean",
            "default": false
          },
          "national_id_ref": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "DependentUpdateInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "relationship": {
            "$ref": "#/components/schemas/DependentRelationship"
          },
          "date_of_birth": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ]
          },
          "gender": {
            "$ref": "#/components/schemas/Gender"
          },
          "is_beneficiary": {
            "type": "boolean"
          },
          "is_dependent_for_insurance": {
            "type": "boolean"
          },
          "national_id_ref": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "BankAccount": {
        "type": "object",
        "description": "db 03 §2 `people.bank_accounts`. `account_number`/`iban` are display-masked.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "account_holder_name",
              "currency_code",
              "is_primary",
              "verification_status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "account_holder_name": {
                "type": "string"
              },
              "account_number_masked": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Last 4 digits only."
              },
              "bank_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ifsc_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "India."
              },
              "iban_masked": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "KSA",
                "WPS-mandatory; masked.": null
              },
              "swift_code": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "branch_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "currency_code": {
                "$ref": "#/components/schemas/CurrencyCode"
              },
              "is_primary": {
                "type": "boolean"
              },
              "verification_status": {
                "$ref": "#/components/schemas/BankVerificationStatus"
              },
              "verified_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Timestamp"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "BankAccountCreateInput": {
        "type": "object",
        "required": [
          "account_holder_name",
          "account_number",
          "currency_code"
        ],
        "additionalProperties": false,
        "properties": {
          "account_holder_name": {
            "type": "string"
          },
          "account_number": {
            "type": "string",
            "description": "Unmasked on write only."
          },
          "bank_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "ifsc_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "India — required for India salary credit."
          },
          "iban": {
            "type": [
              "string",
              "null"
            ],
            "description": "KSA — required for WPS."
          },
          "swift_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "branch_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "currency_code": {
            "$ref": "#/components/schemas/CurrencyCode"
          }
        }
      },
      "BankAccountUpdateInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "account_holder_name": {
            "type": "string"
          },
          "account_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "bank_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "ifsc_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "iban": {
            "type": [
              "string",
              "null"
            ]
          },
          "swift_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "branch_name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "OrgDirectoryEntry": {
        "type": "object",
        "description": "db 03 §6 `people.org_directory` — read-model projection, eventually consistent, may lag the source. This is the ONE tenant-shared read in the product, so it publishes no field the whole tenant should not see: `visibility` itself is deliberately absent (a colleague learns WHETHER contact details are shown from `work_email`/`phone` being `null`, not WHO chose to restrict them). HR's `people.org_directory.list_admin` answers with `AdminOrgDirectoryEntry`, which carries it.\n",
        "required": [
          "id",
          "employee_id",
          "employee_no",
          "display_name",
          "status",
          "projected_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "employee_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "employee_no": {
            "$ref": "#/components/schemas/BusinessNo"
          },
          "display_name": {
            "type": "string"
          },
          "designation_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "department_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "location_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_location_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "The `org.work_locations` key behind `location_name` (migration 0197, #1318). Carried so a consumer can JOIN on the site instead of matching its display label, which breaks on a rename and on two sites sharing a name. Not visibility-gated — a colleague's posting is already published as `location_name`.\n"
          },
          "work_mode": {
            "type": [
              "string",
              "null"
            ],
            "description": "Mirrors `people.employees.work_mode` (`WorkMode`, migration 0222) so the ESS muster roster can narrow a site to its muster workforce; `null` when the row predates the column or has not been reprojected — unknown, never `DESK`.\n"
          },
          "manager_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "work_email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Visibility-gated."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Visibility-gated."
          },
          "status": {
            "$ref": "#/components/schemas/EmployeeStatus"
          },
          "projected_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "AdminOrgDirectoryEntry": {
        "description": "The HR (`people.org_directory.list_admin`) row — the tenant-shared entry plus the subject's own `contact_visibility` choice, which that console renders as a column and the visibility-scoped read withholds.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/OrgDirectoryEntry"
          },
          {
            "type": "object",
            "required": [
              "visibility"
            ],
            "properties": {
              "visibility": {
                "$ref": "#/components/schemas/ContactVisibility"
              }
            }
          }
        ]
      },
      "ColleagueProfile": {
        "type": "object",
        "description": "Profile 360 detail — `org_directory` + `employee_profiles.skills` + the manager chain, composed same-schema (db-docs/00 §12).",
        "required": [
          "id",
          "employee_id",
          "display_name",
          "status"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "employee_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "display_name": {
            "type": "string"
          },
          "designation_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "department_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "location_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "manager_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "manager_display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "tenure_months": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/EmployeeStatus"
          },
          "work_email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present only if contact_visibility permits."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present only if contact_visibility permits."
          },
          "skills": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "level": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "OrgChartNode": {
        "type": "object",
        "description": "One node of the reporting-line tree (fsd 02 PPL-S16).",
        "required": [
          "employee_id",
          "display_name"
        ],
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "display_name": {
            "type": "string"
          },
          "status": {
            "description": "The employee's employment status. PRESENT ONLY for a principal who also holds `people.org_directory.list_admin` (#1397) — the HR chart. The ESS colleague chart runs on the same operation, and employment state is not disclosed there; absence therefore means \"not disclosed to you\" and must never be read as `ACTIVE`.\n\nThe drawn set is everyone still employed, so a manager who is `ON_LEAVE` or `SUSPENDED` keeps their subtree and is rendered with a status chip rather than being dropped (which used to detach their whole team into unlabelled roots). Terminal statuses (`EXITED`, `ALUMNI`) are never drawn, so they are not in the enum.\n",
            "type": "string",
            "enum": [
              "ACTIVE",
              "ON_LEAVE",
              "SUSPENDED"
            ]
          },
          "designation_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "org_unit_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "legal_entity_id": {
            "description": "The employing entity — how a client labels one cluster apart from another.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "reports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrgChartNode"
            }
          },
          "has_more_reports": {
            "description": "Present and `true` only when this node HAS reports that the `depth` cap kept out of `reports`. Absent means the empty `reports` array is the whole truth.\n",
            "type": "boolean"
          },
          "detached": {
            "description": "Present and `true` only on a CLUSTER ROOT that has a `manager_id` on record whose chain reaches nobody still employed — their reporting line runs off the top of the chart into a leaver or a `manager_id` ring (#1396). A genuine top of house omits the field entirely, so an older client that never reads it is unaffected. Never set below a root, and never set on the single root returned for an explicit `root_employee_id` — a caller who named that subtree scoped it deliberately.\n",
            "type": "boolean"
          }
        }
      },
      "DirectoryExportInput": {
        "type": "object",
        "required": [
          "export_type",
          "format"
        ],
        "additionalProperties": false,
        "properties": {
          "export_type": {
            "type": "string",
            "enum": [
              "DIRECTORY",
              "ORG_CHART"
            ]
          },
          "format": {
            "type": "string",
            "enum": [
              "XLSX",
              "PDF",
              "PNG"
            ],
            "description": "PNG valid only for ORG_CHART."
          },
          "root_employee_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "DirectoryExport": {
        "type": "object",
        "required": [
          "id",
          "export_type",
          "format",
          "requested_by",
          "status",
          "created_at",
          "updated_at",
          "version",
          "file"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "export_type": {
            "type": "string",
            "enum": [
              "DIRECTORY",
              "ORG_CHART"
            ]
          },
          "format": {
            "type": "string",
            "enum": [
              "XLSX",
              "PDF",
              "PNG"
            ]
          },
          "root_employee_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "requested_by": {
            "$ref": "#/components/schemas/Uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "QUEUED",
              "RUNNING",
              "COMPLETED",
              "FAILED"
            ]
          },
          "row_count": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0
              },
              {
                "type": "null"
              }
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "A safe, value-free message present only when `status=FAILED`."
          },
          "started_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "completed_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "file": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FileDownload"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present only when `status=COMPLETED`; presigned and tenant-authorized."
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "updated_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "version": {
            "type": "integer"
          }
        }
      },
      "VirtualId": {
        "type": "object",
        "description": "db 03 §3 `people.virtual_ids` — offline-cache credential. The HMAC secret never appears here; `key_id` only references it (xc/KMS).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "credential_no",
              "payload_version",
              "signed_payload",
              "key_id",
              "status",
              "issued_at"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "credential_no": {
                "$ref": "#/components/schemas/BusinessNo"
              },
              "payload_version": {
                "type": "integer",
                "minimum": 1
              },
              "signed_payload": {
                "type": "string",
                "description": "HMAC-signed token (employee_no + entity + validity + version)."
              },
              "key_id": {
                "type": "string"
              },
              "nfc_tag_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "$ref": "#/components/schemas/VirtualIdStatus"
              },
              "issued_at": {
                "$ref": "#/components/schemas/Timestamp"
              },
              "valid_until": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Timestamp"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "revoked_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Timestamp"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "revoke_reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "full_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.employees, by projection — populated only by `GET /virtual-ids/me` (card display)."
              },
              "employee_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.employees.full_name (employee_id), by join — populated by `GET /virtual-ids` (the badge register), where the row identifies somebody other than the caller. The `/me` and `/verify` card reads carry the same name as `full_name` (issue #1640)."
              },
              "employee_no": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.employees; printed employee ID for card display, and joined onto every `GET /virtual-ids` row (issue #1640)."
              },
              "designation_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.designations, by projection — populated only by `GET /virtual-ids/me` (card display)."
              },
              "department_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.departments, by projection — complete-card display."
              },
              "blood_group": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.personal_info; complete-card display."
              },
              "mobile_number": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.personal_info.personal_phone; complete-card display."
              },
              "email_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "email",
                "description": "ref→people.employees.work_email; complete-card display."
              },
              "employee_since": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "ref→people.employees.date_of_joining; complete-card display."
              },
              "employee_status": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/EmployeeStatus"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Current employment state projected for the card."
              },
              "emergency_details": {
                "anyOf": [
                  {
                    "type": "object",
                    "required": [
                      "name",
                      "mobile_number"
                    ],
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "relationship": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "mobile_number": {
                        "type": "string"
                      },
                      "alternate_mobile_number": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "email_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "email"
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Primary emergency contact only; ref→people.contacts."
              },
              "office_details": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "legal_entity_name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "location_name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "location_type": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "address": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "additionalProperties": true
                      },
                      "timezone": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Employing entity and assigned office projection; ref→org.legal_entities and org.work_locations."
              },
              "photo": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/FileDownload"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "ref→people.employee_profiles (photo_storage_key), by projection — presigned, never a raw storage key; populated only by `GET /virtual-ids/me` (card display)."
              },
              "qr_png_base64": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Server-rendered QR PNG (base64-encoded image bytes, not a data URL) encoding exactly `signed_payload` — populated by `GET /virtual-ids/me` and successful QR verification, so the client never has to embed its own QR renderer."
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "VirtualIdVerifyInput": {
        "type": "object",
        "required": [
          "signed_payload"
        ],
        "additionalProperties": false,
        "properties": {
          "signed_payload": {
            "type": "string",
            "minLength": 1,
            "description": "Exact text decoded from the Virtual ID QR; never substitute an employee ID."
          }
        }
      },
      "VirtualIdRevokeInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "revoke_reason": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AppSettings": {
        "type": "object",
        "description": "db 03 §4 `people.app_settings` (1:1). No secrets — MPIN/biometric credentials live in auth (XC-F03).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "employee_id",
              "locale",
              "contact_visibility"
            ],
            "properties": {
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "locale": {
                "type": "string",
                "enum": [
                  "en",
                  "ar"
                ]
              },
              "timezone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "notification_prefs": {
                "$ref": "#/components/schemas/NotificationPrefs"
              },
              "contact_visibility": {
                "$ref": "#/components/schemas/ContactVisibility"
              },
              "biometric_login_enabled": {
                "type": "boolean"
              },
              "mpin_enabled": {
                "type": "boolean"
              },
              "theme": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "EmployeeAppSettingsAdmin": {
        "type": "object",
        "description": "The ADMIN projection of `people.app_settings` (fsd 02 `PPL-S14` Access region) — deliberately NOT `AppSettings`. It is a strict, permanent subset: the two settings that govern how a person is reached. `notification_prefs`, `theme`, `timezone`, `biometric_login_enabled` and `mpin_enabled` are **excluded on purpose** and must never be added here — they are personal preference data an administrator has no access-related reason to read, and they stay behind the employee's own `people.app_setting.get_me` door. Sharing the `AppSettings` schema would have made adding a field to the SELF read silently publish it to HR, which is why this is a separate schema rather than a `$ref` with fewer required fields.\n",
        "required": [
          "employee_id",
          "is_set",
          "locale",
          "contact_visibility"
        ],
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "is_set": {
            "type": "boolean",
            "description": "`false` ⟺ this employee has no `people.app_settings` row, and the three values below are `null`. The row is created lazily by the employee's own first `people.app_setting.get_me`/`.update_me`; this read never creates one, so \"never set anything\" stays distinguishable from \"chose the defaults\".\n"
          },
          "locale": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "en",
              "ar",
              null
            ],
            "description": "The language the product speaks to this employee. `null` when `is_set` is false.\n"
          },
          "contact_visibility": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ContactVisibility"
              },
              {
                "type": "null"
              }
            ],
            "description": "Who this employee's contact details are visible to in the directory (db 03 §4 enum semantics — `PUBLIC` whole tenant · `COLLEAGUES` same org-unit/department · `MANAGER_HR` reporting line + HR · `PRIVATE` self only). `null` when `is_set` is false; the row default is `COLLEAGUES`.\n"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the employee last changed a setting. `null` when `is_set` is false.\n"
          }
        }
      },
      "AppSettingsUpdateInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "locale": {
            "type": "string",
            "enum": [
              "en",
              "ar"
            ]
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ]
          },
          "notification_prefs": {
            "$ref": "#/components/schemas/NotificationPrefs"
          },
          "contact_visibility": {
            "$ref": "#/components/schemas/ContactVisibility"
          },
          "biometric_login_enabled": {
            "type": "boolean"
          },
          "mpin_enabled": {
            "type": "boolean"
          },
          "theme": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "NotificationPrefs": {
        "type": "object",
        "additionalProperties": false,
        "description": "Per-employee notification preferences (design-ess/03 §5.5). Sparse — an absent key means the default, and `{}` is a valid value meaning \"all defaults\". Validated server-side since #673; a violation is `422 VALIDATION_FAILED` with a JSON Pointer per offending field.\n\n**Supersedes the earlier `{channels, categories, quiet_hours}` sketch documented here**, which no code ever wrote or enforced. The shape below is the one design-ess/03 §5 fixes and the one the `xc.notification_digest` job reads.\n",
        "properties": {
          "digest": {
            "type": "object",
            "additionalProperties": false,
            "description": "The daily roll-up of batched notifications. Defaults to enabled at 09:00.",
            "properties": {
              "enabled": {
                "type": "boolean",
                "default": true
              },
              "hour_local": {
                "type": "integer",
                "minimum": 0,
                "maximum": 23,
                "default": 9,
                "description": "Preferred digest hour. **Currently compared against UTC** — there is no per-employee timezone resolution in the jobs tier yet, and the limitation is stated rather than approximated.\n"
              }
            }
          },
          "overrides": {
            "type": "object",
            "additionalProperties": false,
            "description": "Per-category delivery override. **`WORKFLOW` and `ALERT` may not be set to `OFF`** — those carry money and security events respectively, and a product that lets someone mute their own payslip notification has mis-designed the preference. Attempting it is a `422` with `rule: immutable`.\n",
            "properties": {
              "WORKFLOW": {
                "$ref": "#/components/schemas/NotificationOverrideMode"
              },
              "APPROVAL": {
                "$ref": "#/components/schemas/NotificationOverrideMode"
              },
              "REMINDER": {
                "$ref": "#/components/schemas/NotificationOverrideMode"
              },
              "OTP": {
                "$ref": "#/components/schemas/NotificationOverrideMode"
              },
              "ANNOUNCEMENT": {
                "$ref": "#/components/schemas/NotificationOverrideMode"
              },
              "ALERT": {
                "$ref": "#/components/schemas/NotificationOverrideMode"
              }
            }
          }
        }
      },
      "NotificationOverrideMode": {
        "type": "string",
        "enum": [
          "IMMEDIATE",
          "DIGEST",
          "IN_APP",
          "OFF"
        ]
      },
      "PrivacyRequest": {
        "type": "object",
        "description": "db 03 §4 `people.privacy_requests` — DPDP/PDPL data-subject-rights case; survives erasure of the underlying PII.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "request_no",
              "request_type",
              "market",
              "status",
              "requested_at"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "request_no": {
                "$ref": "#/components/schemas/BusinessNo"
              },
              "request_type": {
                "$ref": "#/components/schemas/PrivacyRequestType"
              },
              "market": {
                "$ref": "#/components/schemas/PrivacyRegime"
              },
              "status": {
                "$ref": "#/components/schemas/PrivacyRequestStatus"
              },
              "requested_at": {
                "$ref": "#/components/schemas/Timestamp"
              },
              "processed_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Timestamp"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "retention_hold_until": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnly"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "export_file": {
                "description": "Presigned export bundle (XC-F07), resolved from the stored object-storage key only when `request_type=EXPORT` and `status=COMPLETED`; `null` otherwise. The self-scoped reads (`people.privacy_request.{list,get,create}`) return this handle and NEVER the raw `export_storage_key` — the key is an object-storage implementation detail no subject can use.\n",
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/FileDownload"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "handled_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Uuid"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "employee_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.employees.full_name (employee_id), by projection (not a join) — the self-scoped reads run at `SELF`, where the employees ownership overlay would blank a join (issue #1640)."
              },
              "handled_by_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.employees.full_name (handled_by), by projection — the HR/DPO officer holding the case. `null` while unassigned, or when the name cannot be resolved (issue #1640)."
              },
              "outcome_note": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMeta"
          }
        ]
      },
      "PrivacyRequestDecision": {
        "description": "The HR/DPO decision response (`people.privacy_request.progress`). It echoes the `export_storage_key` the caller itself supplied on `PrivacyRequestProgressInput`, so the console can confirm what was attached; the subject-facing reads never carry it (see `PrivacyRequest.export_file`).\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/PrivacyRequest"
          },
          {
            "type": "object",
            "properties": {
              "export_storage_key": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "As supplied by this caller — never returned to the data subject."
              }
            }
          }
        ]
      },
      "PrivacyRequestCreateInput": {
        "type": "object",
        "required": [
          "request_type",
          "consent"
        ],
        "additionalProperties": false,
        "properties": {
          "request_type": {
            "$ref": "#/components/schemas/PrivacyRequestType"
          },
          "consent": {
            "type": "boolean",
            "description": "Explicit DPDP/PDPL acknowledgement; `false` or absent → 403 CONSENT_REQUIRED."
          }
        }
      },
      "PrivacyRequestProgressInput": {
        "type": "object",
        "required": [
          "status"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "$ref": "#/components/schemas/PrivacyRequestStatus"
          },
          "outcome_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "export_storage_key": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when request_type=EXPORT and status transitions to COMPLETED."
          },
          "retention_hold_until": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOnly"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "EngagementMoment": {
        "type": "object",
        "description": "db 03 §5 `people.engagement_moments` — consume-and-emit projection, no source-of-truth data of its own.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "moment_type",
              "occurs_on",
              "is_visible"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "employee_id": {
                "$ref": "#/components/schemas/Uuid"
              },
              "moment_type": {
                "$ref": "#/components/schemas/MomentType"
              },
              "occurs_on": {
                "$ref": "#/components/schemas/DateOnly"
              },
              "years": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "title": {
                "$ref": "#/components/schemas/LocalizedText"
              },
              "is_visible": {
                "type": "boolean"
              },
              "source_event": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AppendOnlyMeta"
          }
        ]
      },
      "Uuid": {
        "type": "string",
        "format": "uuid",
        "description": "UUIDv7 surrogate primary key (db-docs/00 §3). Never the business identifier."
      },
      "CursorPage": {
        "type": "object",
        "description": "Generic cursor-pagination envelope. List operations compose it via allOf to type `data`, e.g. `allOf: [ {$ref CursorPage}, { properties: { data: { items: {$ref Employee} } } } ]`.\n",
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {}
          },
          "page": {
            "type": "object",
            "required": [
              "has_more"
            ],
            "properties": {
              "next_cursor": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "prev_cursor": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "has_more": {
                "type": "boolean"
              },
              "total_est": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Optional, capped, APPROXIMATE row estimate for grid \"X of Z\" display only — never an exact COUNT(*) on large tables (attend.attendance_records, xc.notifications, audit.*).\n"
              }
            }
          }
        }
      },
      "ValidationProblem": {
        "description": "422 field-level validation failure; extends Problem with a per-field error array. `detail` is ALWAYS present on a 422 (#1251) and is the human summary of `errors[]`: one offending field renders as `\"<field>: <its message>\"` (`withholding_amount: is required for an India entity`), several as `\"N fields were refused: a, b, c.\"`, capped at five names. It is display copy derived from members already in the same body — clients keep branching on `code` and mapping `errors[].pointer` back to a control, never parsing this sentence.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Problem"
          },
          {
            "type": "object",
            "required": [
              "detail",
              "errors"
            ],
            "properties": {
              "detail": {
                "type": "string",
                "description": "Human summary of `errors[]`, always populated on a 422 so a client never has to fall back to generic copy for the one status that names a fixable field.\n",
                "example": "withholding_amount: is required for an India entity"
              },
              "errors": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "pointer",
                    "rule"
                  ],
                  "properties": {
                    "pointer": {
                      "type": "string",
                      "description": "JSON Pointer to the offending field, e.g. /claim_amount"
                    },
                    "rule": {
                      "type": "string",
                      "enum": [
                        "required",
                        "format",
                        "length",
                        "range",
                        "cross-field",
                        "async-server",
                        "consent-gated",
                        "uniqueness-business",
                        "not_found"
                      ],
                      "description": "FSD validation taxonomy rule (fsd-docs/00 §8.2). `not_found` is the server-side-lookup arm: a body field that REFERENCES another resource (e.g. `project_id` on a work entry) and did not resolve for this caller. It is reported here, under the field's pointer, and NOT as a 404 — the request addresses its own resource, so the failure belongs on the form field the client can actually fix (#805).\n"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "DateOnly": {
        "type": "string",
        "format": "date",
        "description": "Calendar-only value (pay-period date, leave date, due date)."
      },
      "BusinessNo": {
        "type": "string",
        "description": "Tenant-unique, prefixed human reference (`employee_no`, `requisition_no`, `offer_no`, `payslip_no`, `claim_no`, `ticket_no`, `asset_no`, `case_no`). Read-model field; never a path key.\n"
      },
      "Timestamp": {
        "type": "string",
        "format": "date-time",
        "description": "timestamptz, serialized UTC ISO-8601. Presentation timezone is a client concern."
      },
      "AuditMeta": {
        "type": "object",
        "description": "Standard mutable-entity columns (db-docs/00 §5). Read-only; present on every mutable read-model.",
        "readOnly": true,
        "properties": {
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "created_by": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ],
            "description": "null = system/jobs"
          },
          "updated_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "updated_by": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "deleted_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ],
            "description": "soft-delete marker; live rows are null. Deleted rows are excluded by default scope."
          },
          "version": {
            "type": "integer",
            "description": "Optimistic-lock counter (where present); surfaces as the ETag."
          }
        }
      },
      "FileDownload": {
        "type": "object",
        "description": "Authorized file handle (db-docs/00 §14, xc.files). Bytes never transit the API — the backend mints a time-limited presigned URL after authorization. Clients never see storage keys or hold storage credentials; the presigned URL is never persisted.\n",
        "required": [
          "file_id",
          "file_name",
          "url",
          "expires_at"
        ],
        "properties": {
          "file_id": {
            "$ref": "#/components/schemas/Uuid"
          },
          "file_name": {
            "type": "string"
          },
          "mime_type": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "content_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "SHA-256",
            "present where tamper-evidence matters (payslips": null,
            "letters": null,
            "e-sign artifacts).": null
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Time-limited presigned URL."
          },
          "expires_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "HijriDisplay": {
        "type": "string",
        "readOnly": true,
        "description": "Formatted Umm al-Qura display string (`*_hijri`, db-docs/00 §6) accompanying a canonical Gregorian value on KSA-facing read-models (GOSI/WPS periods, Iqama expiry, KSA payslips). NEVER the source of truth; never accepted as input.\n"
      },
      "CurrencyCode": {
        "type": "string",
        "enum": [
          "INR",
          "SAR"
        ],
        "description": "ISO-4217. A legal entity operates in a single currency (db-docs/00 §6)."
      },
      "LocalizedText": {
        "type": "object",
        "description": "Locale-keyed content map (db-docs/00 §9) for localized/white-label text (designation titles, announcement bodies, template names). Keys are the legal entity's active locale set.\n",
        "properties": {
          "en": {
            "type": "string"
          },
          "ar": {
            "type": "string"
          }
        },
        "additionalProperties": {
          "type": "string"
        }
      },
      "AppendOnlyMeta": {
        "type": "object",
        "description": "Standard append-only/immutable columns (db-docs/00 §5/§8). No update/version/delete; corrections are new compensating rows.",
        "readOnly": true,
        "properties": {
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "created_by": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem detail (application/problem+json). The platform-wide error envelope (03 §1).",
        "required": [
          "type",
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "default": "about:blank",
            "description": "Problem-type URI."
          },
          "title": {
            "type": "string",
            "description": "Short, human-readable summary (stable per type)."
          },
          "status": {
            "type": "integer",
            "description": "HTTP status code, duplicated for convenience."
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation specific to this occurrence."
          },
          "instance": {
            "type": "string",
            "description": "URI reference for this specific occurrence."
          },
          "code": {
            "$ref": "#/components/schemas/ErrorCode"
          },
          "correlation_id": {
            "$ref": "#/components/schemas/Uuid"
          }
        }
      },
      "ErrorCode": {
        "type": "string",
        "description": "Machine-stable error code (paired with the HTTP status; drives client handling, not display copy).",
        "enum": [
          "VALIDATION_FAILED",
          "IDEMPOTENCY_KEY_REUSE",
          "VERSION_CONFLICT",
          "PRECONDITION_REQUIRED",
          "MAKER_EQUALS_CHECKER",
          "STEP_UP_REQUIRED",
          "CONSENT_REQUIRED",
          "TOKEN_DENIED",
          "SCOPE_DENIED",
          "OWNERSHIP_DENIED",
          "STATE_TRANSITION_INVALID",
          "FACE_MISMATCH",
          "FACE_VERIFICATION_REQUIRED",
          "WITHHOLDING_REVIEW_REQUIRED",
          "FEATURE_NOT_IN_PLAN",
          "PLAN_LIMIT_EXCEEDED",
          "TENANT_PAST_DUE",
          "TENANT_SUSPENDED",
          "TENANT_CANCELLED",
          "RATE_LIMITED",
          "NOT_FOUND"
        ]
      }
    },
    "parameters": {
      "AcceptLanguage": {
        "name": "Accept-Language",
        "in": "header",
        "required": false,
        "description": "Locale for server-rendered/localized text (LocalizedText resolution, letters, notifications). Active locale set comes from the legal entity's compliance pack; KSA tenants default `ar`.\n",
        "schema": {
          "type": "string",
          "enum": [
            "en",
            "ar"
          ],
          "default": "en"
        }
      },
      "CorrelationId": {
        "name": "X-Correlation-Id",
        "in": "header",
        "required": false,
        "description": "Caller-supplied trace id; echoed back. Generated if absent.",
        "schema": {
          "$ref": "#/components/schemas/Uuid"
        }
      },
      "PageSize": {
        "name": "page[size]",
        "in": "query",
        "required": false,
        "description": "Max items per page. Cursor pagination only (03 §2); offset pagination is rejected (ADR 0015).",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "PageAfter": {
        "name": "page[after]",
        "in": "query",
        "required": false,
        "description": "Opaque forward keyset cursor (from a prior page's `page.next_cursor`).",
        "schema": {
          "type": "string"
        }
      },
      "PageBefore": {
        "name": "page[before]",
        "in": "query",
        "required": false,
        "description": "Opaque backward keyset cursor (from a prior page's `page.prev_cursor`).",
        "schema": {
          "type": "string"
        }
      },
      "SortParam": {
        "name": "sort",
        "in": "query",
        "required": false,
        "description": "Comma-separated sort keys; leading `-` = descending. Each key MUST be in the operation's documented sort whitelist (free-form sort is rejected so the keyset cursor stays stable).\n",
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Client-generated unique key. REQUIRED on every mutation (this round tightens ADR 0015's \"platform + retryable mutations\" floor to ALL mutations for uniformity — offline punch/leave sync depends on it). Scoped (tenant, principal, route, key); a replay within the ~24h window returns the stored response with `Idempotency-Replayed: true`; the same key with a different body → 409 IDEMPOTENCY_KEY_REUSE (04 §1).\n",
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 255
        }
      },
      "PathId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "UUIDv7 surrogate key of the target resource. Business numbers (`employee_no`, `claim_no`, …) are read-model fields, never path keys.",
        "schema": {
          "$ref": "#/components/schemas/Uuid"
        }
      },
      "IfMatch": {
        "name": "If-Match",
        "in": "header",
        "required": true,
        "description": "Optimistic-concurrency precondition for mutating a VERSIONED mutable entity (db-docs/00 §5 applies `version` where concurrent edits are likely). Value is the entity's current ETag (the row `version`). Absent → 428; stale → 412 (04 §2). N/A for append-only entities and for unversioned low-contention entities (their update ops simply omit this parameter).\n",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, expired, or invalid session token (no authenticated principal).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Authenticated but denied — permission token not granted, out of scope (self/team/branch), not the owner, tenant suspended, or an MC-2 operation without a fresh step-up challenge. `code` ∈ TOKEN_DENIED | SCOPE_DENIED | OWNERSHIP_DENIED | MAKER_EQUALS_CHECKER | STEP_UP_REQUIRED | CONSENT_REQUIRED | TENANT_SUSPENDED. A plan feature-flag being off is 402 FEATURE_NOT_IN_PLAN, not 403 (see PaymentRequired).\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource does not exist OR is masked by RLS (tenant/self/team/branch scope) — the API does not distinguish, so existence is never confirmed across a scope boundary (02 §4 disclosure posture).\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Per-principal/route rate limit exceeded.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Plan entitlement refused (ADR 0009 entitlement order). `code` ∈ PLAN_LIMIT_EXCEEDED | FEATURE_NOT_IN_PLAN. For PLAN_LIMIT_EXCEEDED, `detail` names the resource, the current count and the cap; GroundIT publishes four enforced numeric limits — `maxEmployees`, `maxUsers`, `maxLegalEntities`, `maxWorkLocations` (README \"Plan limits and feature flags\"). FEATURE_NOT_IN_PLAN is the plan feature-flag gate (the One portal's `feature_locked` → 402). Both resolve via the One portal plan builder, not in-product.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Conflict": {
        "description": "Idempotency-key reuse with a different body, an invalid state transition, or a uniqueness/business-rule violation (fsd 00 §8.2). For database-backed uniqueness rules, detail names the exact violated rule; unmapped constraints are not collapsed into a generic 409.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Locked": {
        "description": "Tenant subscription `past_due` (ADR 0009): WRITES are blocked (423), reads still succeed. `code` = TENANT_PAST_DUE. Mutations return this; list/get operations do not.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "PreconditionFailed": {
        "description": "If-Match / ETag mismatch — the row changed since it was read (412, VERSION_CONFLICT).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Field-level validation failed. The body carries both `errors[]` (per-field, pointer-addressed) and a `detail` summarising them — never an empty `detail` (#1251).\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationProblem"
            },
            "examples": {
              "singleField": {
                "summary": "One refused field — `detail` names it",
                "value": {
                  "type": "about:blank",
                  "title": "Unprocessable Entity",
                  "status": 422,
                  "code": "VALIDATION_FAILED",
                  "detail": "withholding_amount: is required for an India entity",
                  "errors": [
                    {
                      "pointer": "/withholding_amount",
                      "rule": "required",
                      "message": "is required for an India entity"
                    }
                  ]
                }
              },
              "severalFields": {
                "summary": "Several refused fields — `detail` counts and names them",
                "value": {
                  "type": "about:blank",
                  "title": "Unprocessable Entity",
                  "status": 422,
                  "code": "VALIDATION_FAILED",
                  "detail": "2 fields were refused: effective_from, cap_amount.amount.",
                  "errors": [
                    {
                      "pointer": "/effective_from",
                      "rule": "cross-field",
                      "message": "Overlaps an existing version."
                    },
                    {
                      "pointer": "/cap_amount/amount",
                      "rule": "range",
                      "message": "Must be a non-negative decimal string."
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "PreconditionRequired": {
        "description": "If-Match header absent on a mutation of a versioned mutable entity (428).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "headers": {
      "Location": {
        "description": "URI of the created/affected resource.",
        "schema": {
          "type": "string",
          "format": "uri-reference"
        }
      },
      "ETag": {
        "description": "Strong validator of the row `version` (e.g. `\"v7\"`). Use as `If-Match` on the next mutation.",
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyReplayed": {
        "description": "`true` when a stored idempotent response was replayed rather than freshly computed.",
        "schema": {
          "type": "boolean"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying (on 429 / 503).",
        "schema": {
          "type": "integer"
        }
      }
    }
  }
}