{
  "openapi": "3.1.0",
  "info": {
    "title": "GroundIT — Attendance & Leave",
    "version": "0.1.0",
    "license": {
      "name": "Proprietary — GroundIT/Sysmedac internal"
    },
    "description": "On-device geofenced + face-attested clock in/out with an idempotent offline punch queue (ADR 0004, ADR 0015), out-of-zone exception handling, overtime request/approval, attendance regularization (maker-checker, original + corrected both retained), schedules & shift assignment, and the manager's team attendance view (`attend.*`) · plus the append-only leave balances ledger, leave apply/approve, market statutory leave, and leave encashment feeding payroll by event (`leave.*`). See ../../api-docs/00-api-overview-and-conventions.md.\n"
  },
  "servers": [
    {
      "url": "/api/v1"
    }
  ],
  "security": [
    {
      "bearerJWT": []
    }
  ],
  "tags": [
    {
      "name": "attend",
      "description": "On-device attendance — punches, exceptions, overtime, regularization, schedules, team view."
    },
    {
      "name": "leave",
      "description": "Leave ledger, applications/approvals, holidays, comp-off, encashment."
    }
  ],
  "x-reuse-anchors": {
    "parameters": {
      "path_id": {
        "$ref": "#/components/parameters/PathId"
      },
      "page_size": {
        "$ref": "#/components/parameters/PageSize"
      },
      "page_after": {
        "$ref": "#/components/parameters/PageAfter"
      },
      "page_before": {
        "$ref": "#/components/parameters/PageBefore"
      },
      "sort_param": {
        "$ref": "#/components/parameters/SortParam"
      },
      "accept_language": {
        "$ref": "#/components/parameters/AcceptLanguage"
      },
      "idempotency_key": {
        "$ref": "#/components/parameters/IdempotencyKey"
      },
      "if_match": {
        "$ref": "#/components/parameters/IfMatch"
      },
      "scope_legal_entity": {
        "name": "legal_entity_id",
        "in": "query",
        "required": false,
        "description": "Widen the queue to every ACTIVE employee in this legal entity (branch). Combines with `department_id` and `org_unit_id`, and REPLACES the caller's direct-report bound rather than intersecting with it. Requires a tenant-wide grant on this operation (403 otherwise).",
        "schema": {
          "$ref": "#/components/schemas/UuidRef"
        }
      },
      "scope_department": {
        "name": "department_id",
        "in": "query",
        "required": false,
        "description": "Widen the queue to every ACTIVE employee in this department. Combines with `legal_entity_id` and `org_unit_id`. Requires a tenant-wide grant on this operation (403 otherwise).",
        "schema": {
          "$ref": "#/components/schemas/UuidRef"
        }
      },
      "scope_org_unit": {
        "name": "org_unit_id",
        "in": "query",
        "required": false,
        "description": "Widen the queue to every ACTIVE employee in this org unit AND its sub-units — the unit's org-tree closure (`app.org_unit_closure`, migration 0162), so a department's parent unit includes the teams beneath it. Requires a tenant-wide grant on this operation (403 otherwise).",
        "schema": {
          "$ref": "#/components/schemas/UuidRef"
        }
      },
      "scope_all": {
        "name": "scope",
        "in": "query",
        "required": false,
        "description": "`ALL` — every ACTIVE employee in the tenant, with no axis narrowing it. Requires a tenant-wide grant on this operation (403 otherwise).",
        "schema": {
          "type": "string",
          "enum": [
            "ALL"
          ]
        }
      }
    },
    "responses": {
      "unauthorized": {
        "$ref": "#/components/responses/Unauthorized"
      },
      "forbidden": {
        "$ref": "#/components/responses/Forbidden"
      },
      "not_found": {
        "$ref": "#/components/responses/NotFound"
      },
      "conflict": {
        "$ref": "#/components/responses/Conflict"
      },
      "unprocessable": {
        "$ref": "#/components/responses/UnprocessableEntity"
      },
      "locked": {
        "$ref": "#/components/responses/Locked"
      },
      "too_many": {
        "$ref": "#/components/responses/TooManyRequests"
      },
      "precondition_required": {
        "$ref": "#/components/responses/PreconditionRequired"
      },
      "precondition_failed": {
        "$ref": "#/components/responses/PreconditionFailed"
      }
    },
    "headers": {
      "etag": {
        "$ref": "#/components/headers/ETag"
      },
      "location": {
        "$ref": "#/components/headers/Location"
      },
      "idem_replayed": {
        "$ref": "#/components/headers/IdempotencyReplayed"
      }
    }
  },
  "paths": {
    "/late-entry-requests/me": {
      "get": {
        "operationId": "attend.late_entry.list_me",
        "summary": "Read my late-entry requests and HR decisions",
        "tags": [
          "attend"
        ],
        "x-token": "attend.late_entry.list_me",
        "x-realizes-features": [
          "ATT-F03",
          "ATT-F06"
        ],
        "x-screens": [
          "ATT-S02",
          "ATT-S12",
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.late_entry_requests"
        ],
        "x-idempotent": false,
        "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,
        "responses": {
          "200": {
            "description": "Current request state. Approval does not record attendance; a fresh validated clock-in is required before expiry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LateEntryRequest"
                      }
                    }
                  }
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/late-entry-requests": {
      "get": {
        "operationId": "attend.late_entry.list_for_review",
        "summary": "Read late-entry requests for HR review",
        "tags": [
          "attend"
        ],
        "x-token": "attend.late_entry.list_for_review",
        "x-realizes-features": [
          "ATT-F03",
          "ATT-F06"
        ],
        "x-screens": [
          "ATT-S02",
          "ATT-S12",
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.late_entry_requests"
        ],
        "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,
        "responses": {
          "200": {
            "description": "Current request state. Approval does not record attendance; a fresh validated clock-in is required before expiry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LateEntryRequest"
                      }
                    }
                  }
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/late-entry-requests/{id}/submit": {
      "post": {
        "operationId": "attend.late_entry.submit",
        "summary": "Send an absent appeal (clock-in after the absence cutoff) to the reporting manager",
        "description": "#1878. The id is the `absent_appeal_id` the refused punch carried (`refusal_reason: ABSENT_APPEAL_REQUIRED`). `BLOCKED → PENDING`; raises an `ABSENT_APPEAL` approvals-inbox envelope routed to the reporting manager (a manager-less employee's appeal escalates to `ROLE:hr_admin`). Re-submitting a PENDING appeal is a no-op `200`. `409` once it is decided, superseded by an HR day override, or lapsed at 00:00 local on the day after the work date.\n",
        "tags": [
          "attend"
        ],
        "x-token": "attend.late_entry.submit",
        "x-realizes-features": [
          "ATT-F03",
          "ATT-F06"
        ],
        "x-screens": [
          "ATT-S02",
          "ATT-S12",
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.late_entry_requests"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "reason",
                  "version"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  },
                  "version": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "The appeal's current `version` (0 for a fresh refusal)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current request state. Approval does not record attendance; a fresh validated clock-in is required before expiry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LateEntryRequest"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/late-entry-requests/{id}/decide": {
      "post": {
        "operationId": "attend.absent_appeal.decide",
        "summary": "Decide an absent appeal — FULL_DAY, HALF_DAY or REJECT",
        "description": "#1878. The reporting manager's decision (TEAM scope, direct reports only; the approvals inbox reaches the same method, and `xc.approval_inbox.decide_override` lets the tenant ceiling decide at TENANT). FULL_DAY / HALF_DAY approve the appeal: the employee may clock in again (a fresh punch, captured after the decision), and the outcome then LOCKS the day's status — PRESENT or HALF_DAY — whatever the clock-out time. REJECT refuses it; the day stays ABSENT and the salaried absence rule (CL first, then LOP) settles it after midnight. The employee cannot decide their own appeal (`403`); a decided or lapsed appeal is a `409`. Emits `attend.absent_appeal.decided`.\n",
        "tags": [
          "attend"
        ],
        "x-token": "attend.absent_appeal.decide",
        "x-realizes-features": [
          "ATT-F03",
          "ATT-F06"
        ],
        "x-screens": [
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.late_entry_requests",
          "xc.approval_inbox",
          "audit.audit_log"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.absent_appeal.decided",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "outcome"
                ],
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "FULL_DAY",
                      "HALF_DAY",
                      "REJECT"
                    ]
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decided appeal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LateEntryRequest"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/late-entry-requests/{id}/approve": {
      "post": {
        "operationId": "attend.late_entry.approve",
        "summary": "Unlock one fresh clock-in for this shift",
        "tags": [
          "attend"
        ],
        "x-token": "attend.late_entry.approve",
        "x-realizes-features": [
          "ATT-F03",
          "ATT-F06"
        ],
        "x-screens": [
          "ATT-S02",
          "ATT-S12",
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.late_entry_requests"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "note"
                ],
                "properties": {
                  "note": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current request state. Approval does not record attendance; a fresh validated clock-in is required before expiry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LateEntryRequest"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/late-entry-requests/{id}/reject": {
      "post": {
        "operationId": "attend.late_entry.reject",
        "summary": "Reject late entry for this shift",
        "tags": [
          "attend"
        ],
        "x-token": "attend.late_entry.reject",
        "x-realizes-features": [
          "ATT-F03",
          "ATT-F06"
        ],
        "x-screens": [
          "ATT-S02",
          "ATT-S12",
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.late_entry_requests"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "note"
                ],
                "properties": {
                  "note": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current request state. Approval does not record attendance; a fresh validated clock-in is required before expiry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LateEntryRequest"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/muster-face-checks": {
      "post": {
        "operationId": "attend.muster.face_check",
        "summary": "Verify a muster worker face with server-derived marker identity",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.face_check",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_face_checks",
          "attend.face_enrollments",
          "audit.audit_log"
        ],
        "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,
        "description": "Marker identity and SITE/TEAM channel are derived on the server. Worker must be ACTIVE, MUSTER and different from the caller. SITE additionally requires tenant-scoped muster.record; TEAM requires the enabled setting and live membership of a team led by the caller. No similarity, threshold, distance, template or photo bytes appear in responses. An ACTIVE enrolment cannot be replaced by a supervisor; HR must reset it first. Confirm requires ACTIVE plus SUPERVISOR, If-Match and HR permission; revoke uses the existing reset route.\n",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MusterFaceCheckRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Safe metadata; checks expire 30 minutes after creation. NO_FACE returns 422 after auditing the failed capture.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterFaceCheck"
                }
              }
            }
          },
          "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"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          }
        }
      }
    },
    "/muster-workers/{employeeId}/face-enrollment": {
      "post": {
        "operationId": "attend.muster.face_enroll",
        "summary": "Capture an in-person muster face registration for HR approval",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.face_enroll",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_face_checks",
          "attend.face_enrollments",
          "audit.audit_log"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.face_enrollment.captured_by_supervisor",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "description": "Marker identity and SITE/TEAM channel are derived on the server. Worker must be ACTIVE, MUSTER and different from the caller. SITE additionally requires tenant-scoped muster.record; TEAM requires the enabled setting and live membership of a team led by the caller. No similarity, threshold, distance, template or photo bytes appear in responses. An ACTIVE enrolment cannot be replaced by a supervisor; HR must reset it first. Owner ruling 2026-09-28 (supersedes \"active immediately\"): the capture is stored PENDING and raises the same `FACE_ENROLMENT` approval envelope a self-service registration does (`requested_by` = the worker). No face check is minted (`face_check_id` is null), and until HR approves, every marker path refuses a PRESENT/HALF_DAY mark for the worker with `muster-face-enrolment-pending` (409). A second capture while one is PENDING is refused with the same problem. The capturer can never decide the envelope (maker-checker on `captured_by`). Confirm requires ACTIVE plus SUPERVISOR, If-Match and HR permission; revoke uses the existing reset route.\n",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "employeeId",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MusterSupervisorEnrollmentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Safe metadata; checks expire 30 minutes after creation. NO_FACE returns 422 after auditing the failed capture.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterSupervisorEnrollment"
                }
              }
            }
          },
          "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"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          }
        }
      }
    },
    "/face-enrollments/{id}/confirm": {
      "post": {
        "operationId": "attend.face_enrollment.confirm",
        "summary": "Confirm an ACTIVE supervisor capture after the fact",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.face_enrollment.confirm",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_face_checks",
          "attend.face_enrollments",
          "audit.audit_log"
        ],
        "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,
        "description": "Marker identity and SITE/TEAM channel are derived on the server. Worker must be ACTIVE, MUSTER and different from the caller. SITE additionally requires tenant-scoped muster.record; TEAM requires the enabled setting and live membership of a team led by the caller. No similarity, threshold, distance, template or photo bytes appear in responses. An ACTIVE enrolment cannot be replaced by a supervisor; HR must reset it first. Confirm requires ACTIVE plus SUPERVISOR, If-Match and HR permission; revoke uses the existing reset route.\n",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Safe metadata; checks expire 30 minutes after creation. NO_FACE returns 422 after auditing the failed capture.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceEnrollment"
                }
              }
            }
          },
          "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"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          }
        }
      }
    },
    "/face-enrollments": {
      "get": {
        "operationId": "attend.face_enrollment.list_for_review",
        "summary": "List face enrolments for HR review, linked by employee id",
        "description": "Authoritative owning-table queue/history for the People face-approvals page. Unlike the asynchronous unified-inbox projection, this read makes a submitted enrolment visible immediately even when a tenant has no published `FACE_ENROLMENT` routing rule. Returns safe metadata only — never the photo URL or embedding. Open an individual row through `GET /face-enrollments/{id}` to obtain the audited, short-lived photo URL. `status=PENDING` is the review queue; `ACTIVE`/`REJECTED`/`RETIRED` provide employee-linked history.\n",
        "tags": [
          "attend",
          "face_enrollment"
        ],
        "x-token": "attend.face_enrollment.list_for_review",
        "x-realizes-features": [
          "ATT-F08"
        ],
        "x-screens": [
          "PPL-S32"
        ],
        "x-touches-entities": [
          "attend.face_enrollments",
          "people.employees"
        ],
        "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,
        "parameters": [
          {
            "name": "capture_channel",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "SELF",
                "SUPERVISOR"
              ]
            }
          },
          {
            "name": "hr_confirmed",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/FaceEnrollmentStatus"
            }
          },
          {
            "name": "page[size]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Face-enrolment metadata ordered newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FaceEnrollment"
                      }
                    },
                    "page": {
                      "$ref": "#/components/schemas/CursorPageRef"
                    }
                  }
                }
              }
            }
          },
          "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"
          }
        }
      },
      "post": {
        "operationId": "attend.face_enrollment.create",
        "summary": "Enrol my reference face — lands PENDING until HR approves it",
        "description": "**ADR 0025, amended by D12 (GAP-39, `#967`).** The one-time consent-bearing action that starts server-verified attendance capture for this employee. The app has already uploaded a face photo through `xc.file.request_upload_url`; this call passes its `storage_key`, and the server downloads those bytes and derives a **face embedding** (a biometric template) with the configured `FaceMatchPort` adapter — exactly as before. What changed: the new row lands **`PENDING`**, not `ACTIVE`. It is not compared against any punch photo until `attend.face_enrollment.approve` moves it to `ACTIVE` — a self-serve photo does not become a trusted reference on submission alone. **Punches are never blocked on that decision**: a punch made while this enrolment is PENDING is accepted and carries a `FACE_UNVERIFIED_SERVER` warning, exactly as an unenrolled employee's punch already was (`attend.punch.sync`/`.web`). Retires whatever enrolment was `ACTIVE` **or `PENDING`** for this employee and stores the new one (`attend.face_enrollments`). A still-`PENDING` predecessor also has its unified-inbox envelope stamped `WITHDRAWN` in the same transaction (`decision_note`: superseded by a new enrolment), so HR is not left a ghost row over a `RETIRED` source. **Performing this call IS the consent record** for processing biometric data (DPDP India / PDPL KSA); there is no separate consent flag, and re-enrolling replaces the template rather than adding one. The embedding is **never** returned by any operation in this contract. Answers `422` when the photo is undecodable or carries no detectable face — a failed enrolment stores nothing.\n",
        "tags": [
          "attend",
          "face_enrollment"
        ],
        "x-token": "attend.face_enrollment.create",
        "x-realizes-features": [
          "ATT-F08"
        ],
        "x-screens": [
          "ATT-S18"
        ],
        "x-touches-entities": [
          "attend.face_enrollments",
          "xc.object_refs",
          "people.employees",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.face_enrollment.submitted",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FaceEnrollmentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Submitted — `status` is `PENDING`. Any previously ACTIVE or PENDING enrolment for this employee is now RETIRED, and any still-PENDING approval envelope over that predecessor is WITHDRAWN.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceEnrollment"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/face-enrollments/me": {
      "get": {
        "operationId": "attend.face_enrollment.get_me",
        "summary": "My current or latest rejected face enrolment (metadata + signed photo URL — never the template)",
        "description": "The employee's own **live** enrolment — `ACTIVE` (HR-approved, the one a punch is actually verified against) or `PENDING` (awaiting HR; D12, GAP-39, `#967`) — so `ATT-S18` can show what is on file, whether it is still awaiting a decision, and offer to replace or withdraw it. When HR rejects the submission, this operation continues to return the latest `REJECTED` row and its `review_note`, so mobile can explain the decision and offer a retake instead of turning a real rejection into a misleading 404. Returns the enrolment's metadata and a short-lived presigned URL for the **photo**; the `embedding` is a biometric template and is **never** serialised onto this or any other response (ADR 0025). `404` only when the employee has never enrolled, their enrolment was withdrawn, or it was erased — the client treats that as \"enrolment required\" (`FACE_NOT_ENROLLED` is the matching punch-time warning; a `PENDING` row instead produces `FACE_UNVERIFIED_SERVER`, never a punch refusal — punches are **never** blocked on this).\n",
        "tags": [
          "attend",
          "face_enrollment"
        ],
        "x-token": "attend.face_enrollment.get_me",
        "x-realizes-features": [
          "ATT-F08"
        ],
        "x-screens": [
          "ATT-S18"
        ],
        "x-touches-entities": [
          "attend.face_enrollments",
          "xc.object_refs"
        ],
        "x-idempotent": false,
        "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,
        "responses": {
          "200": {
            "description": "The current enrolment (`ACTIVE`/`PENDING`) or latest `REJECTED` attempt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceEnrollmentDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/face-enrollments/{id}": {
      "get": {
        "operationId": "attend.face_enrollment.review",
        "summary": "Review one employee face enrolment (HR) — photo and metadata, never the template",
        "description": "The HR review projection used from the People face-approvals page. Returns the enrolment's status metadata and a 15-minute presigned URL for the submitted photo so the reviewer can make the human identity decision before approving or rejecting it. The biometric embedding is never selected or returned. This is a tenant-scoped privileged read of another employee's biometric record: `hr_admin` only, self-review is refused by the same four-eyes boundary as the decision, and every successful open writes an `audit.access_log` `DATA_ACCESS` row with `PII_OTHER` as the current coarse classification. The decision may be submitted on the owning approve/reject routes used by this page or through `xc.approval_inbox.decide`; the latter routes `attend.face_enrollments` back to the same owning attendance transition.\n",
        "tags": [
          "attend",
          "face_enrollment"
        ],
        "x-token": "attend.face_enrollment.review",
        "x-realizes-features": [
          "ATT-F08"
        ],
        "x-screens": [
          "PPL-S32"
        ],
        "x-touches-entities": [
          "attend.face_enrollments",
          "xc.object_refs",
          "audit.access_log"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Review metadata and a short-lived URL for the submitted photo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceEnrollmentDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/face-enrollments/{id}/approve": {
      "post": {
        "operationId": "attend.face_enrollment.approve",
        "summary": "Approve a PENDING face enrolment (HR) — activates the reference photo",
        "description": "**D12 (GAP-39, `#967`).** The one action that moves a self-serve enrolment from `PENDING` to `ACTIVE` — only from that point on does `attend.punch.sync`/`.web` ever compare a punch photo against it. A **privileged, tenant-scoped read of another employee's biometric-adjacent record**: granted only to `hr_admin` (the module's generic tenant-scope grant rule), and every decision writes an `audit.access_log` `DATA_ACCESS` row (`PII_OTHER` — `access_log.data_class` has no `BIOMETRIC` member, `security-docs/04 §4`) plus an `audit.audit_log` state-change row. Four-eyes is enforced twice — the handler AND `face_enrollments_four_eyes` (migration `0150`, a `CHECK` constraint) — so a reviewer can never approve their own enrolment, even by accident. `409` when the row is not `PENDING` (already decided, withdrawn, or superseded by a later re-enrolment). An inbox decide against a leftover envelope whose source is already `RETIRED` or `REJECTED` WITHDRAWS that envelope and then 409s (`This enrolment was replaced; nothing to decide`) so XC never stamps a decision on a superseded source. Also projects into the unified approvals rail (`xc.approval_inbox`, `XC-F12`) as `request_type: FACE_ENROLMENT` at submit time, the same way `attend.out_of_zone_event.approve` does for its own domain.\n",
        "tags": [
          "attend",
          "face_enrollment"
        ],
        "x-token": "attend.face_enrollment.approve",
        "x-realizes-features": [
          "ATT-F08"
        ],
        "x-screens": [
          "ATT-S18"
        ],
        "x-touches-entities": [
          "attend.face_enrollments",
          "audit.access_log",
          "audit.audit_log",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.face_enrollment.approved",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved — `status` is now `ACTIVE`.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceEnrollment"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/face-enrollments/{id}/reject": {
      "post": {
        "operationId": "attend.face_enrollment.reject",
        "summary": "Reject a PENDING face enrolment (HR) — retires the reference photo",
        "description": "**D12 (GAP-39, `#967`).** Moves a self-serve enrolment from `PENDING` to `REJECTED` and retires it in the SAME transaction — `embedding` is nulled immediately, exactly as a withdrawal or a superseding re-enrolment already does, because a declined reference must stop being a usable claim at once, not \"eventually\". The employee keeps punching in the meantime (`FACE_UNVERIFIED_SERVER`-class warning) and may enrol again. Carries the same privileged-read posture as `.approve` (tenant-scoped, `hr_admin`-only, `audit.access_log` + `audit.audit_log`, four-eyes twice-enforced). `409` when the row is not `PENDING`.\n",
        "tags": [
          "attend",
          "face_enrollment"
        ],
        "x-token": "attend.face_enrollment.reject",
        "x-realizes-features": [
          "ATT-F08"
        ],
        "x-screens": [
          "ATT-S18"
        ],
        "x-touches-entities": [
          "attend.face_enrollments",
          "audit.access_log",
          "audit.audit_log",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.face_enrollment.rejected",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected — `status` is now `REJECTED`, `embedding` is nulled.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceEnrollment"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/face-enrollments/{id}/withdraw": {
      "post": {
        "operationId": "attend.face_enrollment.withdraw",
        "summary": "Withdraw my own face enrolment (Settings) — actually retires the reference",
        "description": "Employee-initiated retirement, self-scoped — Settings' \"remove my face enrolment\" (`design-pwa/06-DEVICE-CAPABILITIES.md` §2: *\"allow withdrawal in Settings\"*). Works on `ACTIVE` (an HR-approved, live reference) or `PENDING` (withdrawn before HR ever looked) alike. **Actually** retires the reference — `status` becomes `RETIRED` and `embedding` is nulled in the SAME transaction, not a display flag an approved template quietly survives behind. A still-`PENDING` approval envelope is stamped `WITHDRAWN` in that same transaction. `409` when the row is already `REJECTED` or `RETIRED` — nothing left to withdraw.\n",
        "tags": [
          "attend",
          "face_enrollment"
        ],
        "x-token": "attend.face_enrollment.withdraw",
        "x-realizes-features": [
          "ATT-F08"
        ],
        "x-screens": [
          "ATT-S18"
        ],
        "x-touches-entities": [
          "attend.face_enrollments",
          "xc.approval_inbox",
          "audit.audit_log"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.face_enrollment.withdrawn",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawn — `status` is now `RETIRED`, `embedding` is nulled.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceEnrollment"
                }
              }
            }
          },
          "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"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/face-enrollments/{id}/reset": {
      "post": {
        "operationId": "attend.face_enrollment.reset",
        "summary": "Reset an employee face enrolment (HR) — retires the current reference",
        "description": "Tenant-scoped HR operation for the employee profile. Retires an `ACTIVE` or `PENDING` enrolment and nulls its biometric embedding in the same transaction. A pending approval envelope is closed as `WITHDRAWN`, so the old photo cannot later be approved. The employee may submit a fresh photo through `POST /face-enrollments`; the mobile GET then reports no current reference until that submission is made.\n",
        "tags": [
          "attend",
          "face_enrollment"
        ],
        "x-token": "attend.face_enrollment.reset",
        "x-realizes-features": [
          "ATT-F08"
        ],
        "x-screens": [
          "PPL-S14"
        ],
        "x-touches-entities": [
          "attend.face_enrollments",
          "xc.approval_inbox",
          "audit.access_log",
          "audit.audit_log"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.face_enrollment.reset",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Reset — `status` is now `RETIRED` and the embedding is nulled.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceEnrollment"
                }
              }
            }
          },
          "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"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-punches/web": {
      "post": {
        "operationId": "attend.punch.web",
        "summary": "Record a plain web (ESS portal) punch — no camera, no geofence",
        "description": "The browser punch rail (ADR 0039, closes `GAP-ESS-20`). Web ESS ships **plain punch**: there is no camera, no geofence and no selfie — that is the mobile surface's job (ADR 0004/0025), and a browser must never be asked to fake one.\nIt is a **separate route because it is a separate permission token**: the route guard enforces exactly one token per route, so a tenant can only allow the browser rail independently of the handset rail if the two have their own doors (design-ess 05 §13 Q1 recommended exactly this). A site that requires device evidence simply does not grant `attend.punch.web`.\nThe request body is the `sync` shape narrowed to an **allowlist**, and each punch must **declare what it did not do**: `geofence.result: UNKNOWN` and `face.result: SKIPPED` — the real enum values for \"not checked\". A web punch may carry ONLY `client_punch_id`, `punch_type`, `capture_mode`, `geofence.{result, captured_at}` and `face.{result, captured_at}`; **every other field of the envelope is refused with 422**, including `geofence.geofence_id`, `geofence.work_location_id`, `geofence.device_meta`, `face.liveness_passed`, `face.confidence` and `face.attestation_meta`. These are pure client claims on the one rail that is unconditionally non-exceptional, and a blocklist that admitted them would let a browser stamp a record with a work location it cannot know (`work_location_id` lands on `attendance_records`) or assert a liveness/confidence score from a surface with no camera. The write path additionally forces those columns NULL when the punch arrives on this rail. Unlike a device punch missing the same evidence, a web punch is **not** flagged exceptional and files no out-of-zone event — the browser was never asked, so the absence is not an anomaly. The honesty lands in the record's `source`, which this rail writes as `WEB` (closing `GAP-ESS-21`, where the column was never written at all and every record silently claimed the `MOBILE_GEOFENCE` default).\n`client_punch_id` remains the idempotency spine: a replayed id yields the same result, so a double-tap or a retry after a dropped connection can never double-punch.\n\n**The web rail is no longer a way around a fenced placement (#1317, ADR 0067).** `attend.punch.web` is a ROLE grant, so before #1317 an employee moved to a fenced plant went on punching from a browser with no evidence and no exception — the assignment looked applied and did nothing. Now: a `FIELD` or `MUSTER` employee's web punch **is** flagged exceptional and files a `LOCATION_UNKNOWN` out-of-zone event (their placement expects a located punch, and this rail carries none — `PUNCH_WITHOUT_LOCATION_EVIDENCE`); and it is refused `403` with the problem `type` `urn:groundit:problem:attend:punch-location-evidence-required` when their assigned work location is `BLOCKING` **and has a geofence** — either because the mode expects a located punch (`FIELD`, `MUSTER`) or because the site's own fence is meant to decide (`DESK`). A `REMOTE` or `HYBRID` employee is **never** refused on this rail even at a `BLOCKING` fenced site: their placement says location is not the measure, so there is nothing for a fence to decide. And a `DESK` employee at an `ADVISORY` location — the overwhelming majority — is completely unaffected: their web punch stays unflagged, files nothing, and reads exactly as it did before.\n\n**`409 FACE_MISMATCH` (#1631) is declared on this rail and is unreachable on it in practice.** The web punch must declare `face.result: SKIPPED` and carries no photo, so the server's verdict is \"there was nothing to check\" and the identity refusal — which requires a SERVER-reached `NO_MATCH` / `SPOOF_SUSPECTED` against an `ACTIVE` enrolment — can never fire here. It is documented so a client that shares one punch-error path across both rails handles the code identically, and so the rail cannot later grow a photo without the contract already saying what happens.\n",
        "tags": [
          "attend",
          "punch"
        ],
        "x-token": "attend.punch.web",
        "x-realizes-features": [
          "ATT-F01",
          "ATT-F03"
        ],
        "x-screens": [
          "ATT-S23"
        ],
        "x-touches-entities": [
          "attend.attendance_records",
          "attend.geofence_attestations",
          "attend.face_attestations",
          "attend.out_of_zone_events",
          "org.work_locations",
          "org.geofences",
          "org.pay_groups",
          "org.legal_entities",
          "admin.tenant_config",
          "pay.employee_compensation",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.attendance_record.punched",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttendancePunchSyncRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Punch recorded against the day's attendance record, `source = WEB`, no exception raised.",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendancePunchSyncResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "The server refused the punch and wrote no attendance data. A `FACE_MISMATCH` is the server's `NO_MATCH` or `SPOOF_SUSPECTED` verdict against an ACTIVE enrolment; render its `detail` and let the employee retry with a fresh capture. A session refusal means an `IN` arrived while a stretch was already open, or an `OUT` had none to close. `punch_session_refusal` is present only for the latter, so a client can branch rather than infer the reason from HTTP status alone.\n",
            "content": {
              "application/problem+json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/PunchFaceMismatchProblem"
                    },
                    {
                      "$ref": "#/components/schemas/PunchSessionRefusalProblem"
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/attendance-punches/sync": {
      "post": {
        "operationId": "attend.punch.sync",
        "summary": "Sync one or more on-device geofence+face punch attestations (batch, offline-queue capable)",
        "description": "The **canonical idempotent-retry mutation** (ADR 0015) for clock in/out. Accepts a batch of client-attested punches — each carrying a `client_punch_id` (offline-queue idempotency key, db 05 §1.1), coordinates, the handset's provisional geofence/face claims, and (when online) a fresh punch-photo `storage_key`. Under ADR 0025 the server recomputes geofence containment and matches that photo against the employee's **HR-approved ACTIVE enrolment**, overriding the handset claims. No raw image is inlined and no biometric template ever crosses the API. A missing photo, PENDING/REJECTED enrolment, or matcher failure keeps the punch available for field workers but marks it exceptional with a named warning; it never manufactures a verified match. The operation reconciles each punch into the day's `attend.attendance_records` row in the same transaction as the attestation insert. Services the online single-punch path (batch of one, ATT-S01 clock-in / ATT-S02 clock-out) and the offline punch queue's reconnect replay (batch of N) identically. A replayed `client_punch_id` upserts the same attestation row and never double-counts. The first validated clock-in is accepted after the shift absence cutoff and outside shift hours. Work date and clock-in time remain server-resolved; the attendance policy snapshot marks lateness. Daily hours and approved overtime still cap worked time and govern automatic stop. This route does not create or consume late-entry HR requests; retained request records and their review endpoints are for existing historical cases. **G-14① (DECIDED 2026-07-02, design-docs/04):** the **attested** (ATT-S02) contract stands — every `OUT` punch requires the same geofence+face attestation payload as an `IN` punch; the prototype's instant clock-out is corrected to this flow (fsd 04 §1 coverage note 8).\n\n**Which day a punch lands on (#1174).** `work_date` is the civil day `geofence.captured_at` falls on **in the work location's timezone**, never a UTC slice of the client's instant — a pre-05:30 IST (pre-03:00 AST) punch used to be filed against yesterday, permanently, because `work_date` keys the day record, the team calendar, cycle close and every payroll input derived from attendance. The zone is resolved most specific first: the punch's own `geofence.work_location_id` → the employee's assigned `org.work_locations.timezone` → their pay group's `org.pay_groups.timezone` → the workspace's `admin.tenant_config['general.timezone']` → the legal entity's market default (`IN` → `Asia/Kolkata`, `KSA` → `Asia/Riyadh`, from the jurisdiction catalogue, never a market branch in code) → `UTC`, which is logged as a warning because nothing named a zone. A candidate the runtime cannot resolve as an IANA zone is skipped rather than accepted.\n\nClients must not re-derive the day from the device's own timezone: a handset outside the work location's zone will disagree with this ledger on exactly the punches that were broken before. Publishing the settled zone to clients is tracked separately.\n\n**Which fence a punch is measured against (#1317).** Most specific first: the `geofence_id` the client named → any active fence of the `geofence.work_location_id` the client named → **any active fence of the employee's assigned `people.employees.work_location_id`** → the tenant's nearest active fence. The third step is new: without it, setting somebody's work location changed what their punch was compared with only by accident of geography.\n\n**One exception, and it is a security boundary.** When the assigned location's `punch_enforcement` is `BLOCKING` and it actually has a fence, the two client-named steps are SKIPPED and only that location's own fences may decide — otherwise naming any other fenced site in the tenant would walk straight through the refusal. Under the default `ADVISORY` dial nothing changes: a roster day that names its own location or fence still wins, because it is a more specific statement than the standing assignment.\n\n**Known consequence under `BLOCKING`, stated so clients are not surprised by it.** A roster day's own `attend.shift_assignments.work_location_id` / `.geofence_id` reaches this path ONLY through the ids the client sends, and pinning discards those. So an employee rostered today at a different site from the one they are assigned to is judged against their ASSIGNED site's fence — they can be standing inside the site they were rostered at and still be refused. Admitting the roster's own fence (read server-side, so it cannot reopen the bypass) is tracked as issue #1438; until then, a tenant with cross-site rosters should leave those sites `ADVISORY`.\n**Where the punch is allowed to be made (#1317, ADR 0067).** The employee's PLACEMENT now governs this route: `people.employees.work_mode` and their assigned `org.work_locations` row (its `location_type` and its `punch_enforcement` dial). Two consequences are visible on this contract. (1) A `REMOTE` or `HYBRID` employee, and any employee assigned to a `REMOTE`-typed location, no longer raises a location exception for a punch made away from a site — the fence is measured and reported, and the response carries `PUNCH_LOCATION_NOT_SITE_BOUND`. A `FIELD` employee is likewise not judged against a site fence (their day is the beat plan, which this path does not yet evaluate — `PUNCH_BEAT_PLAN_NOT_EVALUATED`, design gap `G-86`). (2) When the assigned location's `punch_enforcement` is `BLOCKING`, a punch the SERVER itself placed outside THAT location's own fence is refused with `403` and the problem `type` `urn:groundit:problem:attend:punch-outside-assigned-fence`. A punch the server could NOT verify — no coordinates, an unreadable boundary, a tenant config gap — is still accepted, with `PUNCH_ENFORCEMENT_UNVERIFIED`: nothing on this path may strand somebody at a gate for a failure they did not cause (ADR 0025 §5 is preserved in full). The default dial is `ADVISORY`, under which this route behaves exactly as it did before #1317.\n\n**`403` refusals carry a `type`, not a new `code`.** `code` stays `SCOPE_DENIED` — the closed `ErrorCode` set is not widened (`03 §1`), because a new member is a breaking change for clients that switch on it. Branch on `type`; render `detail` verbatim, which is written as a complete sentence naming the location, the overshoot in metres and the remedy.\n\n**A face mismatch now REFUSES the punch (#1631) — `409`, `code: FACE_MISMATCH`, `type` `urn:groundit:problem:attend:punch-face-mismatch`.** When the employee has an `ACTIVE` `attend.face_enrollments` row AND the SERVER's own verdict is `NO_MATCH` or `SPOOF_SUSPECTED`, the punch is refused and **nothing is written**: no attendance record, no geofence/face attestation, no out-of-zone event, no outbox event. Before this, such a punch was accepted as a normal `PRESENT` day carrying `has_exception: true` and raised an out-of-zone review row — i.e. proxy attendance with an approval queue that read as a location problem.\n**Retry is permitted and is the expected next step.** Because nothing was written, the same `client_punch_id` may be re-sent with a fresh capture; clients should re-enable the punch control and render `detail` verbatim. The `detail` deliberately carries no distance or similarity score — returning one on demand would make this endpoint a face-matching oracle. The figures are audited and readable by HR on `GET /face-mismatch-attempts`.\n**Mobile clock-in requires server verification.** An `IN` without a server `MATCH` — no or pending enrolment, no photo, unusable photo, model-version change, matcher timeout, or other verifier error — is refused with `409`, `code: FACE_VERIFICATION_REQUIRED`, and `urn:groundit:problem:attend:punch-face-verification-required`; no attendance data is written. This is distinct from `FACE_MISMATCH`, which is positive server evidence of a different or replayed face. An `OUT` remains available when verification is unavailable so an open session can always close; an explicit server mismatch or replay remains refused. The deciding input is the server-reached `face_matched` boolean, never `face.result` on its own, so a patched client cannot assert a match or mismatch for itself.\n**Mobile anywhere exemption.** The server evaluates current employee policy at submission/sync time. `allow_mobile_attendance_anywhere = true` is an additional HR/Admin exemption: for mobile IN and OUT it suppresses location blocking and the location capture requirement. The client must not request location permission, read GPS, or run a geofence check in this state. It still submits the required `geofence` object with `result: UNKNOWN` and `captured_at`, omitting latitude, longitude, accuracy, and boundary ids. Face verification and the fresh punch photo remain required. Existing shared exemptions for `REMOTE`, `HYBRID` and `FIELD` work modes, and for a `REMOTE` work location, remain in force. The setting does not affect desktop punches, field visits, face refusals, or already-recorded history. The employee client obtains the fresh effective policy and named assignments from `GET /employees/me` and `GET /employees/me/attendance-locations`; the server remains authoritative, including queued offline punches. A policy read failure must fail closed and must not be treated as an anywhere exemption.\n**Muster v2 self-attendance (S2).** A MUSTER worker in a v2 tenant may punch only when `muster_self_attendance` is enabled. The service locks the tenant-day before reading the worker's allocation. IN needs a live PENDING allocation at a fenced site; an earlier supervisor mark wins. IN and OUT need a server-verified INSIDE at today's allocated site, even when the standing placement allows mobile attendance anywhere. The handset cannot select a different fence. The existing mandatory server face MATCH on IN remains in force. Refusals stay per punch. Legacy tenants retain their existing punch rules.\n",
        "tags": [
          "attend",
          "punch"
        ],
        "x-token": "attend.punch.sync",
        "x-realizes-features": [
          "ATT-F01",
          "ATT-F03"
        ],
        "x-screens": [
          "ATT-S01",
          "ATT-S02",
          "ATT-S03"
        ],
        "x-touches-entities": [
          "attend.attendance_records",
          "attend.geofence_attestations",
          "attend.face_attestations",
          "org.work_locations",
          "org.geofences",
          "org.pay_groups",
          "org.legal_entities",
          "admin.tenant_config",
          "pay.employee_compensation",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.attendance_record.punched",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttendancePunchSyncRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Every punch in the batch reconciled (per-item results — a batch may mix clean and exception outcomes).",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendancePunchSyncResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "The server refused the punch and wrote no attendance data. A `FACE_MISMATCH` is the server's `NO_MATCH` or `SPOOF_SUSPECTED` verdict against an ACTIVE enrolment; `FACE_VERIFICATION_REQUIRED` means an `IN` has no server `MATCH`. Render its `detail` and let the employee enrol or retry with a fresh capture. A session refusal means an `IN` arrived while a stretch was already open, or an `OUT` had none to close. `punch_session_refusal` is present only for the latter, so a client can branch rather than infer the reason from HTTP status alone.\n",
            "content": {
              "application/problem+json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/PunchFaceMismatchProblem"
                    },
                    {
                      "$ref": "#/components/schemas/PunchFaceVerificationRequiredProblem"
                    },
                    {
                      "$ref": "#/components/schemas/PunchSessionRefusalProblem"
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-records": {
      "get": {
        "operationId": "attend.attendance_record.list",
        "summary": "List my attendance records (monthly summary / daily log)",
        "description": "The employee's own reconciled daily attendance rows — month summary counts and day-by-day history (ATT-S04, ATT-S06). Each row includes date-addressed overtime request details, approved/processed hours, and pending requested hours. Pending hours are informational and are not payroll inputs. Overtime requests may have a null attendance_record_id; work date and employee identify the attendance day.\n",
        "tags": [
          "attend",
          "attendance_record"
        ],
        "x-token": "attend.attendance_record.list",
        "x-realizes-features": [
          "ATT-F01"
        ],
        "x-screens": [
          "ATT-S04",
          "ATT-S06"
        ],
        "x-touches-entities": [
          "attend.attendance_records",
          "attend.punch_sessions",
          "attend.geofence_attestations",
          "attend.overtime_requests"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "work_date[from]",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on `work_date`.",
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "work_date[to]",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on `work_date`.",
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/AttendanceStatus"
            }
          },
          {
            "name": "updated_at[from]",
            "in": "query",
            "required": false,
            "description": "Delta-sync affordance (GAP-38, #966): return only records whose `updated_at` is on or after this instant, so the PWA's reconnect pull can ask for what changed since `lastSyncAt` instead of refetching the whole month over a patchy connection.\n",
            "schema": {
              "$ref": "#/components/schemas/TimestampRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `work_date`, `-work_date`, `status`. Default `-work_date`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of the caller's own attendance records.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceRecordPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-records/{id}": {
      "get": {
        "operationId": "attend.attendance_record.get",
        "summary": "Get one day's attendance detail",
        "description": "One day's record — worked time, status, office location, and the OT roll-up for the day (ATT-S05). `worked_hours` is the CLOSED-day figure: since #1625 it is the SUM of the day's closed `attend.punch_sessions` minus the shift's `break_minutes` once (#1630), never the gross span between the first IN and the last OUT. *Overtime* is a derived roll-up of `attend.overtime_requests` for the employee and work date, not a stored column. Request details include state, claimed hours, approved hours and the policy multiplier. The legacy `overtime_hours_today` field remains an alias of `approved_overtime_hours`.\n\n**The \"no break/session column\" DB gap this operation used to declare is CLOSED (#1625).** A day is now a LIST of clock-in/out stretches (migration 0227); `clock_in_at`/`clock_out_at` on this row remain the day's FIRST IN and LAST OUT, and the individual stretches — with the break they imply — are read from `GET /attendance-records/{id}/sessions` below.\n",
        "tags": [
          "attend",
          "attendance_record"
        ],
        "x-token": "attend.attendance_record.get",
        "x-realizes-features": [
          "ATT-F01"
        ],
        "x-screens": [
          "ATT-S05"
        ],
        "x-touches-entities": [
          "attend.attendance_records",
          "org.work_locations",
          "attend.overtime_requests"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The attendance record with its office-location and OT-roll-up projection.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceRecordDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-records/{id}/mark-present": {
      "post": {
        "operationId": "attend.attendance_record.mark_present",
        "summary": "Mark one absent employee present",
        "description": "HR correction for an ABSENT record. Requires the current record version and a reason; the server preserves the existing punch provenance, marks the row regularized, clears the absence LOP marker, rejects regularized or stale records, and emits an audit event. #1878: it is a PRESENT day override (`PUT /attendance-days/{employeeId}/{workDate}/override`) under this token, so a CL an automatic absence conversion spent is credited back.\n",
        "tags": [
          "attend",
          "attendance_record"
        ],
        "x-token": "attend.attendance_record.mark_present",
        "x-rls-scope": "tenant",
        "x-idempotent": false,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "version",
                  "reason"
                ],
                "properties": {
                  "version": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated attendance record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceRecord"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Version conflict or invalid state"
          }
        }
      }
    },
    "/attendance-days/{employeeId}/{workDate}/override": {
      "put": {
        "operationId": "attend.attendance_record.override",
        "summary": "Override one employee-day (HR's final decision)",
        "description": "#1878. HR / owner decide a day: `PRESENT`, `HALF_DAY`, `ABSENT`, `CASUAL_LEAVE` or `LOP`, with a reason (audited, `attend.attendance_day.overridden`). A missing day is created. The day is marked regularized and FINAL — the nightly absence reconcile never re-converts it. `CASUAL_LEAVE` spends 1 day of the configured absence leave type (`422` at `/outcome` when there is no balance or the policy's monthly limit is used); `LOP` and `PRESENT` credit back any CL an automatic conversion spent; `ABSENT` / `HALF_DAY` apply the CL-then-LOP rule once (an ABSENT day that spent a full CL reads `ON_LEAVE`). A pending absent appeal for the day is `SUPERSEDED`. Daily-wage and contractor-team workers may only be marked `PRESENT`, `HALF_DAY` or `ABSENT` (`422`). Refused (`409`) when the employee's attendance cycle is finalized or the month's payroll is past DRAFT.\n",
        "tags": [
          "attend",
          "attendance_record"
        ],
        "x-token": "attend.attendance_record.override",
        "x-realizes-features": [
          "ATT-F03",
          "ATT-F06"
        ],
        "x-screens": [
          "ATT-S12"
        ],
        "x-touches-entities": [
          "attend.attendance_records",
          "attend.absence_resolutions",
          "attend.late_entry_requests",
          "leave.leave_applications",
          "leave.leave_balances",
          "attend.leave_day_marks",
          "audit.audit_log"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.attendance_day.overridden",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "employeeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "workDate",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "outcome",
                  "reason"
                ],
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "PRESENT",
                      "HALF_DAY",
                      "ABSENT",
                      "CASUAL_LEAVE",
                      "LOP"
                    ]
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  },
                  "version": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Required when the day exists — the record version you were shown."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The overridden day, with what the absence rule spent.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/AttendanceRecord"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "override_outcome": {
                          "type": "string",
                          "enum": [
                            "PRESENT",
                            "HALF_DAY",
                            "ABSENT",
                            "CASUAL_LEAVE",
                            "LOP"
                          ]
                        },
                        "override_reason": {
                          "type": "string"
                        },
                        "overridden_by": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "overridden_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "absence_resolution": {
                          "type": "object",
                          "properties": {
                            "action": {
                              "type": "string",
                              "enum": [
                                "SKIPPED",
                                "NOOP",
                                "APPLIED"
                              ]
                            },
                            "cl_days": {
                              "type": "number"
                            },
                            "lop_days": {
                              "type": "number"
                            },
                            "leave_application_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "reversed_application_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/attendance-records/{id}/sessions": {
      "get": {
        "operationId": "attend.attendance_record.get_sessions",
        "summary": "List one day's punch sessions (the employee's own time log)",
        "description": "The PUNCH-LEVEL TIME LOG an employee could not see anywhere before #1625 — the individual clock-in/clock-out stretches of one attendance day, oldest first. `out_at: null` is the OPEN stretch; there is at most one per employee-day (`punch_sessions_open_day_key`), so a client never has to decide which of several open rows it meant.\n\n**Why the day row could not answer this.** `attend.attendance_records` carries exactly one `clock_in_at`/`clock_out_at` pair, so an employee who clocked out for lunch and back in had the second IN folded into the existing window and lost as an event — the punch left no trace and the break was counted as worked time. `attend.punch_sessions` (migration 0227) is the record of what actually happened; the day row stays the ROLL-UP (first IN, last OUT, summed hours) that payroll and the attendance cycle read.\n\n**No new permission token.** It declares `attend.attendance_record.get` — the token that already governs the day these sessions belong to. The sessions ARE that day's detail, an employee who may read the day may read how they worked it, and minting a token to say the same thing would force an RBAC re-seed on every tenant to grant a right they already hold.\n\n`as_of` rides beside the rows because an OPEN session's `worked_minutes` is measured against it — without the stamp a client cannot tell a figure seconds old from one minutes old. An id belonging to another employee answers `404`, never an empty list, which would confirm the record exists.\n",
        "tags": [
          "attend",
          "attendance_record"
        ],
        "x-token": "attend.attendance_record.get",
        "x-realizes-features": [
          "ATT-F01"
        ],
        "x-screens": [
          "ATT-S05",
          "ATT-S23"
        ],
        "x-touches-entities": [
          "attend.punch_sessions",
          "attend.attendance_records"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The day's punch sessions, oldest first, and the instant the open one was measured at.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PunchSessionPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-punches/{id}/photo": {
      "get": {
        "operationId": "attend.attendance_punch.reveal_photo",
        "summary": "Reveal the photo taken at one punch (audited, short-lived URL)",
        "description": "The photo captured at one face punch, for a reviewer reading a day in the records ledger (list side panel, month-grid day popup) or the Today punch feed. `id` is the punch's `attend.face_attestations` row, exposed on every session of `GET /attendance-records/admin` as `in_face_attestation_id` / `out_face_attestation_id`, with `in_has_photo` / `out_has_photo` saying whether a photo is still kept.\n\n**No new permission token.** It declares `attend.attendance_record.list_admin`, the token that already shows this reviewer the day, at the same tenant RLS scope. As a cross-employee read of a face photo, every successful open writes `audit.access_log` (`purpose = PUNCH_PHOTO_REVEAL`) before the 15-minute URL is minted. The object key is never returned.\n\n**Retention.** Punch photos are kept for `PUNCH_PHOTO_RETENTION_DAYS` (default 90) and then deleted by the jobs tier's `attend.punch_photo.retention_sweep`. A punch with no kept photo (web rail, past retention, or captured before keys were recorded) answers `404`.\n",
        "tags": [
          "attend",
          "attendance_record"
        ],
        "x-token": "attend.attendance_record.list_admin",
        "x-realizes-features": [
          "ATT-F01"
        ],
        "x-screens": [
          "ATT-S12",
          "ATT-S17"
        ],
        "x-touches-entities": [
          "attend.face_attestations",
          "audit.access_log"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The punch's facts and a short-lived photo URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "employee_id",
                    "punch_type",
                    "captured_at",
                    "photo"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "employee_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "punch_type": {
                      "type": "string",
                      "enum": [
                        "IN",
                        "OUT"
                      ]
                    },
                    "face_result": {
                      "type": "string",
                      "nullable": true
                    },
                    "captured_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "photo": {
                      "type": "object",
                      "required": [
                        "url",
                        "expires_at"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-records/admin": {
      "get": {
        "operationId": "attend.attendance_record.list_admin",
        "summary": "Search the tenant-wide attendance ledger (HR admin)",
        "description": "HR's searchable attendance ledger across the workforce (ATT-S17), with the source, regularization flag, and date-addressed overtime facts surfaced per row. Widened `x-rls-scope: tenant` relative to the self-scoped `attend.attendance_record.list` — same table, the HR/admin view.\n",
        "tags": [
          "attend",
          "attendance_record"
        ],
        "x-token": "attend.attendance_record.list_admin",
        "x-realizes-features": [
          "ATT-F01"
        ],
        "x-screens": [
          "ATT-S17"
        ],
        "x-touches-entities": [
          "attend.attendance_records",
          "attend.punch_sessions",
          "attend.geofence_attestations",
          "attend.overtime_requests"
        ],
        "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,
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "work_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "work_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/AttendanceStatus"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/AttendanceSource"
            }
          },
          {
            "name": "has_exception",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "updated_at[from]",
            "in": "query",
            "required": false,
            "description": "Delta-sync affordance (GAP-38, #966) — same predicate as the self-scoped list. Return only records whose `updated_at` is on or after this instant.\n",
            "schema": {
              "$ref": "#/components/schemas/TimestampRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `work_date`, `employee_id`, `status`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant-wide page of attendance records.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceRecordPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-records/admin/exports": {
      "post": {
        "operationId": "attend.attendance_record.export",
        "summary": "Export the tenant-wide attendance ledger",
        "description": "Generates a capped, filtered CSV export of the attendance ledger (ATT-S17 **Export**).",
        "tags": [
          "attend",
          "attendance_record"
        ],
        "x-token": "attend.attendance_record.export",
        "x-realizes-features": [
          "ATT-F01"
        ],
        "x-screens": [
          "ATT-S17"
        ],
        "x-touches-entities": [
          "attend.attendance_records"
        ],
        "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,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttendanceRecordExportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Presigned download handle for the generated export.",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileDownloadRef"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/attendance-team-summary": {
      "get": {
        "operationId": "attend.attendance_team_summary.list",
        "summary": "Manager's team attendance roll-up for a day",
        "description": "The manager's daily pulse — present/absent/late/on-leave counts and member rows (ATT-S12 §M *Time › Team* + §W dashboard). Direct reports are resolved through the `people` module's ID-only read seam, then the `attend` module computes this request's bounded daily view. Team scope is resolved through XC-F04.\n**Revised 2026-08-15 (#986, #960).** The \"deliberately performs no cross-schema join\" posture this row previously stated no longer holds: the team-pulse screen cannot render a member row with no name, so `attend.attendance_team_summary.list` now LEFT JOINs `people.employees` in the SAME query to attach `employee_name`, rather than adding a second round trip the PWA's offline cache cannot afford. This does not widen what the operation can see — `people.employees` carries the identical TEAM-scope RESTRICTIVE ownership policy (`manager_id = app.current_employee_id()`, migration 0029) that `attend.attendance_records` already carries (migration 0087), so the join can only ever attach a name to a row this operation already returned.\n**Revised again 2026-08-15 (#986 follow-up, a P2 PWA-Supervisor contract review).** `counts.late` was a permanent, lying zero — nothing ever populated it, so the team-pulse \"Late\" tile could never render a real number. It is now wired from the same `late_minutes` column the payroll cycle already trusts, computed in the same query as `present`/`absent`/ `on_leave` (no extra round trip). See `AttendanceTeamSummaryPage.counts` below for the exact definition and why `late` can overlap `present`.\n",
        "tags": [
          "attend",
          "attendance_team_summary"
        ],
        "x-token": "attend.attendance_team_summary.list",
        "x-realizes-features": [
          "ATT-F06"
        ],
        "x-screens": [
          "ATT-S12"
        ],
        "x-touches-entities": [
          "attend.attendance_records",
          "attend.shift_assignments",
          "people.employees"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "work_date",
            "in": "query",
            "required": false,
            "description": "Defaults to today (employee-local calendar date).",
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/AttendanceStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `status`, `employee_id`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Team pulse counts plus a page of member rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceTeamSummaryPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/out-of-zone-events": {
      "post": {
        "operationId": "attend.out_of_zone_event.create",
        "summary": "Submit an out-of-zone / missed-punch attestation",
        "description": "A reasoned out-of-zone request — projected to the unified approval inbox and routed to the configured approver as an exception (ATT-S07, XC-F12). When clock-in is blocked before a punch exists, the client may submit latitude and longitude directly. A legacy request may instead reference an existing punch/day context. `event_type` is server-assigned.\n",
        "tags": [
          "attend",
          "out_of_zone_event"
        ],
        "x-token": "attend.out_of_zone_event.create",
        "x-realizes-features": [
          "ATT-F03"
        ],
        "x-screens": [
          "ATT-S07"
        ],
        "x-touches-entities": [
          "attend.out_of_zone_events",
          "attend.geofence_attestations",
          "people.employees",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.out_of_zone_event.submitted",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OutOfZoneEventCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Exception created, `PENDING` review, routed to the unified approvals inbox (XC-F12).",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutOfZoneEvent"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "attend.out_of_zone_event.list",
        "summary": "Manager's out-of-zone / exception queue",
        "description": "The manager's queue of out-of-zone / missed / late / early exceptions to decide (ATT-S13). Newest first by `created_at` and id. Follow `page.next_cursor` with `page[after]` to locate an event beyond the first page without losing the captured location evidence.\n",
        "tags": [
          "attend",
          "out_of_zone_event"
        ],
        "x-token": "attend.out_of_zone_event.list",
        "x-realizes-features": [
          "ATT-F03"
        ],
        "x-screens": [
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.out_of_zone_events"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/OutOfZoneStatus"
            }
          },
          {
            "name": "event_type",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/OutOfZoneType"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "name": "legal_entity_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this legal entity (branch). Combines with `department_id` and `org_unit_id`, and REPLACES the caller's direct-report bound rather than intersecting with it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "department_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this department. Combines with `legal_entity_id` and `org_unit_id`. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "org_unit_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this org unit AND its sub-units — the unit's org-tree closure (`app.org_unit_closure`, migration 0162), so a department's parent unit includes the teams beneath it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "`ALL` — every ACTIVE employee in the tenant, with no axis narrowing it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "type": "string",
              "enum": [
                "ALL"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of out-of-zone exceptions in the manager's scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutOfZoneEventPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/face-mismatch-attempts": {
      "get": {
        "operationId": "attend.face_mismatch.list",
        "summary": "Refused face-mismatch punch attempts (identity exception queue)",
        "description": "Every punch refused with `409 FACE_MISMATCH` (#1631) — the attempts where the server compared the punch photo against the employee's own `ACTIVE` `attend.face_enrollments` reference and it was not them, or refused the photo as a replay. This is the queue that replaces the out-of-zone rows those attempts used to raise: a proxy-attendance attempt filed as a location exception reads as a GPS problem and gets rubber-stamped, which is the defect #1631 closes.\n**These rows are EVIDENCE, not requests.** Nothing was written for a refused punch — no attendance record, no attestation, no out-of-zone event — so there is nothing to approve or reject and no `version`/`If-Match` on this rail. What the queue is for is the pattern: the same employee refused eleven times in a week is a shared handset, and the distance/threshold pair beside each row is what tells a near-miss apart from a different person.\nThe source is the tenant's own `audit.audit_log` (action `attend.punch.face_mismatch_refused`), which is append-only and hash-chained. `distance` and `threshold` are shown to a REVIEWER here and are deliberately absent from the `409` the punching client receives — returning a score on demand would make the punch endpoint a face-matching oracle.\nBounded exactly like the other approval queues: absent scope ⇒ the caller's own direct reports; a named org scope widens it only as far as the caller's grant allows.\n",
        "tags": [
          "attend",
          "face_mismatch"
        ],
        "x-token": "attend.face_mismatch.list",
        "x-realizes-features": [
          "ATT-F03"
        ],
        "x-screens": [
          "ATT-S13"
        ],
        "x-touches-entities": [
          "audit.audit_log",
          "people.employees"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "work_date[from]",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on the refused punch's `work_date`.",
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "work_date[to]",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on the refused punch's `work_date`.",
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "name": "legal_entity_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this legal entity (branch). Combines with `department_id` and `org_unit_id`, and REPLACES the caller's direct-report bound rather than intersecting with it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "department_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this department. Combines with `legal_entity_id` and `org_unit_id`. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "org_unit_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this org unit AND its sub-units — the unit's org-tree closure (`app.org_unit_closure`, migration 0162), so a department's parent unit includes the teams beneath it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "`ALL` — every ACTIVE employee in the tenant, with no axis narrowing it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "type": "string",
              "enum": [
                "ALL"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of refused face-mismatch attempts in the caller's scope, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceMismatchAttemptPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/out-of-zone-events/{id}": {
      "get": {
        "operationId": "attend.out_of_zone_event.get",
        "summary": "Status of one out-of-zone / exception attestation I submitted",
        "description": "The submitter's read-back of their OWN attestation, so a mobile client can render the Pending / Approved / Rejected screen and refresh it on demand (ATT-S07). Self-scope by construction: the row is matched on `employee_id = app.current_employee_id()`, so this is never a second door onto the manager queue that `GET /out-of-zone-events` already serves. `reviewed_by_name` is projected alongside `reviewed_by` because a decided request that can only name its reviewer by uuid is not renderable.\n",
        "tags": [
          "attend",
          "out_of_zone_event"
        ],
        "x-token": "attend.out_of_zone_event.get",
        "x-realizes-features": [
          "ATT-F03"
        ],
        "x-screens": [
          "ATT-S07"
        ],
        "x-touches-entities": [
          "attend.out_of_zone_events",
          "people.employees"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's own out-of-zone exception, with its current review state.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutOfZoneEvent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/out-of-zone-events/{id}/approve": {
      "post": {
        "operationId": "attend.out_of_zone_event.approve",
        "summary": "Approve an out-of-zone / exception attestation",
        "description": "The deviation is accepted; the punch/day stands. Re-derives `attendance_records.has_exception` from the day's remaining unresolved exceptions (ATT-S13,",
        "tags": [
          "attend",
          "out_of_zone_event"
        ],
        "x-token": "attend.out_of_zone_event.approve",
        "x-realizes-features": [
          "ATT-F03"
        ],
        "x-screens": [
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.out_of_zone_events",
          "attend.attendance_records"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.out_of_zone_event.approved",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutOfZoneEvent"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/out-of-zone-events/{id}/reject": {
      "post": {
        "operationId": "attend.out_of_zone_event.reject",
        "summary": "Reject an out-of-zone / exception attestation",
        "description": "The deviation is not accepted; may prompt the employee toward a regularization (ATT-S15).",
        "tags": [
          "attend",
          "out_of_zone_event"
        ],
        "x-token": "attend.out_of_zone_event.reject",
        "x-realizes-features": [
          "ATT-F03"
        ],
        "x-screens": [
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.out_of_zone_events",
          "attend.attendance_records"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.out_of_zone_event.rejected",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutOfZoneEvent"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/overtime-requests": {
      "post": {
        "operationId": "attend.overtime_request.create",
        "summary": "Raise an overtime request",
        "description": "Overtime claimed against a worked day, projected to the unified approval inbox and routed to the manager for approval (ATT-S08, XC-F12). **DB gap (fsd 04 §1.4):** there is no `ot_type`/`project_link` column; `OT Type` is resolved server-side from policy and `Project Link` folds into `reason` per the FSD note.\n",
        "tags": [
          "attend",
          "overtime_request"
        ],
        "x-token": "attend.overtime_request.create",
        "x-realizes-features": [
          "ATT-F04"
        ],
        "x-screens": [
          "ATT-S08"
        ],
        "x-touches-entities": [
          "attend.overtime_requests",
          "attend.attendance_records",
          "people.employees",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.overtime_request.submitted",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OvertimeRequestCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Overtime request created, `PENDING_APPROVAL`, and projected to the unified approval inbox.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OvertimeRequest"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "attend.overtime_request.list",
        "summary": "My overtime — month summary",
        "description": "The employee's month of overtime, approved vs pending (ATT-S09).",
        "tags": [
          "attend",
          "overtime_request"
        ],
        "x-token": "attend.overtime_request.list",
        "x-realizes-features": [
          "ATT-F04"
        ],
        "x-screens": [
          "ATT-S09"
        ],
        "x-touches-entities": [
          "attend.overtime_requests"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/OvertimeStatus"
            }
          },
          {
            "name": "work_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "work_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `work_date`, `-work_date`, `status`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of the caller's own overtime requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OvertimeRequestPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/overtime-requests/{id}": {
      "get": {
        "operationId": "attend.overtime_request.get",
        "summary": "Get one overtime request",
        "description": "Row → request detail drill-down (ATT-S09).",
        "tags": [
          "attend",
          "overtime_request"
        ],
        "x-token": "attend.overtime_request.get",
        "x-realizes-features": [
          "ATT-F04"
        ],
        "x-screens": [
          "ATT-S09"
        ],
        "x-touches-entities": [
          "attend.overtime_requests"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The overtime request.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OvertimeRequest"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "attend.overtime_request.update",
        "summary": "Edit my own still-pending overtime request",
        "description": "Edit **in place** (ESS gate G1, `GAP-ESS-24`) — the overtime half of the same rule that lets a pending regularization be corrected without cancel-and-resubmit. Confined to the author's own row while it is still `PENDING_APPROVAL`; the version bump is re-projected to `xc.approval_inbox` so the approver's `If-Match` evidence stays current.\n",
        "tags": [
          "attend",
          "overtime_request"
        ],
        "x-token": "attend.overtime_request.update",
        "x-realizes-features": [
          "ATT-F04"
        ],
        "x-screens": [
          "ATT-S25"
        ],
        "x-touches-entities": [
          "attend.overtime_requests",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.overtime_request.updated",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OvertimeRequestUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated, still `PENDING_APPROVAL`, and re-projected to the approval inbox.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OvertimeRequest"
                }
              }
            }
          },
          "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"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/overtime-requests/{id}/withdraw": {
      "post": {
        "operationId": "attend.overtime_request.withdraw",
        "summary": "Withdraw my own still-pending overtime request",
        "description": "ESS gate G1 (`GAP-ESS-24`): withdraw in place. `PENDING_APPROVAL` → `CANCELLED`, a value the status enum already carried.\n",
        "tags": [
          "attend",
          "overtime_request"
        ],
        "x-token": "attend.overtime_request.withdraw",
        "x-realizes-features": [
          "ATT-F04"
        ],
        "x-screens": [
          "ATT-S25"
        ],
        "x-touches-entities": [
          "attend.overtime_requests"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.overtime_request.withdrawn",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawn — the request is `CANCELLED` and leaves the approver's queue.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OvertimeRequest"
                }
              }
            }
          },
          "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"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/overtime-requests/pending-approval": {
      "get": {
        "operationId": "attend.overtime_request.list_for_approval",
        "summary": "Manager's overtime approval queue",
        "description": "Overtime claims awaiting decision, hours-to-grant capped at claimed hours (ATT-S14).",
        "tags": [
          "attend",
          "overtime_request"
        ],
        "x-token": "attend.overtime_request.list_for_approval",
        "x-realizes-features": [
          "ATT-F04"
        ],
        "x-screens": [
          "ATT-S14"
        ],
        "x-touches-entities": [
          "attend.overtime_requests"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/OvertimeStatus"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "pay_period",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `work_date`, `status`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "legal_entity_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this legal entity (branch). Combines with `department_id` and `org_unit_id`, and REPLACES the caller's direct-report bound rather than intersecting with it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "department_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this department. Combines with `legal_entity_id` and `org_unit_id`. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "org_unit_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this org unit AND its sub-units — the unit's org-tree closure (`app.org_unit_closure`, migration 0162), so a department's parent unit includes the teams beneath it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "`ALL` — every ACTIVE employee in the tenant, with no axis narrowing it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "type": "string",
              "enum": [
                "ALL"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of overtime requests in the manager's approval scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OvertimeRequestPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/overtime-requests/{id}/approve": {
      "post": {
        "operationId": "attend.overtime_request.approve",
        "summary": "Approve (or part-approve) an overtime request",
        "description": "Sets `approved_hours` (≤ claimed `ot_hours`; omitted = the claimed hours); maker ≠ checker enforced (`submitted_by <> approved_by`). Approved rows feed payroll by event (PAY-F04) — never a cross-schema write (ATT-S14).\n",
        "tags": [
          "attend",
          "overtime_request"
        ],
        "x-token": "attend.overtime_request.approve",
        "x-realizes-features": [
          "ATT-F04"
        ],
        "x-screens": [
          "ATT-S14"
        ],
        "x-touches-entities": [
          "attend.overtime_requests"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.overtime_request.approved",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OvertimeApprovalInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved (or part-approved).",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OvertimeRequest"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/overtime-requests/{id}/reject": {
      "post": {
        "operationId": "attend.overtime_request.reject",
        "summary": "Reject an overtime request",
        "description": "Declines the claim; maker ≠ checker enforced (ATT-S14).",
        "tags": [
          "attend",
          "overtime_request"
        ],
        "x-token": "attend.overtime_request.reject",
        "x-realizes-features": [
          "ATT-F04"
        ],
        "x-screens": [
          "ATT-S14"
        ],
        "x-touches-entities": [
          "attend.overtime_requests"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.overtime_request.rejected",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OvertimeRequest"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/regularizations": {
      "post": {
        "operationId": "attend.regularization.create",
        "summary": "Submit an attendance regularization request",
        "description": "Correct a missed/mistaken punch with a reason (ATT-S10), projected to the unified approval inbox (XC-F12). The service freezes the pre-correction `original_snapshot` on submit; **both original and corrected values are retained** on approval (XC-F06) — no silent edits.\n#1634 — the request names a `work_date`; a day with no attendance record yet is CREATED here, as the CALENDAR-EQUIVALENT `SYSTEM` row the nightly materializer would have written, so it can be corrected. `attendance_record_id` stays accepted for backward compatibility. A future `work_date` is a 422 at `/work_date`.\n",
        "tags": [
          "attend",
          "regularization"
        ],
        "x-token": "attend.regularization.create",
        "x-realizes-features": [
          "ATT-F05"
        ],
        "x-screens": [
          "ATT-S10"
        ],
        "x-touches-entities": [
          "attend.regularizations",
          "attend.attendance_records",
          "people.employees",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.regularization.submitted",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegularizationCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Regularization request created, `PENDING_APPROVAL`, and projected to the unified approval inbox.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Regularization"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "attend.regularization.list",
        "summary": "Manager's regularization approval queue",
        "description": "Before (`original_snapshot`) vs after (requested) — approve/reject attendance corrections (ATT-S15).",
        "tags": [
          "attend",
          "regularization"
        ],
        "x-token": "attend.regularization.list",
        "x-realizes-features": [
          "ATT-F05"
        ],
        "x-screens": [
          "ATT-S15"
        ],
        "x-touches-entities": [
          "attend.regularizations"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/RegularizationStatus"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `created_at`, `-created_at`, `status`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "legal_entity_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this legal entity (branch). Combines with `department_id` and `org_unit_id`, and REPLACES the caller's direct-report bound rather than intersecting with it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "department_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this department. Combines with `legal_entity_id` and `org_unit_id`. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "org_unit_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this org unit AND its sub-units — the unit's org-tree closure (`app.org_unit_closure`, migration 0162), so a department's parent unit includes the teams beneath it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "`ALL` — every ACTIVE employee in the tenant, with no axis narrowing it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "type": "string",
              "enum": [
                "ALL"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of regularization requests in the manager's scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegularizationPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/regularizations/me": {
      "get": {
        "operationId": "attend.regularization.list_me",
        "summary": "My own regularization requests",
        "description": "The self-scoped read of this rail (closes `GAP-ESS-20`-series item `GAP-ESS-23`). Until this landed the only list was the manager's TEAM queue, so an employee could raise a correction and never see it again — the row was visible to its approver and invisible to its author. Named `_me` after this module's own precedent (`attend.face_enrollment.get_me`); the TEAM list is untouched, because widening that token would have narrowed the manager's queue to their own rows.\n",
        "tags": [
          "attend",
          "regularization"
        ],
        "x-token": "attend.regularization.list_me",
        "x-realizes-features": [
          "ATT-F05"
        ],
        "x-screens": [
          "ATT-S25"
        ],
        "x-touches-entities": [
          "attend.regularizations"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/RegularizationStatus"
            }
          },
          {
            "name": "work_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "work_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of the caller's own regularization requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegularizationPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/regularizations/{id}": {
      "patch": {
        "operationId": "attend.regularization.update",
        "summary": "Edit my own still-pending regularization",
        "description": "Edit **in place**, the ESS G1 rule: a pending request must be correctable without the cancel-and-resubmit dance (`GAP-ESS-24`). Confined to the author's own row while it is still `PENDING_APPROVAL` — once an approver has acted the row is evidence, not a draft. The version bump is re-projected to `xc.approval_inbox`, so the approver's `If-Match` evidence stays current and an employee edit cannot silently break their manager's ability to decide.\n",
        "tags": [
          "attend",
          "regularization"
        ],
        "x-token": "attend.regularization.update",
        "x-realizes-features": [
          "ATT-F05"
        ],
        "x-screens": [
          "ATT-S25"
        ],
        "x-touches-entities": [
          "attend.regularizations",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.regularization.updated",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegularizationUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated, still `PENDING_APPROVAL`, and re-projected to the approval inbox.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Regularization"
                }
              }
            }
          },
          "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"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/regularizations/{id}/withdraw": {
      "post": {
        "operationId": "attend.regularization.withdraw",
        "summary": "Withdraw my own still-pending regularization",
        "description": "The other half of ESS gate G1 (`GAP-ESS-24`): a pending request is withdrawable in place. Transitions `PENDING_APPROVAL` → `CANCELLED`, a value the status enum already carried — what was missing was an employee-side door to it.\n",
        "tags": [
          "attend",
          "regularization"
        ],
        "x-token": "attend.regularization.withdraw",
        "x-realizes-features": [
          "ATT-F05"
        ],
        "x-screens": [
          "ATT-S25"
        ],
        "x-touches-entities": [
          "attend.regularizations"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.regularization.withdrawn",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawn — the request is `CANCELLED` and leaves the approver's queue.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Regularization"
                }
              }
            }
          },
          "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"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/regularizations/{id}/approve": {
      "post": {
        "operationId": "attend.regularization.approve",
        "summary": "Approve an attendance regularization",
        "description": "Writes the corrected values to `attendance_records` (`is_regularized = true`) in one transaction with `applied_at`; original + corrected both retained (ATT-S15).\n",
        "tags": [
          "attend",
          "regularization"
        ],
        "x-token": "attend.regularization.approve",
        "x-realizes-features": [
          "ATT-F05"
        ],
        "x-screens": [
          "ATT-S15"
        ],
        "x-touches-entities": [
          "attend.regularizations",
          "attend.attendance_records"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.regularization.approved",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved and applied to the attendance record.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Regularization"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/regularizations/{id}/reject": {
      "post": {
        "operationId": "attend.regularization.reject",
        "summary": "Reject an attendance regularization",
        "description": "Declines the correction; the original record stands (ATT-S15).",
        "tags": [
          "attend",
          "regularization"
        ],
        "x-token": "attend.regularization.reject",
        "x-realizes-features": [
          "ATT-F05"
        ],
        "x-screens": [
          "ATT-S15"
        ],
        "x-touches-entities": [
          "attend.regularizations"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.regularization.rejected",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Regularization"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/schedules": {
      "get": {
        "operationId": "attend.schedule.list",
        "summary": "List schedules (roster build)",
        "description": "Per-employee / per-team schedules the roster build view manages (ATT-S16); enforces, never redefines, `org` roster config (ORG-F04).",
        "tags": [
          "attend",
          "schedule"
        ],
        "x-token": "attend.schedule.list",
        "x-realizes-features": [
          "ATT-F02"
        ],
        "x-screens": [
          "ATT-S16"
        ],
        "x-touches-entities": [
          "attend.schedules",
          "org.rosters",
          "org.work_locations"
        ],
        "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,
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "roster_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ScheduleStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `effective_from`, `status`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of schedules.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SchedulePage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "attend.schedule.create",
        "summary": "Create a schedule",
        "description": "Builds a per-employee or team-template schedule window from `org` roster config (ATT-S16, ORG-F04).",
        "tags": [
          "attend",
          "schedule"
        ],
        "x-token": "attend.schedule.create",
        "x-realizes-features": [
          "ATT-F02"
        ],
        "x-screens": [
          "ATT-S16"
        ],
        "x-touches-entities": [
          "attend.schedules",
          "org.rosters",
          "org.work_locations"
        ],
        "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,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Schedule created.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/schedules/{id}": {
      "patch": {
        "operationId": "attend.schedule.update",
        "summary": "Update a schedule",
        "description": "Edits the window, weekly pattern, or lifecycle status of a schedule (ATT-S16).",
        "tags": [
          "attend",
          "schedule"
        ],
        "x-token": "attend.schedule.update",
        "x-realizes-features": [
          "ATT-F02"
        ],
        "x-screens": [
          "ATT-S16"
        ],
        "x-touches-entities": [
          "attend.schedules"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated schedule.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/shift-allocations": {
      "post": {
        "operationId": "attend.shift_assignment.allocate",
        "summary": "Allocate a recurring shift to employees",
        "description": "Creates an active attendance schedule and concrete weekday shift assignments atomically. An omitted end date is recurring; the attendance materializer expands only upcoming local weekdays, preserving weekends and manual assignments.",
        "tags": [
          "attend",
          "shift-assignment"
        ],
        "x-token": "attend.shift_assignment.create",
        "x-realizes-features": [
          "ATT-F02"
        ],
        "x-screens": [
          "ATT-S16"
        ],
        "x-touches-entities": [
          "attend.schedules",
          "attend.shift_assignments",
          "org.shifts"
        ],
        "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,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "employee_ids",
                  "shift_id",
                  "effective_from",
                  "working_days"
                ],
                "properties": {
                  "employee_ids": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "$ref": "#/components/schemas/UuidRef"
                    }
                  },
                  "shift_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "roster_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "legal_entity_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "roster_code": {
                    "type": "string"
                  },
                  "rotation": {
                    "type": "string",
                    "enum": [
                      "FIXED",
                      "WEEKLY",
                      "CONTINENTAL",
                      "CUSTOM"
                    ]
                  },
                  "week_off_pattern": {
                    "type": "object",
                    "nullable": true
                  },
                  "work_location_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "location_id": {
                    "$ref": "#/components/schemas/UuidRef",
                    "description": "Alias accepted for work_location_id."
                  },
                  "geofence_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "effective_from": {
                    "type": "string",
                    "format": "date"
                  },
                  "effective_to": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "working_days": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string"
                    }
                  },
                  "weekly_off_days": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "schedule_name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Allocation created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "roster_id",
                    "employee_ids",
                    "schedule_ids",
                    "schedule_count",
                    "assignment_count"
                  ],
                  "properties": {
                    "roster_id": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/UuidRef"
                        }
                      ],
                      "nullable": true
                    },
                    "employee_ids": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UuidRef"
                      }
                    },
                    "schedule_ids": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UuidRef"
                      }
                    },
                    "schedule_count": {
                      "type": "integer"
                    },
                    "assignment_count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/shift-allocations/{id}": {
      "get": {
        "operationId": "attend.shift_assignment.allocation_get",
        "summary": "Read a roster allocation and its linked schedules",
        "tags": [
          "attend",
          "shift-assignment"
        ],
        "x-token": "attend.shift_assignment.list_admin",
        "x-rls-scope": "tenant",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Roster, all linked employees and schedules, and materialized assignments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "roster",
                    "employees",
                    "assignments"
                  ],
                  "properties": {
                    "roster": {
                      "type": "object"
                    },
                    "employees": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "assignments": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "attend.shift_assignment.allocation_update",
        "summary": "Change future roster enforcement",
        "description": "Updates linked schedules and only future unpunched assignments. Punched and approval-linked history is retained unchanged.",
        "tags": [
          "attend",
          "shift-assignment"
        ],
        "x-token": "attend.shift_assignment.update",
        "x-idempotent": true,
        "x-rls-scope": "tenant",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "roster_code": {
                    "type": "string"
                  },
                  "shift_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "work_location_id": {
                    "$ref": "#/components/schemas/UuidRef",
                    "nullable": true
                  },
                  "location_id": {
                    "$ref": "#/components/schemas/UuidRef",
                    "nullable": true
                  },
                  "geofence_id": {
                    "$ref": "#/components/schemas/UuidRef",
                    "nullable": true
                  },
                  "effective_to": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "working_days": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "weekly_off_days": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "schedule_name": {
                    "type": "string"
                  },
                  "rotation": {
                    "type": "string",
                    "enum": [
                      "FIXED",
                      "WEEKLY",
                      "CONTINENTAL",
                      "CUSTOM"
                    ]
                  },
                  "week_off_pattern": {
                    "type": "object",
                    "nullable": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "ACTIVE",
                      "INACTIVE",
                      "DRAFT"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated linked roster allocation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "roster",
                    "employees",
                    "assignments"
                  ]
                }
              }
            }
          },
          "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"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/shift-allocations/{id}/deactivate": {
      "post": {
        "operationId": "attend.shift_assignment.allocation_deactivate",
        "summary": "Deactivate a roster allocation",
        "description": "Cancels future unpunched assignments while retaining punched and approval-linked history.",
        "tags": [
          "attend",
          "shift-assignment"
        ],
        "x-token": "attend.shift_assignment.update",
        "x-idempotent": true,
        "x-rls-scope": "tenant",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Deactivated linked roster allocation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "roster",
                    "employees",
                    "assignments"
                  ]
                }
              }
            }
          },
          "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"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/shift-assignments": {
      "get": {
        "operationId": "attend.shift_assignment.list",
        "summary": "My upcoming shifts",
        "description": "The employee's upcoming shift assignments — date, window, expected hours, geofenced location (ATT-S11). Config owned by `org` (ORG-F04); read-only here.",
        "tags": [
          "attend",
          "shift_assignment"
        ],
        "x-token": "attend.shift_assignment.list",
        "x-realizes-features": [
          "ATT-F02"
        ],
        "x-screens": [
          "ATT-S11"
        ],
        "x-touches-entities": [
          "attend.shift_assignments",
          "org.shifts",
          "org.work_locations"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "assignment_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "assignment_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `assignment_date`, `status`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of the caller's own upcoming shift assignments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShiftAssignmentPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "attend.shift_assignment.create",
        "summary": "Assign a shift",
        "description": "Assigns an employee to a shift/date (+ the geofence a punch is measured against), from the roster build view (ATT-S16).",
        "tags": [
          "attend",
          "shift_assignment"
        ],
        "x-token": "attend.shift_assignment.create",
        "x-realizes-features": [
          "ATT-F02"
        ],
        "x-screens": [
          "ATT-S16"
        ],
        "x-touches-entities": [
          "attend.shift_assignments",
          "org.shifts",
          "org.geofences",
          "org.work_locations"
        ],
        "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,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShiftAssignmentCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Shift assignment created.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShiftAssignment"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/shift-assignments/admin": {
      "get": {
        "operationId": "attend.shift_assignment.list_admin",
        "summary": "Roster grid — all shift assignments",
        "description": "The tenant-wide calendar/grid of assignments backing the roster build view (ATT-S16).",
        "tags": [
          "attend",
          "shift_assignment"
        ],
        "x-token": "attend.shift_assignment.list_admin",
        "x-realizes-features": [
          "ATT-F02"
        ],
        "x-screens": [
          "ATT-S16"
        ],
        "x-touches-entities": [
          "attend.shift_assignments"
        ],
        "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,
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "shift_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "assignment_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "assignment_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ShiftAssignmentStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `assignment_date`, `employee_id`, `status`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant-wide page of shift assignments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShiftAssignmentPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/shift-assignments/{id}": {
      "patch": {
        "operationId": "attend.shift_assignment.update",
        "summary": "Update / swap a shift assignment",
        "description": "Edits or swaps a shift assignment (`status → SWAPPED`/`CANCELLED` supported) from the roster build view (ATT-S16).",
        "tags": [
          "attend",
          "shift_assignment"
        ],
        "x-token": "attend.shift_assignment.update",
        "x-realizes-features": [
          "ATT-F02"
        ],
        "x-screens": [
          "ATT-S16"
        ],
        "x-touches-entities": [
          "attend.shift_assignments"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShiftAssignmentUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated shift assignment.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShiftAssignment"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/attendance-cycles": {
      "get": {
        "operationId": "attend.attendance_cycle.list",
        "summary": "List attendance-cycle finalization snapshots",
        "description": "The finalization register (`attend.attendance_cycle_finalizations`, ADR 0028 §(h)) — one row per `(employee, cycle)`, filterable by pay group, org unit, cycle window and lifecycle state (`DRAFT` · `FINALIZED` · `AMENDED`). This is the read a payroll manager opens to answer *\"is August's attendance signed off, and by whom\"* before touching a run — the readiness pipeline's attendance stage (`ATTENDANCE_FINALIZED`, ADR 0029 §(c)) reads exactly these rows as its evidence. Sort whitelist: `cycle_end`, `-cycle_end`, `employee_id`, `status`. Default `-cycle_end`.\n",
        "tags": [
          "attend",
          "attendance_cycle"
        ],
        "x-token": "attend.attendance_cycle.list",
        "x-realizes-features": [
          "ATT-F10"
        ],
        "x-screens": [
          "ATT-S19",
          "PAY-S33"
        ],
        "x-touches-entities": [
          "attend.attendance_cycle_finalizations",
          "attend.attendance_records",
          "people.employees"
        ],
        "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,
        "parameters": [
          {
            "name": "pay_group_id",
            "in": "query",
            "required": false,
            "description": "Soft `ref→org.pay_groups` (ADR 0028 §(a)) — the population segment whose cycle this is.",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "org_unit_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "cycle_start[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "cycle_end[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/AttendanceCycleStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of finalization snapshots.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceCycleFinalizationPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-cycles/preview": {
      "get": {
        "operationId": "attend.attendance_cycle.preview",
        "summary": "Preview one pay-group cycle from live attendance facts",
        "description": "Returns the exact effective population and live per-employee counts for one generated cycle. `recorded_days` and `expected_days` make daily-record coverage explicit; a gap is also enforced server-side by finalization, so bypassing the console cannot sign an incomplete cycle. Signed rows are returned unchanged with `is_preview=false`; unsigned rows are calculated without persistence.\n",
        "tags": [
          "attend",
          "attendance_cycle"
        ],
        "x-token": "attend.attendance_cycle.list",
        "x-realizes-features": [
          "ATT-F10"
        ],
        "x-screens": [
          "ATT-S19"
        ],
        "x-touches-entities": [
          "attend.attendance_cycle_finalizations",
          "attend.attendance_records",
          "attend.overtime_requests",
          "people.employees",
          "pay.employee_compensation"
        ],
        "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,
        "parameters": [
          {
            "name": "pay_group_id",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "cycle_start",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "cycle_end",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live preview and exact coverage for the selected cycle.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceCyclePreview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-cycles/month-close": {
      "get": {
        "operationId": "attend.attendance_cycle.month_close",
        "summary": "Month close — things to fix, pay teams and per-person open items for one calendar month",
        "description": "Issue #1881. The read behind the Month close screen (ATT-S19). For one calendar month (`month=YYYY-MM`, window = the 1st to the last day, the exact window payroll's `finalizedCyclesWithinTx` consumes) and one pay group it returns the close population's per-person `missing_days` (daily-wage and contractor-team days with no record are \"not worked\", not gaps), open items (pending absent appeals, regularizations, overtime, missed-punch exceptions), month-end pay team (when pay-by-teams is on) and own daily-rate override; the pay teams with contractor and `rate_missing`; the employees active in the month but outside the population (`not_in_payroll`); the pay-period / payroll-month lock state; and a `checklist` of totals including DRAFT / SUBMITTED muster rolls dated in the month. Read-only. Counts are the same predicates finalize's readiness check enforces.\n",
        "tags": [
          "attend",
          "attendance_cycle"
        ],
        "x-token": "attend.attendance_cycle.list",
        "x-realizes-features": [
          "ATT-F10"
        ],
        "x-screens": [
          "ATT-S19"
        ],
        "x-touches-entities": [
          "attend.attendance_records",
          "attend.late_entry_requests",
          "attend.regularizations",
          "attend.overtime_requests",
          "attend.out_of_zone_events",
          "attend.muster_rolls",
          "payroll.pay_teams",
          "payroll.employee_pay",
          "payroll.settings",
          "payroll.months",
          "work.team_members",
          "people.employees"
        ],
        "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,
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "example": "2026-09"
            }
          },
          {
            "name": "pay_group_id",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The month's close context.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceMonthClose"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-cycles/{id}": {
      "get": {
        "operationId": "attend.attendance_cycle.get",
        "summary": "Get one employee's finalized cycle snapshot (the counts payroll will pay on)",
        "description": "One `(employee, cycle)` snapshot with its full decimal-string count set and its `snapshot_hash` (ADR 0028 §(h)). A payslip cites this row's id in its explanation chain's `source_refs` ([ADR 0031](../../../architecture-docs/adr/0031-pay-explanations-as-data.md)), so this is the operation a disputed number resolves to: the counts here are the counts that were computed from, not the counts that are true today.\n",
        "tags": [
          "attend",
          "attendance_cycle"
        ],
        "x-token": "attend.attendance_cycle.get",
        "x-realizes-features": [
          "ATT-F10"
        ],
        "x-screens": [
          "ATT-S19",
          "PAY-S33"
        ],
        "x-touches-entities": [
          "attend.attendance_cycle_finalizations"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The snapshot.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceCycleFinalization"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-cycles/finalize": {
      "post": {
        "operationId": "attend.attendance_cycle.finalize",
        "summary": "Finalize an attendance cycle in bulk (per pay group or per org unit) — the accountability moment",
        "description": "**ADR 0028 §(h).** Freezes the cycle for every employee in the selected population and writes one `FINALIZED` snapshot each, stamped `finalized_by`/`finalized_at` with a `snapshot_hash` over the counts. This is the handoff that used to be a verbal agreement — after it, *\"these are the days we are paying this person for\"* is a dated, owned row.\n\n**Blocked while the window still holds pending readiness gates.** Any `PENDING_APPROVAL` `attend.regularizations` or `attend.overtime_requests` dated inside the cycle refuses the call with `409` and a per-blocker breakdown in the problem detail — the two queues already carry named approvers, which is precisely why finalization is an accountability moment and not a query. Clearing them is the operator's work; the API never finalizes over an unsigned exception.\n\nBulk and jobs-tier (XC-F08), so `202` + `attend.attendance_cycle.finalized`, which `pay` consumes into `ATTENDANCE_SUMMARY` payroll inputs ([ADR 0030](../../../architecture-docs/adr/0030-payroll-inputs-as-projections.md) §(b)).\n\n**Idempotent over a MIXED population (issue #1302).** The call signs the UNSIGNED rows — `DRAFT`, `REOPENED`, and employees with no snapshot yet — and leaves `FINALIZED`/`AMENDED` siblings exactly as they are, signature and all; the accepted response reports the split as `to_sign` / `already_signed`. It refuses with `409` only when there is NOTHING left to sign, because re-freezing a wholly closed cycle is a state transition and not a no-op. Previously ANY signed row in the population refused the whole batch, which made reopen-then-refinalize — ADR 0028 §(h)'s only pre-lock correction path — impossible for anything short of a whole-population reopen. Correcting an already-signed row is still `reopen` (pre-lock) or `amend` (post-lock), never a second finalize.\n",
        "tags": [
          "attend",
          "attendance_cycle"
        ],
        "x-token": "attend.attendance_cycle.finalize",
        "x-realizes-features": [
          "ATT-F10",
          "PAY-F09"
        ],
        "x-screens": [
          "ATT-S19"
        ],
        "x-touches-entities": [
          "attend.attendance_cycle_finalizations",
          "attend.attendance_records",
          "attend.regularizations",
          "attend.overtime_requests",
          "attend.muster_entries"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "async",
        "x-emits-event": "attend.attendance_cycle.finalized",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttendanceCycleFinalizeRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Finalization enqueued; poll `attend.attendance_cycle.list` or subscribe to `attend.attendance_cycle.finalized`.\n",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceCycleFinalizeAccepted"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/attendance-cycles/{id}/reopen": {
      "post": {
        "operationId": "attend.attendance_cycle.reopen",
        "summary": "Reopen a finalized cycle — pre-lock only",
        "description": "**ADR 0028 §(h).** Returns a `FINALIZED` snapshot to the UNSIGNED state `REOPENED` (issue #1302 — it used to be `DRAFT`, which made a mid-correction row indistinguishable from one nobody had ever signed) so the window can be corrected before payroll consumes it. `finalize` re-signs it. **Refused (`409`) once the target `pay.pay_periods` row has reached `INPUTS_LOCKED`** — after that the only correction is `amend`, because reopening a locked period would silently move numbers a run has already staged. There is deliberately no third option: a closed cycle is never edited in place.\n",
        "tags": [
          "attend",
          "attendance_cycle"
        ],
        "x-token": "attend.attendance_cycle.reopen",
        "x-realizes-features": [
          "ATT-F10"
        ],
        "x-screens": [
          "ATT-S19"
        ],
        "x-touches-entities": [
          "attend.attendance_cycle_finalizations"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttendanceCycleReopenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Snapshot reopened to `REOPENED` — unsigned, and re-signed by the next `finalize`.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceCycleFinalization"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/attendance-cycles/{id}/amend": {
      "post": {
        "operationId": "attend.attendance_cycle.amend",
        "summary": "Amend a finalized cycle after the period locked — becomes an arrear, never a rewrite",
        "description": "**ADR 0028 §(h), the post-lock path.** Bumps `amendment_seq`, moves the snapshot to `AMENDED`, recomputes `snapshot_hash`, and emits `attend.attendance_cycle.amended`. `pay` consumes that event into an **`ARREAR`** payroll input plus a `pay.arrear_receipts` row rather than superseding a summary that has already been paid ([ADR 0030](../../../architecture-docs/adr/0030-payroll-inputs-as-projections.md) §(b)/§(d)) — the difference is realized forward as `ARREARS` earning lines in the next run, never as an edit to a published slip (the `pay.protect_published_payroll_line()` trigger already refuses that).\n",
        "tags": [
          "attend",
          "attendance_cycle"
        ],
        "x-token": "attend.attendance_cycle.amend",
        "x-realizes-features": [
          "ATT-F10",
          "PAY-F09"
        ],
        "x-screens": [
          "ATT-S19"
        ],
        "x-touches-entities": [
          "attend.attendance_cycle_finalizations",
          "attend.attendance_records"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.attendance_cycle.amended",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttendanceCycleAmendRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Snapshot amended; an arrear will be raised against the next open period.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceCycleFinalization"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/muster-templates": {
      "post": {
        "operationId": "attend.muster_template.create",
        "summary": "Save a crew as a reusable muster template",
        "description": "Creates the template and its pinned members in one transaction. `owner_employee_id` is the authenticated actor (or, with team scope over them, a named supervisor) and is never taken from the body. `default_day_status` may not be `PENDING`: a template carries a decision forward, and `PENDING` is the absence of one.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_template.create",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_templates",
          "attend.muster_template_members",
          "org.work_locations",
          "org.shifts",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MusterTemplateCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Template saved.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterTemplateDetail"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "attend.muster_template.list",
        "summary": "List muster templates",
        "description": "The supervisor's saved crews, most-recently-used first (`last_used_at DESC NULLS LAST`, then name) — the order the create modal's default depends on. Filterable by site and lifecycle state; `ACTIVE` only unless `status` says otherwise, because an archived template must not reappear in a picker.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_template.list",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_templates"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "work_location_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/MusterTemplateStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of muster templates.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterTemplatePage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/muster-templates/{id}": {
      "get": {
        "operationId": "attend.muster_template.get",
        "summary": "Get a muster template with its pinned crew",
        "description": "The template plus every `attend.muster_template_members` row — the employee, the day status a new roll is pre-seeded with, and the piece-rate unit they are normally counted against.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_template.get",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_templates",
          "attend.muster_template_members"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The template and its members.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterTemplateDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "attend.muster_template.update",
        "summary": "Rename, re-crew, re-shift or archive a muster template",
        "description": "Edits the template in place. Supplying `members` REPLACES the crew wholesale — that is what \"re-save yesterday's roll onto this template\" means, and a partial member patch would leave the caller unable to remove anybody. Retiring a template is `status: ARCHIVED`, not a delete: a template a roll was built from is provenance (`attend.muster_rolls.muster_template_id`), and archiving also releases its name to a successor.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_template.update",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_templates",
          "attend.muster_template_members",
          "org.shifts",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MusterTemplateUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template updated.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterTemplateDetail"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/muster-rolls": {
      "post": {
        "operationId": "attend.muster.create",
        "summary": "Open a muster roll for one site-day",
        "description": "Creates the `DRAFT` roll for `(work_location_id, work_date, supervisor_employee_id)` — unique per tenant (ADR 0032 §(b)). The roll is the supervisor's record of one site's day; its entries are added by `attend.muster.record` and it becomes attendance only on approval.\n\n**Daily site rolls** (`attend.tenant_settings.daily_site_rolls`, migration 0263): when the tenant has the switch on this is **get-or-create on `(work_location_id, work_date)`** — the site's existing live roll (`DRAFT`/`SUBMITTED`/`APPROVED`) is returned (`200`, same `MusterRollDetail` body) instead of a `409`, so a second supervisor joins the roll rather than being refused. In that mode the caller must hold the token at TENANT scope (the `site_supervisor` role or a super admin; `403 SCOPE_DENIED` otherwise), there is no direct-report bound on `supervisor_employee_id`, and `template_id` is refused (`422` — crew templates are not used). Flag off: unchanged.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.create",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "org.work_locations",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MusterRollCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Draft roll opened.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRoll"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "attend.muster.list",
        "summary": "List muster rolls",
        "description": "Rolls by site, date window and lifecycle state (`DRAFT` · `SUBMITTED` · `APPROVED` · `REJECTED`). Feeds the ATT-S13 manager exception queue's *\"roll past its SLA\"* lane (ADR 0032 §(e)) — the failure mode that silently loses a crew's day. Sort whitelist: `work_date`, `-work_date`, `status`. Default `-work_date`. In default newest-first order, `page.next_cursor` is a stable site-day/id continuation token; pass it as `page[after]` to load remaining submitted rolls.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.list",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20",
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.muster_rolls"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "work_location_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "supervisor_employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "work_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "work_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/MusterRollStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          },
          {
            "name": "legal_entity_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this legal entity (branch). Combines with `department_id` and `org_unit_id`, and REPLACES the caller's direct-report bound rather than intersecting with it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "department_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this department. Combines with `legal_entity_id` and `org_unit_id`. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "org_unit_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this org unit AND its sub-units — the unit's org-tree closure (`app.org_unit_closure`, migration 0162), so a department's parent unit includes the teams beneath it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "`ALL` — every ACTIVE employee in the tenant, with no axis narrowing it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "type": "string",
              "enum": [
                "ALL"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of muster rolls.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/muster-rolls/{id}": {
      "get": {
        "operationId": "attend.muster.get",
        "summary": "Get a muster roll with its entries",
        "description": "The roll plus every `attend.muster_entries` row — day status, optional in/out times, `units_done` (the decimal-string piece-rate quantity [ADR 0033](../../../architecture-docs/adr/0033-pay-models-daily-wage-and-piece-rate.md) §(c) prices), remarks, and `recorded_by`. A superseded entry from a re-approved roll stays on the roll as the before-image (ADR 0032 §(b)).\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.get",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The roll and its entries.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "attend.muster.delete",
        "summary": "Discard a draft muster roll",
        "description": "**`DRAFT` only, and a soft delete** (#1299). `attend.muster_rolls` is uniquely keyed on `(tenant_id, work_location_id, work_date, supervisor_employee_id)` — one supervisor's record of one site's day — so a roll opened against the wrong site or date used to own that site-day permanently, with submit-then-reject as the only escape. This discards it: the roll and its `attend.muster_entries` are stamped `deleted_at` (the rows stay for audit, and an `attend.muster_roll.discarded` audit fact is written in the same transaction), and the site-day key is scoped `WHERE deleted_at IS NULL`, so the correct roll can then be created.\n\n**A `SUBMITTED`, `APPROVED` or `REJECTED` roll is refused with `409`.** The `attend.muster_roll_status` reasoning — *\"`REJECTED` is the inbox's own branch, carried here so a declined roll is a STATE rather than a deletion\"* — is about a roll somebody ASSERTED, and stands unchanged for those. A `DRAFT` has been asserted by nobody: it is in no inbox and has written no `attendance_records`.\n\n**Who may discard:** the roll's own `supervisor_employee_id`, or a site manager / HR admin — resolved as the holder of `attend.muster.approve`, the token whose holder already decides this roll's fate at the inbox. Anyone else holding `attend.muster.delete` is refused `403` on this roll.\n\n`client_roll_id` uniqueness stays TOTAL: a capture id must never resolve to two rolls, discarded or not, or a replayed offline flush would mint a second one.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.delete",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "description": "Discarded; the site-day is free for a new roll."
          },
          "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"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/muster-rolls/{id}/handlers": {
      "post": {
        "operationId": "attend.muster.handlers.add",
        "summary": "Delegate a draft muster roll to additional capture handlers",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.record",
        "x-rls-scope": "team",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "employee_ids"
                ],
                "additionalProperties": false,
                "properties": {
                  "employee_ids": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "$ref": "#/components/schemas/UuidRef"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated draft roll with active handlers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollDetail"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/muster-rolls/{id}/handlers/{handlerId}": {
      "delete": {
        "operationId": "attend.muster.handlers.remove",
        "summary": "Remove a delegated handler from a draft muster roll",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.record",
        "x-rls-scope": "team",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "handlerId",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Updated draft roll.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollDetail"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          }
        }
      }
    },
    "/muster-rolls/{id}/entries": {
      "post": {
        "operationId": "attend.muster.record",
        "summary": "Record (mark-on-behalf) a crew's day onto a draft roll",
        "description": "**ADR 0032 §(b) — the most abusable capability in an attendance system, and therefore its own grant.** `attend.muster.record` is **not** implied by `attend.attendance_record.list_admin` and is **not** a widening of the regularization tokens; it is separately grantable so it can be seen, audited and revoked on its own.\n\nUpserts one entry per employee on a `DRAFT` roll, carrying `day_status` (the existing `attend.attendance_status` domain), optional `in_time`/`out_time`, `units_done` as a **decimal string** against a `unit_code` from the employee's piece-rate catalogue, and free-text `remarks`. **`recorded_by` is written from the authenticated actor and is refused (`422`) if supplied in the body** — the row must say who decided, not merely what was decided (the ADR 0025 analogue of `verified_by`). Refused (`409`) on a roll that is no longer `DRAFT`. In v2, a non-PENDING mark is refused with `muster-worker-already-marked` when this worker already clocked in themselves on that date. A PENDING re-record can unmark a draft row.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.record",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries",
          "people.employees",
          "org.piece_rate_items"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MusterEntryRecordRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Entries recorded on the draft roll.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollDetail"
                }
              }
            }
          },
          "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": "attend.muster.clear_entries",
        "summary": "Clear every worker off a draft muster roll",
        "description": "Soft-deletes every live `attend.muster_entries` row on a `DRAFT` roll in one transaction (`409` otherwise), re-derives `entry_count`, bumps the roll's version and writes an `attend.muster_roll.entries_cleared` audit fact. Same keepers and token as `attend.muster.remove_entry`. An already-empty roll is a success.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.remove_entry",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_entries",
          "attend.muster_rolls",
          "audit.audit_log"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "The roll with no entries.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollDetail"
                }
              }
            }
          },
          "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"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/muster-rolls/{id}/copy-from": {
      "post": {
        "operationId": "attend.muster.copy_from",
        "summary": "Copy another day's crew onto this draft roll",
        "description": "Daily site rolls only. Under the per-(tenant, target-date) lock, copies the live roll for the same site on `source_work_date` onto this `DRAFT` roll. **Partial success**: workers already on another live roll for the target date, or already marked on a non-SYSTEM attendance record, are listed in `skipped` rather than refusing the call (unlike `attend.muster_allocation.assign`). Default `day_status` is `PRESENT`. No source roll is an empty copy (`added: []`, `skipped: []`) without bumping the version.\n\nA `409` `type: urn:groundit:problem:attend:muster-worker-already-marked` is not used here — those workers are skipped so the supervisor can move them from the allocation board.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.record",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries",
          "audit.audit_log"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "source_work_date"
                ],
                "properties": {
                  "source_work_date": {
                    "$ref": "#/components/schemas/DateOnlyRef"
                  },
                  "day_status": {
                    "$ref": "#/components/schemas/AttendanceStatus"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The roll after the copy, with who was added and who was skipped.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "roll",
                    "added",
                    "skipped"
                  ],
                  "properties": {
                    "roll": {
                      "$ref": "#/components/schemas/MusterRollDetail"
                    },
                    "added": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UuidRef"
                      }
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "employee_id",
                          "reason"
                        ],
                        "properties": {
                          "employee_id": {
                            "$ref": "#/components/schemas/UuidRef"
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "ON_ANOTHER_ROLL",
                              "ALREADY_MARKED"
                            ]
                          },
                          "other_roll": {
                            "oneOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "muster_roll_id": {
                                    "$ref": "#/components/schemas/UuidRef"
                                  },
                                  "muster_no": {
                                    "type": "string"
                                  },
                                  "work_location_id": {
                                    "$ref": "#/components/schemas/UuidRef"
                                  },
                                  "site_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "status": {
                                    "$ref": "#/components/schemas/MusterRollStatus"
                                  }
                                }
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/attendance-settings": {
      "get": {
        "operationId": "attend.tenant_setting.get",
        "summary": "Read the tenant's attendance capture settings",
        "description": "`attend.tenant_settings` (migration 0263). One row per tenant; an absent row answers the defaults (`daily_site_rolls: false`, `version: 0`). Read by the muster screen to decide whether rolls are shared per site per day and whether crew templates are shown.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.tenant_setting.get",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.tenant_settings"
        ],
        "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,
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "patch": {
        "operationId": "attend.tenant_setting.update",
        "summary": "Change the tenant's attendance capture settings",
        "description": "Upserts the tenant's one settings row and writes an `attend.tenant_setting.updated` audit fact. `If-Match` is honoured when a row already exists (`412` on a stale version) and may be omitted on the first write. Partial (#1879): every key is optional and an omitted key keeps its stored value; at least one key must be sent.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.tenant_setting.update",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.tenant_settings",
          "audit.audit_log"
        ],
        "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,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "minProperties": 1,
                "properties": {
                  "daily_site_rolls": {
                    "type": "boolean"
                  },
                  "holiday_scope_types": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "$ref": "#/components/schemas/HolidayScopeType"
                    }
                  },
                  "absent_appeal": {
                    "type": "boolean",
                    "description": "#1878 — see `AttendSettings`."
                  },
                  "absence_leave_conversion": {
                    "type": "boolean",
                    "description": "#1878 — see `AttendSettings`."
                  },
                  "absence_leave_type_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "#1878 — see `AttendSettings`."
                  },
                  "muster_self_attendance": {
                    "type": "boolean"
                  },
                  "muster_supervisor_face": {
                    "type": "string",
                    "enum": [
                      "OFF",
                      "ADVISORY",
                      "REQUIRED"
                    ]
                  },
                  "muster_marker_geofence": {
                    "type": "string",
                    "enum": [
                      "ADVISORY",
                      "BLOCKING"
                    ]
                  },
                  "muster_team_marking": {
                    "type": "boolean",
                    "description": "Requires daily_site_rolls=true."
                  },
                  "muster_default_shift_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "muster_ess_restricted": {
                    "type": "boolean"
                  },
                  "muster_override_employee_ids": {
                    "type": "array",
                    "maxItems": 5,
                    "uniqueItems": true,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "muster_self_approval": {
                    "type": "boolean",
                    "default": false,
                    "description": "#1908 — the submitter of a muster roll may also approve/reject it (four-eyes lifted for this workspace; audited as self_approval)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/muster-days/me": {
      "get": {
        "operationId": "attend.muster_day.get_me",
        "summary": "Read the caller's own muster allocation and attendance state for one civil day",
        "description": "SELF-scoped. The database definer function takes only a date and binds employee and tenant to the session. Defaults to the caller's attendance-zone civil date. Returns only `{applicable:false}` for a worker whose work mode is not MUSTER.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_day.get_me",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S02",
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_entries",
          "attend.muster_rolls",
          "attend.attendance_records",
          "attend.punch_sessions",
          "attend.face_enrollments",
          "attend.tenant_settings",
          "org.work_locations",
          "org.geofences",
          "org.shifts",
          "people.employees"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "work_date",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The worker's own muster day.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterDay"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/muster-teams/me": {
      "get": {
        "operationId": "attend.muster_team.list_mine",
        "summary": "List the lead's live muster team members and their site-day state",
        "description": "The caller must hold the narrow TEAM role. Only teams led by the session employee and members live on work_date are returned. A point identifies every active fenced site here; no client-selected site or fence is trusted for a later mark. Team marking and daily site rolls must both be enabled. No face templates or scores are returned.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_team.list_mine",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "work.teams",
          "work.team_members",
          "people.employees",
          "attend.muster_rolls",
          "attend.muster_entries",
          "attend.tenant_settings",
          "org.work_locations",
          "org.geofences"
        ],
        "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,
        "parameters": [
          {
            "name": "work_date",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "latitude",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            }
          },
          {
            "name": "longitude",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Date-bound team roster and sites containing the supplied point.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterTeamList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/muster-teams/{teamId}/marks": {
      "post": {
        "operationId": "attend.muster_team.mark",
        "summary": "Mark live team members on their own site rolls",
        "description": "All 1–50 marks commit or roll back together. The caller must lead this ACTIVE team and every worker must be a live ACTIVE MUSTER member on work_date. A worker already allocated stays on that site's DRAFT roll; an unallocated worker needs an active fenced site containing the marker, named explicitly when multiple fences overlap. TEAM face checks must match this worker, date, marker and channel. The server stamps recorder, channel and positive marks' clock-in time; it refuses self-punched days, the lead's own employee record, and attempts to replace another recorder's mark. A lead may correct only their own TEAM marks. No general muster.record capability is conferred by this operation.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_team.mark",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "work.teams",
          "work.team_members",
          "attend.muster_rolls",
          "attend.muster_entries",
          "attend.muster_face_checks",
          "audit.audit_log"
        ],
        "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,
        "parameters": [
          {
            "name": "teamId",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MusterTeamMarkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The workers marked and the versions of all touched site rolls.",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterTeamMarkResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          }
        }
      }
    },
    "/attendance-holidays": {
      "get": {
        "operationId": "attend.scoped_holiday.list",
        "summary": "List scoped holidays declared in a month",
        "description": "Live (uncancelled unless `include_cancelled=true`) scoped holidays whose `work_date` falls in `month`. Feeds the Records month grid's date headers (#1879).\n",
        "tags": [
          "attend"
        ],
        "x-token": "attend.scoped_holiday.list",
        "x-realizes-features": [
          "ATT-F06",
          "ATT-F10"
        ],
        "x-screens": [
          "ATT-S12"
        ],
        "x-touches-entities": [
          "attend.scoped_holidays"
        ],
        "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,
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            }
          },
          {
            "name": "include_cancelled",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The month's holidays.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ScopedHoliday"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "post": {
        "operationId": "attend.scoped_holiday.create",
        "summary": "Declare a holiday for a scope on one date",
        "description": "\"No work at all\" for the scope on `work_date` (#1879). The scope resolves to the people employed on the date (TEAM via live `work.team_members`, DEPARTMENT/DESIGNATION via `people.employees`, ROLE via ACTIVE `admin.member_grants` in both subject partitions, EMPLOYEE by id) and is frozen. Applied at once, set-based: a missing day or a SYSTEM `ABSENT`/`PENDING` day with no clock-in becomes `HOLIDAY`; PRESENT / HALF_DAY / ON_FIELD / leave / weekly off / regularized days are kept (who worked keeps it), and a person with approved leave on the date gets no row. The nightly materializer honours the holiday for dates that mature later. `409 STATE_TRANSITION_INVALID` when the date is in a non-DRAFT `payroll.months` period or any in-scope person's attendance cycle is FINALIZED/AMENDED; `422` for a scope type the tenant does not allow, a date outside [-400 days, +366 days], or a scope that resolves to nobody. Audited as `attend.scoped_holiday.created`.\n",
        "tags": [
          "attend"
        ],
        "x-token": "attend.scoped_holiday.create",
        "x-realizes-features": [
          "ATT-F06",
          "ATT-F10"
        ],
        "x-screens": [
          "ATT-S12"
        ],
        "x-touches-entities": [
          "attend.scoped_holidays",
          "attend.attendance_records",
          "audit.audit_log"
        ],
        "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,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "work_date",
                  "name",
                  "scope_type"
                ],
                "properties": {
                  "work_date": {
                    "type": "string",
                    "format": "date"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "salaried_paid": {
                    "type": "boolean",
                    "default": false
                  },
                  "scope_type": {
                    "$ref": "#/components/schemas/HolidayScopeType"
                  },
                  "scope_ids": {
                    "type": "array",
                    "maxItems": 500,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Required (1–500) for every type but ALL; must be empty for ALL."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The holiday, with what the apply did.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ScopedHoliday"
                    },
                    {
                      "type": "object",
                      "required": [
                        "holiday_days",
                        "kept_days"
                      ],
                      "properties": {
                        "holiday_days": {
                          "type": "integer",
                          "description": "Employee-days now HOLIDAY."
                        },
                        "kept_days": {
                          "type": "integer",
                          "description": "In-scope people whose day was kept."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/attendance-holidays/{id}/cancel": {
      "post": {
        "operationId": "attend.scoped_holiday.cancel",
        "summary": "Cancel a scoped holiday",
        "description": "Cancels the holiday and re-materializes its date (#1879): every day it still holds as an untouched SYSTEM `HOLIDAY` goes back to the status it had before (`scoped_holiday_prior_status`), a row the holiday itself created is removed for the attendance materializer to re-derive, and a day somebody later worked just drops the link. Same `409` guards as declare. Audited as `attend.scoped_holiday.cancelled`.\n",
        "tags": [
          "attend"
        ],
        "x-token": "attend.scoped_holiday.cancel",
        "x-realizes-features": [
          "ATT-F06",
          "ATT-F10"
        ],
        "x-screens": [
          "ATT-S12"
        ],
        "x-touches-entities": [
          "attend.scoped_holidays",
          "attend.attendance_records",
          "audit.audit_log"
        ],
        "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,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled holiday, with what the cancel did.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ScopedHoliday"
                    },
                    {
                      "type": "object",
                      "required": [
                        "restored",
                        "removed"
                      ],
                      "properties": {
                        "restored": {
                          "type": "integer"
                        },
                        "removed": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/muster-workers": {
      "get": {
        "operationId": "attend.muster.search_workers",
        "summary": "Search workers to add to a muster roll",
        "description": "ACTIVE employees with `work_mode = MUSTER`, matched on name or employee number (case-insensitive substring; `%`/`_` match literally). Tenant-wide for a TENANT-scoped holder (site supervisors, super admins); a TEAM-scoped manager sees what the people overlay admits. With `work_date`, each hit carries `allocation` — the live roll that already holds the worker that day — so the UI can say where they are before an add is refused. `allocation.movable` is true only when that other roll is `DRAFT` and the worker is listed (not marked PRESENT / HALF_DAY / ABSENT, and no non-SYSTEM attendance record for the date); the app shows \"Move here\" only then.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.search_workers",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "people.employees",
          "attend.muster_entries",
          "attend.muster_rolls"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 120
            }
          },
          {
            "name": "work_date",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching workers (at most `page[size]`, default 25).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MusterWorkerHit"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "post": {
        "operationId": "attend.muster.add_worker",
        "summary": "Quick add a daily worker from the muster screen",
        "description": "Mints an ACTIVE `MUSTER`-mode `CONTRACT` employee with no sign-in through the same kernel as the HR wizard (plan seat, `employee_no`, satellites, audit), optionally enrolled on a squad (`work.teams`, `team_id`) in the same transaction. The legal entity is the site's; with no site it is the tenant's only legal entity (`422` when there are several). `date_of_joining` defaults to today in the site's timezone. The body answered is a `MusterWorkerHit` (plus `team_id` / `team_name`), ready for `attend.muster.record` on the open roll.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.add_worker",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "people.employees",
          "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,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "full_name"
                ],
                "properties": {
                  "mobile_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\+[1-9][0-9]{7,14}$",
                    "description": "Personal phone used to provision a phone-only ESS login."
                  },
                  "full_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "work_location_id": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/UuidRef"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "team_id": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/UuidRef"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "date_of_joining": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/DateOnlyRef"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Worker created (and enrolled on the team when `team_id` was sent).",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/MusterWorkerHit"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "team_id": {
                          "oneOf": [
                            {
                              "$ref": "#/components/schemas/UuidRef"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "team_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/muster-allocations": {
      "get": {
        "operationId": "attend.muster_allocation.list",
        "summary": "The Site allocation board for one day",
        "description": "Every ACTIVE work location with its live roll(s) for `work_date` — status, roll number, version, who last edited it and the workers on it (with their marked day status) — plus the ACTIVE `MUSTER`-mode workers on NO live roll that day (`unallocated`). DRAFT and SUBMITTED rolls are included, not only APPROVED ones. Works whatever `daily_site_rolls` says (echoed in the body); the assign / move doors refuse when it is off.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_allocation.list",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries",
          "org.work_locations",
          "people.employees"
        ],
        "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,
        "parameters": [
          {
            "name": "work_date",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The board.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterAllocationBoard"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/muster-allocations/workers/{employeeId}": {
      "get": {
        "operationId": "attend.muster_allocation.get_worker_history",
        "summary": "One worker's sites over a window",
        "description": "One row per day in `[from, to]` (default the 30 days ending `to`, itself default today; at most 93 days): the live muster entry for the day (`basis: MUSTER`, with the roll), else an attendance ledger day whose `work_location_id` is set (`basis: LEDGER`). In v2, each row also carries a read-only `self_punch` summary when the worker clocked in themselves.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_allocation.get_worker_history",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_entries",
          "attend.muster_rolls",
          "attend.attendance_records"
        ],
        "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,
        "parameters": [
          {
            "name": "employeeId",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The worker's days.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterWorkerHistory"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/muster-allocations/assign": {
      "post": {
        "operationId": "attend.muster_allocation.assign",
        "summary": "Put workers on a site's roll for a day",
        "description": "Daily site rolls only (`409` when the switch is off). Under the per-(tenant, date) lock: the site's live roll is found or opened (get-or-create, as `attend.muster.create`), must be `DRAFT` (`409` naming the roll and who can reopen it otherwise), and each worker is added with `day_status` (default `PRESENT`). **All or nothing**: a worker already on another live roll that day refuses the whole call with `409` `type: urn:groundit:problem:attend:muster-worker-on-another-roll`, naming each conflict's site and roll number. A non-SYSTEM `attend.attendance_records` row for the worker and date refuses with `409` `type: urn:groundit:problem:attend:muster-worker-already-marked`. Workers already on this roll are left untouched and counted in `already_on_roll`. `day_status` default `PRESENT`; `PENDING` lists the worker without marking them.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_allocation.assign",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries",
          "audit.audit_log"
        ],
        "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,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "work_date",
                  "work_location_id",
                  "employee_ids"
                ],
                "properties": {
                  "work_date": {
                    "$ref": "#/components/schemas/DateOnlyRef"
                  },
                  "work_location_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "employee_ids": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "$ref": "#/components/schemas/UuidRef"
                    }
                  },
                  "day_status": {
                    "$ref": "#/components/schemas/AttendanceStatus",
                    "description": "Defaults to `PRESENT`. `PENDING` lists the worker without marking them."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Workers assigned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "work_date",
                    "assigned",
                    "already_on_roll",
                    "roll"
                  ],
                  "properties": {
                    "work_date": {
                      "$ref": "#/components/schemas/DateOnlyRef"
                    },
                    "assigned": {
                      "type": "integer"
                    },
                    "already_on_roll": {
                      "type": "integer"
                    },
                    "roll": {
                      "$ref": "#/components/schemas/MusterRoll"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/muster-allocations/move": {
      "post": {
        "operationId": "attend.muster_allocation.move",
        "summary": "Move one worker to another site for a day, atomically",
        "description": "One transaction under the per-(tenant, date) lock: the worker's entry is taken off every source roll and the same day (status, times, units, remarks) lands on the target site's roll, opened if needed. At no committed instant is the worker on two rolls or on none. Every source and the target must be `DRAFT` — moving out of an `APPROVED` roll is refused (`409`; the ledger already holds that day, and a super admin corrects it through attendance regularization), as is a `SUBMITTED` one (a super admin can reject it back from Approvals). A worker already marked PRESENT / HALF_DAY / ABSENT on a DRAFT roll, or who has a non-SYSTEM `attend.attendance_records` row for the date, is refused with `409` `type: urn:groundit:problem:attend:muster-worker-already-marked`. A listed (`PENDING`) worker on a DRAFT roll can still be moved. A worker on no roll is simply assigned; moving to the site they are on is a no-op (`moved: false`). Daily site rolls only.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster_allocation.move",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries",
          "audit.audit_log"
        ],
        "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,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "employee_id",
                  "work_date",
                  "to_work_location_id"
                ],
                "properties": {
                  "employee_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "work_date": {
                    "$ref": "#/components/schemas/DateOnlyRef"
                  },
                  "to_work_location_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "day_status": {
                    "type": "string",
                    "description": "Optional in muster v2; only PENDING is accepted. Legacy tenants do not accept this property.",
                    "enum": [
                      "PENDING",
                      "PRESENT",
                      "HALF_DAY",
                      "ABSENT"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The move (or the no-op).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "work_date",
                    "moved",
                    "from_roll",
                    "to_roll"
                  ],
                  "properties": {
                    "work_date": {
                      "$ref": "#/components/schemas/DateOnlyRef"
                    },
                    "moved": {
                      "type": "boolean"
                    },
                    "from_roll": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/MusterRoll"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "to_roll": {
                      "$ref": "#/components/schemas/MusterRoll"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/muster-rolls/{id}/entries/{employeeId}": {
      "delete": {
        "operationId": "attend.muster.remove_entry",
        "summary": "Take one worker off a draft muster roll",
        "description": "Soft-deletes the worker's entry on a `DRAFT` roll (`409` otherwise, naming who can reopen it), re-derives `entry_count`, bumps the roll's version and writes an `attend.muster_roll.entry_removed` audit fact. Adding the worker back revives the same row. Same keepers as `attend.muster.record`.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.remove_entry",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_entries",
          "attend.muster_rolls",
          "audit.audit_log"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "employeeId",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The roll after removal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollDetail"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          }
        }
      }
    },
    "/muster-rolls/sync": {
      "post": {
        "operationId": "attend.muster.sync",
        "summary": "Sync offline-captured muster rolls (multi-employee batch, offline-queue capable)",
        "description": "**ADR 0032 §(d) — the multi-employee batch variant of `attend.punch.sync`, under the `attend.muster.record` token.** Rolls are recorded where there is no signal, so the sync path extends the **existing two-key offline contract** (`04-idempotency-concurrency-webhooks.md` §1.1) rather than inventing a second one: a **client-generated `client_roll_id`** per roll, captured at record time and **independent of the HTTP `Idempotency-Key`** on the sync call itself. The call may legitimately retry the whole batch while each roll inside it must resolve to exactly one `attend.muster_rolls` row no matter how many times it appears across retried batches — a replayed `client_roll_id` upserts the same roll and its entries and **never double-counts**. One offline story for the product, not a second one for field crews.\n\nPer-item results (a batch may mix clean and conflicting outcomes) mirror `AttendancePunchSyncResponse`. Rolls arrive `DRAFT` or `SUBMITTED` per item; approval is never implied by a sync.\n\n**As-built (#584).** Items are applied in **array order** and the `results` array mirrors that order one-for-one — `04 §1.1` makes array order significant to the replay decision, so a reordered batch is a different request. Each item runs inside its own **savepoint**: a rejected roll is rolled back alone and every other roll in the same batch still commits, so a partial failure never poisons the batch (the isolation `work.timesheet.approve_batch` established). `submit: true` runs the *same* `DRAFT → SUBMITTED` transition the online submit does — same audit fact, same `attend.muster.submitted` event, same `MUSTER` inbox projection — and nothing on this path writes `attend.attendance_records`. A capture id that already names a **different site-day** is refused (`CONFLICT`) rather than reconciled: a client key must resolve to exactly one roll.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.sync",
        "x-realizes-features": [
          "ATT-F09",
          "XC-F10"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries",
          "org.work_locations",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.muster.submitted",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MusterRollSyncRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Every roll in the batch reconciled (per-item results).",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollSyncResponse"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/muster-rolls/{id}/submit": {
      "post": {
        "operationId": "attend.muster.submit",
        "summary": "Submit a muster roll for approval",
        "description": "`DRAFT → SUBMITTED`, raising an `xc.approval_inbox` request of type **`MUSTER`** — append-only `xc.approval_request_type` growth on exactly the mechanism ADR 0027 §(e) established for `INVOICE`, routed by a seeded rule (site manager, then HR where a site wants a second leg). The inbox's SLA sweep escalates a roll left unsigned. Refused (`422`) on a roll with no entries. Owner ruling 2026-09-28 (revised, #1908): the site roll is the one unit that is locked and approved; in v2 daily site mode ANY holder of this token (site supervisors and team leads) may lock it. Outside v2 site mode a holder without `attend.muster.record` is refused with `muster-site-supervisor-submits` (403).\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.submit",
        "x-realizes-features": [
          "ATT-F09",
          "XC-F16"
        ],
        "x-screens": [
          "ATT-S20",
          "XC-S15"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.muster.submitted",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Submitted; an inbox request is routed to the configured approver chain.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRoll"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/muster-rolls/{id}/reopen": {
      "post": {
        "operationId": "attend.muster.reopen",
        "summary": "Reopen a rejected muster roll back to draft",
        "description": "`REJECTED → DRAFT`. Clears `submitted_at` / `submitted_by` / `approved_by` / `approved_at`, keeps the rejection note in `notes`, bumps `version`, and withdraws any still-PENDING `xc.approval_inbox` row for this roll. A later `attend.muster.submit` raises a new MUSTER envelope (the inbox unique key only covers PENDING rows).\n\nRefused (`409`) unless the roll is `REJECTED`; refused when any of its workers now sit on another live roll for the date (`type: urn:groundit:problem:attend:muster-worker-on-another-roll`); refused in daily-site-roll mode when another live roll already exists for the same site and day. Anyone holding `attend.muster.submit` at the roll's keeper scope may reopen.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.submit",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "xc.approval_inbox",
          "audit.audit_log"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "The roll is a DRAFT again.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollDetail"
                }
              }
            }
          },
          "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"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/muster-rolls/{id}/approve": {
      "post": {
        "operationId": "attend.muster.approve",
        "summary": "Approve a muster roll — writes the attendance ledger in the same transaction",
        "description": "**Four-eyes (#1908):** the submitter may not decide their own roll (`403 MAKER_EQUALS_CHECKER`) unless the workspace setting `muster_self_approval` is on; then the decision is allowed, audited with `self_approval: true`, and the roll's MUSTER inbox envelope is closed in the same transaction. **ADR 0032 §(b).** On `APPROVED` the `attend` service **upserts `attend.attendance_records` for every entry in the same call, the same transaction and the same schema** — `attend` writing `attend`, which `03-domain-modules` §4 permits precisely because atomicity is required and no schema boundary is crossed. There is therefore **no window in which a roll is approved but the day it approves does not exist**. `source` is **`MUSTER`**, a new member of `attend.attendance_source`.\n\n**Re-approval is an upsert, not a duplicate.** `attendance_records` is already unique on `(tenant_id, employee_id, work_date)`, so a corrected roll re-approved for the same day updates the same row and the superseded entry stays on its roll as the before-image.\n\nMuster is a **weaker evidentiary class than a verified punch** and the ledger records which class the day came from (`source`), so payroll, an inspector, or the EWA underwriting signal can weigh it accordingly (ADR 0032 Consequences).\n\n**The upsert only overwrites rows this mechanism owns (#1172).** It rewrites its own earlier `MUSTER` assertion and the nightly materializer's `SYSTEM` placeholder — never a `MOBILE_GEOFENCE` / `WEB` / `DEVICE` / `BIOMETRIC` / `IMPORT` / `REGULARIZED` day, and never a day an approved regularization has already decided (`is_regularized`). Those days keep their status, their clock times and their provenance; restamping one `MUSTER` would make the strongest provenance in the system indistinguishable from a supervisor's word, which is the exact property `source` exists to preserve. The refused entries stay on their roll with `applied_at: null` and are reported in the response's **`ledger`** (`applied` · `skipped_verified` · `conflicts`) — ADR 0032 §(e)'s muster-vs-punch disagreement, surfaced rather than resolved. **A skip is not an error:** the roll is still `APPROVED` and the call is still `200`. In v2, PENDING entries write nothing; `ledger.self_attended` counts PENDING workers with a final self-punch and `ledger.not_marked` the other PENDING workers.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.approve",
        "x-realizes-features": [
          "ATT-F09",
          "ATT-F01"
        ],
        "x-screens": [
          "ATT-S20",
          "ATT-S13",
          "XC-S15"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "attend.muster_entries",
          "attend.attendance_records",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.muster.approved",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved; the day's `attend.attendance_records` rows are written with `source = MUSTER`, except any the provenance guard refused — see `ledger` (#1172).",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRollDetail"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/muster-rolls/{id}/reject": {
      "post": {
        "operationId": "attend.muster.reject",
        "summary": "Reject a muster roll back to the supervisor",
        "description": "`SUBMITTED → REJECTED` with a mandatory reason; nothing is written to the attendance ledger. The supervisor corrects the roll and resubmits — a rejected roll is never silently dropped, because a site-day with no record is exactly the loss the ATT-S13 exception queue exists to surface.\n\n**`note` is REQUIRED here** (#582, as-built) even though the shared `ApprovalDecisionInput` leaves it optional for other actions: a site-day bounced with no explanation cannot be re-recorded. The reason is appended to the roll's `notes`, so the supervisor reads it where they read the roll.\n\n**Emits `attend.muster.rejected` (#1644).** Until then this action emitted nothing, so the roll's `xc.approval_inbox` envelope had no event to be reconciled from and stayed `PENDING` for ever with a live `sla_due_at` over an already-decided roll. The event carries `decided_by` and the rejection `note`, which is what `xc-approval-decision-reconciler` stamps onto the envelope.\n",
        "tags": [
          "attend",
          "muster"
        ],
        "x-token": "attend.muster.reject",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S20",
          "XC-S15"
        ],
        "x-touches-entities": [
          "attend.muster_rolls",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.muster.rejected",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected; the roll returns to the supervisor for correction.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusterRoll"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/beat-plans": {
      "post": {
        "operationId": "attend.beat_plan.create",
        "summary": "Create a beat plan (a route of client/site stops for a field employee)",
        "description": "`attend.beat_plans` exactly as db 05 §1.4 specifies it — `(tenant_id, beat_no)` unique, the `stops` JSONB shape unchanged, `attend.beat_plan_status` ∈ `DRAFT` · `ACTIVE` · `COMPLETED` · `CANCELLED`. Created `DRAFT`. Access field-ops supervisor, HR Admin.\n",
        "tags": [
          "attend",
          "beat_plan"
        ],
        "x-token": "attend.beat_plan.create",
        "x-realizes-features": [
          "ATT-F07"
        ],
        "x-screens": [
          "ATT-S21"
        ],
        "x-touches-entities": [
          "attend.beat_plans",
          "org.work_locations",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BeatPlanCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Beat plan created as `DRAFT`.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BeatPlan"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "attend.beat_plan.list",
        "summary": "List beat plans",
        "description": "Field-ops route register (ATT-S21). Sort whitelist: `plan_date`, `-plan_date`, `beat_no`, `status`. Default `-plan_date`.\n",
        "tags": [
          "attend",
          "beat_plan"
        ],
        "x-token": "attend.beat_plan.list",
        "x-realizes-features": [
          "ATT-F07"
        ],
        "x-screens": [
          "ATT-S21"
        ],
        "x-touches-entities": [
          "attend.beat_plans"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "plan_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "plan_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/BeatPlanStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of beat plans.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BeatPlanPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/beat-plans/{id}": {
      "get": {
        "operationId": "attend.beat_plan.get",
        "summary": "Get a beat plan with its stops",
        "description": "The plan and its ordered `stops[]` (db 05 §1.4 JSONB shape), plus each stop's visit state.",
        "tags": [
          "attend",
          "beat_plan"
        ],
        "x-token": "attend.beat_plan.get",
        "x-realizes-features": [
          "ATT-F07"
        ],
        "x-screens": [
          "ATT-S21"
        ],
        "x-touches-entities": [
          "attend.beat_plans",
          "attend.field_visits"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The beat plan.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BeatPlan"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "attend.beat_plan.update",
        "summary": "Update a beat plan (stops, assignment, status)",
        "description": "Edits the route or advances `attend.beat_plan_status`. A stop that was never visited resolves to `MISSED` on plan completion and surfaces in the ATT-S13 exception queue (ADR 0032 §(e)).\n",
        "tags": [
          "attend",
          "beat_plan"
        ],
        "x-token": "attend.beat_plan.update",
        "x-realizes-features": [
          "ATT-F07"
        ],
        "x-screens": [
          "ATT-S21"
        ],
        "x-touches-entities": [
          "attend.beat_plans"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BeatPlanUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Beat plan updated.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BeatPlan"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/field-visits/checkin": {
      "post": {
        "operationId": "attend.field_visit.checkin",
        "summary": "Check in at a beat stop / client site (geo-attested, offline-queue capable)",
        "description": "**ADR 0032 §(a) — field visits are NOT exempt from ADR 0025.** `client_visit_id` is the capture-time idempotency key, reconciled exactly like `client_punch_id` (`04 §1.1`), and the visit's `geofence_attestation_id` puts the **check-in** under the same server-side containment recomputation ADR 0025 §4 applies to punches: a geotagged visit is a claim, and the server re-derives it. A client/server disagreement is flagged and marks the day exceptional rather than being denied. `OUT` uses the supplied capture timestamp to close the visit; ATT-F07 has no second-attestation or checkout-coordinate columns, so this operation does not invent a second evidence model.\n\nA `CHECKED_IN` visit **may mark the day `ON_FIELD`** on `attend.attendance_records` — the `ON_FIELD` member of `attend.attendance_status` already exists for exactly this. `attend.field_visit_status` ∈ `PLANNED` · `CHECKED_IN` · `CHECKED_OUT` · `MISSED` · `CANCELLED`; the same operation records the check-out leg by carrying `punch_type: OUT`.\n",
        "tags": [
          "attend",
          "field_visit"
        ],
        "x-token": "attend.field_visit.checkin",
        "x-realizes-features": [
          "ATT-F07",
          "ATT-F08"
        ],
        "x-screens": [
          "ATT-S21"
        ],
        "x-touches-entities": [
          "attend.field_visits",
          "attend.beat_plans",
          "attend.geofence_attestations",
          "attend.attendance_records",
          "org.geofences",
          "org.work_locations",
          "org.pay_groups",
          "org.legal_entities",
          "admin.tenant_config",
          "pay.employee_compensation",
          "people.employees"
        ],
        "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,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FieldVisitCheckinRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Visit reconciled. A replayed `client_visit_id` upserts the same row and never double-counts.\n",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FieldVisit"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/field-visits": {
      "get": {
        "operationId": "attend.field_visit.list",
        "summary": "List field visits",
        "description": "Visits by employee, beat plan, date window and status. `MISSED` stops are the ATT-S13 exception queue's field lane (ADR 0032 §(e)). Sort whitelist: `visit_date`, `-visit_date`, `status`. Default `-visit_date`.\n",
        "tags": [
          "attend",
          "field_visit"
        ],
        "x-token": "attend.field_visit.list",
        "x-realizes-features": [
          "ATT-F07"
        ],
        "x-screens": [
          "ATT-S21",
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.field_visits"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "beat_plan_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "visit_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "visit_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/FieldVisitStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of field visits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FieldVisitPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/field-visits/{id}": {
      "get": {
        "operationId": "attend.field_visit.get",
        "summary": "Get one field visit",
        "description": "The visit with its server-recomputed geofence attestation and its beat-stop linkage (db 05 §1.4).",
        "tags": [
          "attend",
          "field_visit"
        ],
        "x-token": "attend.field_visit.get",
        "x-realizes-features": [
          "ATT-F07"
        ],
        "x-screens": [
          "ATT-S21"
        ],
        "x-touches-entities": [
          "attend.field_visits",
          "attend.geofence_attestations"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The field visit.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FieldVisit"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-devices": {
      "post": {
        "operationId": "attend.device.register",
        "summary": "Register a fixed attendance reader",
        "description": "Creates the `attend.devices` registry row — `serial_no` (tenant-unique), `name`, `vendor`, `model`, `work_location_id`, an optional `webhook_secret_ref`, and `status` ∈ `ACTIVE` · `OFFLINE` · `RETIRED` — and **ISSUES the reader's HMAC signing secret**.\n\nThe plaintext secret is in the `201` body and **nowhere else, ever**: only its bcrypt hash is stored, so a database dump is not a set of forgeable punch signers, and an operator who loses it rotates rather than looks it up. It is not written to the idempotency replay cache either — caching a secret-bearing response would put the plaintext at rest in a second table — so this operation's `Idempotency-Key` serialises concurrent registrations rather than replaying a stored body; a genuine replay is caught by the `(tenant_id, serial_no)` uniqueness and answers `409`, which is the correct answer: one reader must never end up with two live credentials.\n\nThe request never carries secret material. `webhook_secret_ref` is an optional **reference** to a credential an operator holds in their own KMS — the `org.statutory_config.credential_ref` precedent — and supplying a raw secret under any other key is a `422`.\n",
        "tags": [
          "attend",
          "device"
        ],
        "x-token": "attend.device.register",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S22"
        ],
        "x-touches-entities": [
          "attend.devices",
          "org.work_locations"
        ],
        "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,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttendanceDeviceRegister"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Device registered `ACTIVE`; it may begin pushing signed events immediately. **Carries the one-time `webhook_secret`** — configure it on the reader now, because it is not retrievable afterwards.\n",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceDeviceWithSecret"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "attend.device.list",
        "summary": "List registered attendance devices (with health)",
        "description": "The device board — `status`, `last_heartbeat_at`, `last_event_at` per reader. `ACTIVE → OFFLINE` is flipped by the `attend.device_health_sweep` job on heartbeat silence past the tenant's threshold, which emits `attend.device.health_changed`; this list is where an operator sees the result. Sort whitelist: `serial_no`, `last_heartbeat_at`, `-last_heartbeat_at`, `status`. Default `serial_no`.\n",
        "tags": [
          "attend",
          "device"
        ],
        "x-token": "attend.device.list",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S22",
          "ATT-S13"
        ],
        "x-touches-entities": [
          "attend.devices"
        ],
        "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,
        "parameters": [
          {
            "name": "work_location_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DeviceStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "$ref": "#/components/parameters/SortParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of devices with their health signals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceDevicePage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendance-devices/{id}": {
      "get": {
        "operationId": "attend.device.get",
        "summary": "Get one attendance device",
        "description": "The registry row and its health signals. `webhook_secret_ref` is returned as a reference; the secret itself is never serialised.",
        "tags": [
          "attend",
          "device"
        ],
        "x-token": "attend.device.get",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S22"
        ],
        "x-touches-entities": [
          "attend.devices"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The device.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceDevice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "attend.device.update",
        "summary": "Update a device's registration (location, model, secret reference)",
        "description": "Re-points a reader's `work_location_id`, corrects its nameplate, re-points the external `webhook_secret_ref`, or — with `rotate_secret: true` — **rotates the HMAC signing secret**, which returns the new plaintext ONCE and keeps the previous secret verifying for a bounded `drain_minutes` window (default 60, max 1440, `0` = revoke immediately). Rotation lives on this operation rather than a route of its own because this is already the operation defined as rotating the secret, and the permission catalogue is append-only: a freshly minted token would be held by nobody and would lock every existing operator out of rotation on the day they most need it. A rotation on a `RETIRED` device is a `409` — there is no credential to rotate; register a replacement.\n\n`status` is not settable here: `ACTIVE`/`OFFLINE` is owned by the health sweep and `RETIRED` by `attend.device.retire`, so a device can never be talked back online by an edit.\n",
        "tags": [
          "attend",
          "device"
        ],
        "x-token": "attend.device.update",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S22"
        ],
        "x-touches-entities": [
          "attend.devices"
        ],
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttendanceDeviceUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Device updated. Carries a one-time `webhook_secret` only when `rotate_secret` was set.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceDeviceMaybeSecret"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/attendance-devices/{id}/retire": {
      "post": {
        "operationId": "attend.device.retire",
        "summary": "Retire a device — its signature stops being accepted",
        "description": "`ACTIVE | OFFLINE → RETIRED`. The ingest endpoint answers `403` for a retired device from that instant, so decommissioning a reader is a revocation and not a hope. Already-staged `attend.device_events` rows are **kept** (append-only staging is the evidence behind a disputed punch, ADR 0032 §(c)) and remain normalizable.\n",
        "tags": [
          "attend",
          "device"
        ],
        "x-token": "attend.device.retire",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [
          "ATT-S22"
        ],
        "x-touches-entities": [
          "attend.devices"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "attend.device.health_changed",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Device retired; its HMAC signature is no longer accepted.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttendanceDevice"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/ingest/attendance-devices/{deviceId}/events": {
      "post": {
        "operationId": "attend.device_event.ingest",
        "summary": "Device-push ingest — HMAC-signed reader events (platform surface, NO user permission token)",
        "description": "**A device-as-client push, not a product-app operation** (ADR 0032 §(c)). This is the one endpoint in this file that does **not** authenticate a human: there is no bearer session, no `principal_class`, and no RBAC resolution — the caller is a gate reader, and its identity is the signature.\n\n**On `x-token`.** The corpus invariant is `x-token == operationId` on every operation (00 §4, the join key `coverage.py` reads), and the unauthenticated `platform.health.check` follows it too, so the field is populated here for the same structural reason. It is **not a grantable permission**: `attend.device_event.ingest` is never seeded into the tenant permission catalogue, never appears in the role→permission matrix, and **no principal can ever hold it**, because authorisation on this route is the HMAC signature and nothing else. Treat it as the operation's identity, not as an access token — the security-docs token catalogue is the place that must record the exclusion explicitly.\n\n**Auth scheme — `deviceHmac`, documented distinctly from `bearerJWT`.** The reader signs with the shared secret behind its `attend.devices.webhook_secret_ref`, using the timestamp + replay-window + idempotency discipline the `/platform/*` seam already defines (`04-idempotency-concurrency-webhooks.md` §4, ADR 0009 §b):\n1. **`X-Device-Signature`** — HMAC-SHA256 over\n   `timestamp + \"\\n\" + METHOD + \"\\n\" + path + \"\\n\" + SHA256(body)`, compared timing-safe.\n2. **`X-Device-Timestamp`** — enforced within **±300 s**; outside the window the request is rejected\n   before any work. Note this bounds the *transport*, not the events: see the backfill rule below.\n3. **`Idempotency-Key`** — required, with the standard replay window; a replayed batch stages the\n   same rows and never double-counts.\n4. A retired, unknown, wrong-tenant, wrongly-signed or stale request answers ONE opaque `401` —\n   never `403` and never `404` *(revised, issue #583: the earlier `403`-for-unknown-device rule is\n   superseded)*. Distinguishable failures make this endpoint a device ORACLE — `403` would mean\n   \"this id exists\" — letting an unauthenticated caller walk the id space and enumerate another\n   tenant's reader estate, including learning that a named gate's reader was just decommissioned.\n\n**Offline-buffered backfill is a first-class case, not an exception.** A reader that loses its uplink buffers locally and auto-flushes on reconnect, and a newly registered reader flushes its stored history the same way. This endpoint therefore **explicitly accepts events dated in the past, carrying their original device-side timestamps**, and normalization attributes each to **the day it happened, not the day it arrived**. Device-side clock skew is trusted at face value on backfill, with no server-side plausibility check — stated as known residue in ADR 0032, not as an oversight.\n\n**Nothing is interpreted at the edge, and nothing is deferred that a reader needs an answer about.** The staging write **completes inside the request** — hence `x-sync-async: sync` and a `200`, and hence no domain event: `attend.normalize_device_events` is a **drain job over staging, not an outbox consumer**, so there is no event for this operation to emit and `x-emits-event: null` is correct rather than an omission. The payload lands verbatim in append-only `attend.device_events` staging with its receipt timestamp, its original timestamp and a normalization status; the `attend.normalize_device_events` job then drains staging into punches and `attend.attendance_records` with `source = DEVICE`, idempotent per raw event. The raw row survives normalization so a disputed punch has evidence behind it and a normalizer bug is replayable rather than lossy. `DEVICE` is kept distinct from the pre-existing `BIOMETRIC` and `IMPORT` members: those are human-mediated, while `DEVICE` means *normalized from a signed push by a registered reader whose health the platform tracks* — collapsing them would make the strongest provenance in the system indistinguishable from a spreadsheet.\n\n**KSA scope has not been worked on at all in this wave** — device ingestion is specified against India site-labour practice; the KSA equivalents are untouched.\n",
        "tags": [
          "attend",
          "device_event"
        ],
        "x-token": "attend.device_event.ingest",
        "x-realizes-features": [
          "ATT-F09"
        ],
        "x-screens": [],
        "x-touches-entities": [
          "attend.device_events",
          "attend.devices"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": true,
        "x-entitlement": null,
        "x-provisional": null,
        "security": [
          {
            "deviceHmac": []
          }
        ],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "description": "The `attend.devices` registry id the signature is verified against. A retired/unknown device is `403`.",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "X-Device-Timestamp",
            "in": "header",
            "required": true,
            "description": "Request timestamp, checked against a ±300 s replay window. Bounds the transport, never the event timestamps inside the body.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceEventIngestRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch staged append-only into `attend.device_events` within this request. Fast acknowledgement — normalization is a later drain job and is never awaited by the reader.\n",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceEventIngestResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "423": {
            "$ref": "#/components/responses/Locked"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-balances/summary": {
      "get": {
        "operationId": "leave.leave_balance.summary",
        "summary": "My leave balance summary by type",
        "description": "Per-leave-type accrued/used/remaining, aggregated from the latest `leave.leave_balances` rows (append-only ledger — this is a read aggregate, never a stored balance, LVE-S01).\n**Every leave type the caller may apply for appears, ledger or no ledger.** The aggregate is driven by the ledger, and a type the employee has never accrued in has no ledger rows at all — so before this was zero-filled, the balance screen listed FEWER types than the picker (`GET /leave-types?applicable_to=me`) offered, and a client could only render a blank where a `0` belonged. Applicability is that same predicate: the PUBLISHED `org.leave_policies` row for the caller's own legal entity, honouring the policy's optional grade banding. Types the employee holds a ledger in are never dropped, even if the policy behind them has since been unpublished or re-banded — history does not disappear because entitlement changed.\n",
        "tags": [
          "leave",
          "leave_balance"
        ],
        "x-token": "leave.leave_balance.summary",
        "x-realizes-features": [
          "LVE-F01"
        ],
        "x-screens": [
          "LVE-S01"
        ],
        "x-touches-entities": [
          "leave.leave_balances",
          "org.leave_types",
          "org.leave_policies",
          "people.employees"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "fiscal_year",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One summary row per leave type applicable to the caller (zero-filled when the ledger is empty).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LeaveBalanceSummaryItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-balances": {
      "get": {
        "operationId": "leave.leave_balance.list",
        "summary": "My leave balance ledger (transaction history)",
        "description": "The append-only ledger detail behind a balance card — every accrual/debit/carry-forward/lapse/adjustment transaction (LVE-S01 \"type card → balance ledger detail\").",
        "tags": [
          "leave",
          "leave_balance"
        ],
        "x-token": "leave.leave_balance.list",
        "x-realizes-features": [
          "LVE-F01"
        ],
        "x-screens": [
          "LVE-S01"
        ],
        "x-touches-entities": [
          "leave.leave_balances"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "leave_type_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "txn_type",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/BalanceTxnType"
            }
          },
          {
            "name": "fiscal_year",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `effective_date`, `-effective_date`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of the caller's own ledger transactions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveBalanceTxnPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-balances/admin": {
      "get": {
        "operationId": "leave.leave_balance.list_admin",
        "summary": "HR's balance administration grid",
        "description": "Employee × leave-type balance grid for HR view/adjust (LVE-S08). Also the Employee-360 Leave tab's balance read, filtered to one employee (PPL-S14, issue #1158).\n",
        "tags": [
          "leave",
          "leave_balance"
        ],
        "x-token": "leave.leave_balance.list_admin",
        "x-realizes-features": [
          "LVE-F01"
        ],
        "x-screens": [
          "LVE-S08",
          "PPL-S14"
        ],
        "x-touches-entities": [
          "leave.leave_balances",
          "org.leave_types"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "tenant",
        "x-append-only": true,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "leave_type_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "fiscal_year",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `employee_id`, `leave_type_id`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant-wide page of balance summary rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveBalanceAdminItemPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-balances/adjustments": {
      "post": {
        "operationId": "leave.leave_balance.adjust",
        "summary": "Post a manual balance adjustment",
        "description": "HR correction posted as a new `ADJUSTMENT` ledger row (`note` required) — **never an in-place edit** of the append-only ledger (LVE-S08, db 05 §2.1).\n",
        "tags": [
          "leave",
          "leave_balance"
        ],
        "x-token": "leave.leave_balance.adjust",
        "x-realizes-features": [
          "LVE-F01"
        ],
        "x-screens": [
          "LVE-S08"
        ],
        "x-touches-entities": [
          "leave.leave_balances"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_balance.adjusted",
        "x-rls-scope": "tenant",
        "x-append-only": true,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaveBalanceAdjustmentCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Adjustment ledger row posted.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveBalanceTxn"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/leave-accrual-runs": {
      "post": {
        "operationId": "leave.leave_accrual.run",
        "summary": "Run the leave accrual writer for this tenant",
        "description": "**ADR 0035 §(a).** Posts one `ACCRUAL` ledger row per `(employee, policy, period)` that has none — monthly periods for a `MONTHLY` policy, the fiscal year for an `ANNUAL` one, and the finalized attendance cycles projected into `leave.worked_day_counts` for an `ACCRUAL_RULE` one. Every statutory number it applies (the 180-day eligibility gate, the 1-day-per-20-days-worked rate) is resolved from `org.leave_policies` and the compliance pack; a policy missing a parameter its method needs is skipped with a recorded reason rather than accrued on a hard-coded default.\n\n**Idempotent per `(employee, leave type, period)` and safe to re-trigger.** The quantity is always the SHORTFALL against what the fiscal year should have accrued by now, so a re-run posts nothing and an amended attendance cycle is picked up by the next period as a further `ACCRUAL` — the ledger is append-only, and a correction is a compensating row, never an edit.\n\nJobs-tier (XC-F08), so `202` + an `xc.job_runs` row: the sweep walks every employee and policy in the tenant. One in-flight run per tenant — a second call while one is queued or running is a `409`. `Idempotency-Key` is required and honoured: a retry with the same key and body replays the stored `202` with `Idempotency-Replayed: true` rather than queueing a second run; the same key with a different body is a `409`.\n",
        "tags": [
          "leave",
          "leave_accrual"
        ],
        "x-token": "leave.leave_accrual.run",
        "x-realizes-features": [
          "LVE-F01"
        ],
        "x-screens": [
          "LVE-S08"
        ],
        "x-touches-entities": [
          "leave.leave_balances",
          "leave.worked_day_counts",
          "org.leave_policies",
          "people.employees",
          "xc.job_runs"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "async",
        "x-emits-event": "leave.accrual_run.requested",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaveRunRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accrual run enqueued; poll `xc.job_runs` or `leave.leave_balance.list_admin`.",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveRunAccepted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-year-end-runs": {
      "post": {
        "operationId": "leave.leave_year_end.run",
        "summary": "Close the fiscal year — carry forward, then lapse",
        "description": "**ADR 0035 §(b).** Closes the fiscal year that has just ended, resolving the boundary from `org.legal_entities.fiscal_year_start_month` (`4` India, `1` KSA — pack-resolved, never a literal). Posts, per employee and leave type: `CARRY_FORWARD` capped per policy, a **second, uncapped** `CARRY_FORWARD` for days on applications marked `refused_for_business_reasons`, and `LAPSE` for the remainder. The order is fixed and load-bearing — the cap is applied to the balance NET of refused days, because an employer that refuses leave for its own operational reasons may not then let that leave expire.\n\nThe lapse is a **posted row an employee can see and question**, not an arithmetic silence. Idempotent per `(employee, leave type, closing fiscal year)`: a second run posts nothing. `force` closes a boundary the scheduled sweep has already moved past — an explicit operator act, because the closing balance it reads has since been moved by the new year's accruals. `Idempotency-Key` is required and honoured on the trigger itself: a retry with the same key and body replays the stored `202` with `Idempotency-Replayed: true`; the same key with a different body is a `409`.\n",
        "tags": [
          "leave",
          "leave_year_end"
        ],
        "x-token": "leave.leave_year_end.run",
        "x-realizes-features": [
          "LVE-F01"
        ],
        "x-screens": [
          "LVE-S08"
        ],
        "x-touches-entities": [
          "leave.leave_balances",
          "leave.leave_applications",
          "org.leave_policies",
          "people.employees",
          "xc.job_runs"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "async",
        "x-emits-event": "leave.year_end_run.requested",
        "x-rls-scope": "tenant",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaveYearEndRunRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Year-end close enqueued.",
            "headers": {
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveRunAccepted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-worked-day-counts": {
      "get": {
        "operationId": "leave.worked_day_count.list",
        "summary": "Days worked per employee per finalized attendance cycle",
        "description": "**db 05 §2.5.** The leave-local projection of `attend.attendance_cycle.finalized` / `.amended` that `leave.run_accruals` reads its input from — **worked days, not payable days and not present days**, carried from the signed snapshot with its `snapshot_hash`. Read-only: never written at request time.\n\nThis surface exists because the accrual writer depends on finalization being current, and a tenant that never finalizes a cycle accrues nothing — which is *correct* and will nonetheless read as a bug the first time it happens. It answers \"why hasn't this person accrued?\" with a row rather than a guess.\n",
        "tags": [
          "leave",
          "worked_day_count"
        ],
        "x-token": "leave.worked_day_count.list",
        "x-realizes-features": [
          "LVE-F01"
        ],
        "x-screens": [
          "LVE-S08"
        ],
        "x-touches-entities": [
          "leave.worked_day_counts"
        ],
        "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,
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "fiscal_year",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cycle_end[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "cycle_end[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of projected worked-day counts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkedDayCountPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-applications": {
      "post": {
        "operationId": "leave.leave_application.create",
        "summary": "Apply for leave",
        "description": "Type, dates, half-day, reason, optional document — projected to the unified approval inbox and routed to the manager (LVE-S02, XC-F12). `total_days` is server-computed. A published policy's optional `rules.monthly_limit_days` caps working days per calendar month, including pending and approved requests. Cross-month requests consume each month's quota separately. A 422 VALIDATION_FAILED with rule `monthly_limit` directs the employee to select Unpaid Leave for additional days. Approval rechecks the same ceiling; override authority does not bypass it. Unpaid approval marks attendance unpaid for payroll LOP; submitting a request alone does not deduct salary. Half-day requests must start and end on the same civil date; this is also checked on approval. A MUSTER worker in a tenant with `muster_ess_restricted` is refused 403 TOKEN_DENIED, `type: urn:groundit:problem:leave:not-offered`, before the balance or application is read.\n",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.create",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S02"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "org.leave_types",
          "org.leave_policies",
          "people.employees",
          "xc.approval_inbox"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_application.submitted",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaveApplicationCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Leave application created, `PENDING_APPROVAL`, and projected to the unified approval inbox.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "leave.leave_application.list",
        "summary": "My leave requests",
        "description": "The employee's leave applications and their approval status (LVE-S03).",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.list",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S03"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "leave.leave_approvals"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ApplicationStatus"
            }
          },
          {
            "name": "leave_type_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "start_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "start_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `start_date`, `-start_date`, `status`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of the caller's own leave applications.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplicationPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-applications/admin": {
      "get": {
        "operationId": "leave.leave_application.list_admin",
        "summary": "Admin leave read — tenant approval queue, or one employee's history",
        "description": "The HR/admin read of leave applications. `employee_id` selects between two shapes that arrived independently (#1158 and #1167) on one operation:\n\n**Omitted** — the tenant-wide admin approval queue (LVE-S07, issue #1167). Defaults to `status=PENDING_APPROVAL`, because a queue shows what is waiting rather than all history; pass `status` explicitly, or `unrouted`, to widen it. `unrouted=true` narrows to applications with no resolved approver or a routing failure — the queue an admin works to reroute.\n\n**Given** — that one employee's FULL application history with approval status, the Employee-360 Leave tab's read (PPL-S14, issue #1158). The pending-only default does NOT apply here: truncating a history read to open requests would misreport its own coverage.\n\nRows carry `leave_type_name`, `employee_name`, the pending step's `approver_id` / `approver_name`, and the `xc.approval_inbox` routing state by projection.\n",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.list_admin",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "PPL-S14",
          "LVE-S07"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "leave.leave_approvals",
          "org.leave_types",
          "people.employees",
          "xc.approval_inbox"
        ],
        "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,
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "description": "Omit for the tenant-wide queue; give it for one employee's history. Validated as a UUID when present.\n",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ApplicationStatus"
            }
          },
          {
            "name": "unrouted",
            "in": "query",
            "required": false,
            "description": "Queue filter (#1167). `true` = no resolved approver or a routing failure; `false` = routed cleanly. Supplying it also suppresses the pending-only default.\n",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "leave_type_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "start_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "start_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `start_date`, `-start_date`, `status`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of leave applications — tenant-wide, or the named employee's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplicationPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-applications/preview": {
      "post": {
        "operationId": "leave.leave_application.preview",
        "summary": "What this leave range will cost",
        "description": "The chargeable day count for a range, computed by the SERVER, before anything is submitted (issue #1626). Nothing is created, reserved or mutated; the operation is a read that happens to carry a body, and it is safe to repeat.\n\n**Why it exists.** Until this operation there was no way to ask, so every client counted locally and each got a different number — the web mirrored the server's then hard-coded Saturday/Sunday loop and hid the figure entirely when the holiday calendar could not be read, and the mobile app's own copy counted weekend days. A count is a server fact: it depends on the employee's `attend.schedules` week-off pattern, on the ACTIVE holiday calendars of their legal entity and work location, and on the optional holidays they personally claimed — none of which a client can see in full. **Clients display `days`; they must not recompute it.**\n\n**It is the same computation `POST /leave-applications` charges and the post-approval projection writes**, so the number previewed here is the `total_days` of the application that range produces.\n\n`excluded` names every date inside the range that costs nothing and why: `WEEK_OFF` (the employee's own rest day — never a hard-coded Saturday/Sunday) or `HOLIDAY` (an ACTIVE, entity- and location-scoped holiday, or an optional holiday this employee has already claimed). A range that is entirely non-working returns `days: \"0.00\"`, which is exactly what `POST /leave-applications` refuses with rule `format`.\n",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.preview",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S02"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "attend.schedules",
          "attend.shift_assignments",
          "org.holiday_calendars",
          "org.legal_entities",
          "people.employees"
        ],
        "x-idempotent": false,
        "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,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaveDayPreviewRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The day count for the range, and the dates that cost nothing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveDayPreview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-applications/{id}": {
      "get": {
        "operationId": "leave.leave_application.get",
        "summary": "Get one leave request",
        "description": "Row → request detail (LVE-S03).",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.get",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S03"
        ],
        "x-touches-entities": [
          "leave.leave_applications"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The leave application.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-applications/{id}/cancel": {
      "post": {
        "operationId": "leave.leave_application.cancel",
        "summary": "Cancel a leave request (pre-approval)",
        "description": "Withdraws an application before it is decided → `CANCELLED` (LVE-S03).",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.cancel",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S03"
        ],
        "x-touches-entities": [
          "leave.leave_applications"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_application.cancelled",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/leave-applications/{id}/withdraw": {
      "post": {
        "operationId": "leave.leave_application.withdraw",
        "summary": "Withdraw a leave request (post-approval)",
        "description": "Withdraws an already-approved application → `WITHDRAWN`, posting a `CREDIT_REVERSAL` to the leave ledger (LVE-S03, LVE-F01).",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.withdraw",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S03"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "leave.leave_balances"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_application.withdrawn",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawn; a `CREDIT_REVERSAL` ledger row restores the balance.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/leave-applications/pending-approval": {
      "get": {
        "operationId": "leave.leave_application.list_for_approval",
        "summary": "Manager's leave approval queue",
        "description": "Leave applications awaiting decision, with balance + overlap check surfaced for the decision (LVE-S07).",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.list_for_approval",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S07"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "leave.leave_approvals"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ApplicationStatus"
            }
          },
          {
            "name": "leave_type_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `start_date`, `status`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "legal_entity_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this legal entity (branch). Combines with `department_id` and `org_unit_id`, and REPLACES the caller's direct-report bound rather than intersecting with it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "department_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this department. Combines with `legal_entity_id` and `org_unit_id`. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "org_unit_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this org unit AND its sub-units — the unit's org-tree closure (`app.org_unit_closure`, migration 0162), so a department's parent unit includes the teams beneath it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "`ALL` — every ACTIVE employee in the tenant, with no axis narrowing it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "type": "string",
              "enum": [
                "ALL"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of leave applications in the manager's approval scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplicationPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-applications/{id}/approve": {
      "post": {
        "operationId": "leave.leave_application.approve",
        "summary": "Approve a leave application (approval step)",
        "description": "Writes a `leave_approvals` step; when all steps clear, the application flips `APPROVED`, posts a `DEBIT` to `leave_balances`, and marks the day(s) `ON_LEAVE` on `attend.attendance_records` by event (LVE-S07). Maker-checker + delegation honoured (XC-F12/XC-F14).\n",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.approve",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S07"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "leave.leave_approvals",
          "leave.leave_balances"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_application.approved",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approval step recorded (and, if final, the application approved + balance debited).",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/leave-applications/{id}/reject": {
      "post": {
        "operationId": "leave.leave_application.reject",
        "summary": "Reject a leave application",
        "description": "Declines the application (terminal) — no debit posted (LVE-S07).",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.reject",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S07"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "leave.leave_approvals"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_application.rejected",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/leave-applications/{id}/return": {
      "post": {
        "operationId": "leave.leave_application.return",
        "summary": "Return a leave application for changes",
        "description": "Sends the application back to the employee for edits (`leave.approval_decision = RETURNED`, LVE-S07).",
        "tags": [
          "leave",
          "leave_application"
        ],
        "x-token": "leave.leave_application.return",
        "x-realizes-features": [
          "LVE-F02"
        ],
        "x-screens": [
          "LVE-S07"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "leave.leave_approvals"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_application.returned",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Returned to the employee.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/comp-offs": {
      "post": {
        "operationId": "leave.comp_off.create",
        "summary": "Submit a comp-off credit request",
        "description": "Compensatory-off earned for working a holiday/rest day, tied to the worked attendance day (ATT-F01) (LVE-S05).",
        "tags": [
          "leave",
          "comp_off"
        ],
        "x-token": "leave.comp_off.create",
        "x-realizes-features": [
          "LVE-F04"
        ],
        "x-screens": [
          "LVE-S05"
        ],
        "x-touches-entities": [
          "leave.comp_off",
          "attend.attendance_records",
          "org.leave_types",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.comp_off.submitted",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompOffCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Comp-off credit request created, `PENDING_APPROVAL`.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompOff"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "leave.comp_off.list",
        "summary": "Comp-off approval queue",
        "description": "HR/manager's comp-off tab — decide credits before they lapse (LVE-S10). `team`-scoped, but also granted to `employee` (#995): the RLS ownership policy already includes the caller's own rows unconditionally, so an employee reads back the requests they raised while a manager additionally sees their team's.",
        "tags": [
          "leave",
          "comp_off"
        ],
        "x-token": "leave.comp_off.list",
        "x-realizes-features": [
          "LVE-F04"
        ],
        "x-screens": [
          "LVE-S10"
        ],
        "x-touches-entities": [
          "leave.comp_off"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/CompOffStatus"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "worked_date[from]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "name": "worked_date[to]",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/DateOnlyRef"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `worked_date`, `status`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "legal_entity_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this legal entity (branch). Combines with `department_id` and `org_unit_id`, and REPLACES the caller's direct-report bound rather than intersecting with it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "department_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this department. Combines with `legal_entity_id` and `org_unit_id`. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "org_unit_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this org unit AND its sub-units — the unit's org-tree closure (`app.org_unit_closure`, migration 0162), so a department's parent unit includes the teams beneath it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "`ALL` — every ACTIVE employee in the tenant, with no axis narrowing it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "type": "string",
              "enum": [
                "ALL"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of comp-off requests in the caller's scope (their own, plus their team's for a manager).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompOffPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/comp-offs/{id}/approve": {
      "post": {
        "operationId": "leave.comp_off.approve",
        "summary": "Approve a comp-off credit",
        "description": "Posts a `COMP_OFF_CREDIT` row to the leave ledger, available to avail (LVE-S10, LVE-F01).",
        "tags": [
          "leave",
          "comp_off"
        ],
        "x-token": "leave.comp_off.approve",
        "x-realizes-features": [
          "LVE-F04"
        ],
        "x-screens": [
          "LVE-S10"
        ],
        "x-touches-entities": [
          "leave.comp_off",
          "leave.leave_balances"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.comp_off.approved",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved; comp-off credited to the ledger.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompOff"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/comp-offs/{id}/reject": {
      "post": {
        "operationId": "leave.comp_off.reject",
        "summary": "Reject a comp-off credit request",
        "description": "Declines the comp-off credit (LVE-S10).",
        "tags": [
          "leave",
          "comp_off"
        ],
        "x-token": "leave.comp_off.reject",
        "x-realizes-features": [
          "LVE-F04"
        ],
        "x-screens": [
          "LVE-S10"
        ],
        "x-touches-entities": [
          "leave.comp_off"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.comp_off.rejected",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompOff"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/leave-encashments": {
      "post": {
        "operationId": "leave.leave_encashment.create",
        "summary": "Request leave encashment",
        "description": "Convert eligible unused balance to cash; `rate_per_day_amount`/`encashment_amount` are server-computed from leave-policy config (ORG-F05) (LVE-S06).",
        "tags": [
          "leave",
          "leave_encashment"
        ],
        "x-token": "leave.leave_encashment.create",
        "x-realizes-features": [
          "LVE-F05"
        ],
        "x-screens": [
          "LVE-S06"
        ],
        "x-touches-entities": [
          "leave.leave_encashments",
          "org.leave_types",
          "org.leave_policies",
          "people.employees"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_encashment.submitted",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaveEncashmentCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Encashment request created, `PENDING_APPROVAL`.",
            "headers": {
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveEncashment"
                }
              }
            }
          },
          "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"
          }
        }
      },
      "get": {
        "operationId": "leave.leave_encashment.list",
        "summary": "Encashment approval queue",
        "description": "HR/manager/Finance's encashment tab (LVE-S10). `team`-scoped, but also granted to `employee` (#995): the RLS ownership policy already includes the caller's own rows unconditionally, so an employee reads back the encashments they raised while a manager additionally sees their team's.",
        "tags": [
          "leave",
          "leave_encashment"
        ],
        "x-token": "leave.leave_encashment.list",
        "x-realizes-features": [
          "LVE-F05"
        ],
        "x-screens": [
          "LVE-S10"
        ],
        "x-touches-entities": [
          "leave.leave_encashments"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": null,
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/EncashmentStatus"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "pay_period",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          },
          {
            "$ref": "#/components/parameters/PageBefore"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort whitelist: `created_at`, `-created_at`, `status`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "legal_entity_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this legal entity (branch). Combines with `department_id` and `org_unit_id`, and REPLACES the caller's direct-report bound rather than intersecting with it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "department_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this department. Combines with `legal_entity_id` and `org_unit_id`. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "org_unit_id",
            "in": "query",
            "required": false,
            "description": "Widen the queue to every ACTIVE employee in this org unit AND its sub-units — the unit's org-tree closure (`app.org_unit_closure`, migration 0162), so a department's parent unit includes the teams beneath it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "`ALL` — every ACTIVE employee in the tenant, with no axis narrowing it. Requires a tenant-wide grant on this operation (403 otherwise).",
            "schema": {
              "type": "string",
              "enum": [
                "ALL"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of encashment requests in the caller's scope (their own, plus their team's for a manager).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveEncashmentPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/leave-encashments/{id}/approve": {
      "post": {
        "operationId": "leave.leave_encashment.approve",
        "summary": "Approve a leave encashment",
        "description": "Posts an `ENCASHMENT_DEBIT` to the leave ledger; the payout feeds payroll by event (PAY-F04 / full-&-final PAY-F07) (LVE-S10).",
        "tags": [
          "leave",
          "leave_encashment"
        ],
        "x-token": "leave.leave_encashment.approve",
        "x-realizes-features": [
          "LVE-F05"
        ],
        "x-screens": [
          "LVE-S10"
        ],
        "x-touches-entities": [
          "leave.leave_encashments",
          "leave.leave_balances"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_encashment.approved",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved; ledger debited, payout raised by event.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveEncashment"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/leave-encashments/{id}/reject": {
      "post": {
        "operationId": "leave.leave_encashment.reject",
        "summary": "Reject a leave encashment",
        "description": "Declines the payout request (LVE-S10).",
        "tags": [
          "leave",
          "leave_encashment"
        ],
        "x-token": "leave.leave_encashment.reject",
        "x-realizes-features": [
          "LVE-F05"
        ],
        "x-screens": [
          "LVE-S10"
        ],
        "x-touches-entities": [
          "leave.leave_encashments"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_encashment.rejected",
        "x-rls-scope": "team",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApprovalDecisionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveEncashment"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/holidays/me": {
      "get": {
        "operationId": "leave.holiday.list_me",
        "summary": "My holiday calendar",
        "description": "This employee's own holidays, resolved from their legal entity and work location.\n\n**Resolution.** Entity-wide (`work_location_id IS NULL`) and location-specific calendars are **additive**, which is the rule `attend` already materialises `HOLIDAY` attendance days by — the two must never disagree about what a day was. Only `ACTIVE` calendars participate: a `DRAFT` calendar is still being authored and is not yet a fact anybody may be shown. One row per date, the most specific calendar winning the label.\n\n**Optional dates are included and marked.** `is_optional` is true for a `RESTRICTED` holiday (India's restricted-holiday convention) — it is a day the employee MAY take, not one they are given, so it is badged rather than counted. `is_claimed` says whether this employee has claimed it (`POST /optional-holidays/claims`).\n",
        "tags": [
          "leave",
          "holiday"
        ],
        "x-token": "leave.holiday.list_me",
        "x-realizes-features": [
          "LVE-F03"
        ],
        "x-screens": [
          "LVE-S04"
        ],
        "x-touches-entities": [
          "org.holiday_calendars",
          "people.employees",
          "leave.leave_applications"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive civil lower bound. Omitted means the whole authored calendar.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive civil upper bound. Omitted means the whole authored calendar.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "fiscal_year",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's own holidays, one row per date, ascending.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EmployeeHoliday"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/optional-holidays/me": {
      "get": {
        "operationId": "leave.optional_holiday.list_me",
        "summary": "My optional (restricted) holidays and the quota behind them",
        "description": "The optional dates on this employee's own calendar, which of them they have claimed, and the quota the \"N of M claimed\" line is drawn from (#1636).\n\n**`quota` is null when the tenant has authored no optional-holiday leave type** — a different fact from \"you have none left\", and the card hides on it rather than rendering a zero the employee can do nothing about. When present, `quota_total` is what the ledger granted and `quota_remaining` what is still claimable; both are exact decimal day strings.\n",
        "tags": [
          "leave",
          "holiday"
        ],
        "x-token": "leave.optional_holiday.list_me",
        "x-realizes-features": [
          "LVE-F03"
        ],
        "x-screens": [
          "LVE-S04"
        ],
        "x-touches-entities": [
          "org.holiday_calendars",
          "org.leave_types",
          "leave.leave_balances",
          "leave.leave_applications",
          "people.employees"
        ],
        "x-idempotent": false,
        "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,
        "parameters": [
          {
            "name": "fiscal_year",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's optional holidays and their remaining quota.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "quota"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EmployeeHoliday"
                      }
                    },
                    "quota": {
                      "anyOf": [
                        {
                          "$ref": "#/components/schemas/OptionalHolidayQuota"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/optional-holidays/claims": {
      "post": {
        "operationId": "leave.optional_holiday.claim",
        "summary": "Claim one optional holiday (self-service, no approval)",
        "description": "The employee picks one of their own optional dates, up to their quota. **Self-service — there is no approval step and the claim enters nobody's approval inbox**, which is the owner's ruling for this pass.\n\n**A claim is an auto-approved one-day leave application** on the tenant's optional-holiday leave type (`org.leave_types.category = 'OPTIONAL_HOLIDAY'`), approved inside this request's own transaction. That is deliberate rather than a bespoke claims table: the quota is already the employee's ledger on that type, and every downstream consumer — the attendance day record, `attend.leave_day_marks`, `xc.approved_leave_projection`, payroll's paid/unpaid read — already handles approved leave. The response is therefore a `LeaveApplication`, carrying `is_optional_holiday_claim: true`.\n\n**Refusals** are `422` with a per-field `rule` a client can branch on: `exists` (not an optional date on YOUR calendar), `range` (before today in the tenant's timezone), `not_entitled` (no optional-holiday leave type configured), `insufficient` (quota exhausted), `overlap` (a live leave request — including a claim you already made — covers that day).\n",
        "tags": [
          "leave",
          "holiday"
        ],
        "x-token": "leave.optional_holiday.claim",
        "x-realizes-features": [
          "LVE-F03"
        ],
        "x-screens": [
          "LVE-S04"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "leave.leave_balances",
          "org.leave_types",
          "org.holiday_calendars",
          "attend.leave_day_marks",
          "attend.attendance_records"
        ],
        "x-idempotent": false,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_application.approved",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OptionalHolidayClaimCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The claim, as the approved one-day leave application it is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          },
          "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"
          }
        }
      }
    },
    "/optional-holidays/claims/{id}": {
      "delete": {
        "operationId": "leave.optional_holiday.revoke",
        "summary": "Give a claimed optional holiday back",
        "description": "Allowed **up to and including the date itself** — an employee who wakes up on the festival and decides to work may still release it; the day after, `attend` has settled that day and the ledger credit would be rewriting history (`409`).\n\nRuns the same reversal `POST /leave-applications/{id}/withdraw` does: a `CREDIT_REVERSAL` row restores the quota (the ledger is append-only, so the debit stays and is answered rather than deleted) and the post-approval projection is reversed. `{id}` is the claim's leave-application id. A row that is not yours, does not exist, or is an ordinary leave application answers one opaque `404` — an ordinary application is cancelled through its own door.\n",
        "tags": [
          "leave",
          "holiday"
        ],
        "x-token": "leave.optional_holiday.revoke",
        "x-realizes-features": [
          "LVE-F03"
        ],
        "x-screens": [
          "LVE-S04"
        ],
        "x-touches-entities": [
          "leave.leave_applications",
          "leave.leave_balances",
          "org.leave_types",
          "attend.leave_day_marks",
          "attend.attendance_records"
        ],
        "x-idempotent": true,
        "x-market": "both",
        "x-sync-async": "sync",
        "x-emits-event": "leave.leave_application.withdrawn",
        "x-rls-scope": "self",
        "x-append-only": false,
        "x-entitlement": null,
        "x-provisional": null,
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The released claim, now `WITHDRAWN`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          },
          "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"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerJWT": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Product session token. Two mint paths, one contract (ADR 0010): employees/managers authenticate against Keycloak (mobile + web); workspace staff arrive from the One portal via bridge-token SSO (`POST /api/sso/exchange` verifies the platform's Ed25519 token and mints the product session). The token carries IDENTITY ONLY — `sub`, `tenantUid`, `principal_class`, MFA level, session ref, `exp`. Roles/permissions are re-resolved server-side per request. Enforcement is layered: gateway (TLS/WAF/routing only — NEVER trusted for auth) → NestJS auth guard (validates token, builds the request auth-context) → entitlement middleware (subscription-status → feature-flag → numeric-limit, ADR 0009) → `SET LOCAL app.tenant_id` / `app.user_id` → Postgres FORCED RLS. `tenantUid` is NEVER a path, query, or body parameter.\n"
      },
      "deviceHmac": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Device-Signature",
        "description": "**Device-as-client authentication for `POST /ingest/attendance-devices/{deviceId}/events` ONLY** (ADR 0032 §(c)). Declared separately from `bearerJWT` because the caller is a **registered fixed biometric reader, not a person**: there is no product session, no `principal_class`, no RBAC resolution, and consequently **no permission token** on the operation it guards.\n\n**Two headers, and both are required** *(as-built, issue #583)*. The device's secret is stored as a one-way **bcrypt hash** (`attend.devices.webhook_secret_hash`) so that a database dump is not a set of forgeable punch signers — and a one-way hash cannot serve as an HMAC key. The reader therefore **presents** the secret and **proves the request** with it, exactly as ADR 0009 §b defines for the inbound `/platform/*` seam:\n\n1. `Authorization: Bearer <device secret>` — bcrypt-compared against the device's current hash and,\n   while a rotation drain window is open, its `_PREV` hash. Possession of the credential.\n\n2. `X-Device-Signature` — HMAC-SHA256 over\n   `timestamp + \"\\n\" + METHOD + \"\\n\" + path + \"\\n\" + SHA256(body)`, **keyed by that same presented\n   secret**, hex-lowercase, compared timing-safe. This is what the bearer alone cannot do: it binds the\n   credential to this exact method, path and byte-for-byte body, so a captured header cannot be\n   re-pointed at a different batch.\n\n3. `X-Device-Timestamp` — epoch **milliseconds**, enforced inside a ±300 s replay window, and\n   `Idempotency-Key` required on the push.\n\n\n`attend.devices.webhook_secret_ref` remains a **pointer into an external KMS/secret store, never material held in the business database** (the `org.statutory_config.credential_ref` precedent) and is optional: the platform issues the signing secret itself and returns the plaintext **exactly once**, on the registration or rotation response.\n\n**Every authentication failure answers one opaque `401`** — unknown device, retired device, malformed or wrong bearer, bad signature, stale timestamp *(revised, issue #583; the earlier `403`-for-unknown- device rule is superseded)*. A `403`/`401` split is a device-enumeration **oracle**: `403` would mean \"this id exists\", letting an unauthenticated caller walk the id space and learn a tenant's reader estate, including that a named gate's reader was just decommissioned. `404` is likewise never used. Product-app clients never use this scheme, and no other operation in this file accepts it.\n"
      }
    },
    "schemas": {
      "LateEntryRequest": {
        "type": "object",
        "required": [
          "id",
          "employee_id",
          "work_date",
          "status",
          "attempted_at",
          "expires_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "employee_id": {
            "type": "string",
            "format": "uuid"
          },
          "employee_name": {
            "type": "string"
          },
          "work_date": {
            "type": "string",
            "format": "date"
          },
          "shift_assignment_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "BLOCKED",
              "PENDING",
              "APPROVED",
              "REJECTED",
              "CONSUMED",
              "SUPERSEDED",
              "LAPSED"
            ],
            "description": "#1878: `SUPERSEDED` — an HR day override decided the day first; `LAPSED` — undecided (or approved and unused) at 00:00 local on the day after the work date (`expires_at`).\n"
          },
          "decision_outcome": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "FULL_DAY",
              "HALF_DAY",
              null
            ],
            "description": "The manager's approved outcome; it locks the day's status once the clock-in consumes the appeal."
          },
          "attempted_at": {
            "type": "string",
            "format": "date-time"
          },
          "client_captured_at": {
            "type": "string",
            "format": "date-time"
          },
          "shift_start_at": {
            "type": "string",
            "format": "date-time"
          },
          "cutoff_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "attempted_late_minutes": {
            "type": "integer"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "decided_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "decision_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "actual_clock_in_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "actual_late_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "consumed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "version": {
            "type": "integer"
          }
        }
      },
      "UuidRef": {
        "$ref": "#/components/schemas/Uuid"
      },
      "DateOnlyRef": {
        "$ref": "#/components/schemas/DateOnly"
      },
      "FileDownloadRef": {
        "$ref": "#/components/schemas/FileDownload"
      },
      "AttendanceStatus": {
        "type": "string",
        "enum": [
          "PENDING",
          "PRESENT",
          "HALF_DAY",
          "ABSENT",
          "ON_LEAVE",
          "HOLIDAY",
          "WEEKLY_OFF",
          "ON_FIELD"
        ],
        "description": "attend.attendance_status (db 05 §1.1)."
      },
      "AttendanceSource": {
        "type": "string",
        "enum": [
          "MOBILE_GEOFENCE",
          "WEB",
          "BIOMETRIC",
          "REGULARIZED",
          "SYSTEM",
          "IMPORT",
          "MUSTER",
          "DEVICE"
        ],
        "description": "'attend.attendance_source (db 05 §1.1).' **`MUSTER` and `DEVICE` are append-only enum growth from the pay/attend wave (ADR 0032 §(f)).** `MUSTER` = a supervisor recorded this day on someone's behalf and an approval chain signed it; `DEVICE` = normalized from a signed push by a registered reader whose health the platform tracks. The pre-existing `BIOMETRIC` (a hand-recorded biometric result) and `IMPORT` (a file-loaded register) are retained unchanged — both human-mediated. Collapsing device push into `BIOMETRIC` would make the strongest provenance in the system indistinguishable from a spreadsheet, and `SYSTEM` remains what `attend.materialize_daily_records` writes for a day nobody punched (ADR 0028 §(f)). The source column is how a downstream consumer — payroll, an inspector, the EWA underwriting signal — weighs a day's evidentiary class.\n**A day materialised by `POST /regularizations` (#1634) is a `SYSTEM` row**, not a new value: it is byte-for-byte what `attend.materialize_daily_records` would have written for that employee-day (`is_regularized = false`, status from the same calendar derivation), so the nightly planner may still re-derive it and every `source IN ('SYSTEM', 'IMPORT')` guard keeps admitting it. `REGULARIZED` arrives only on APPROVAL, when the corrected times are actually written onto the day.\n"
      },
      "PunchType": {
        "type": "string",
        "enum": [
          "IN",
          "OUT"
        ],
        "description": "attend.punch_type (db 05 §1.1)."
      },
      "GeofenceResult": {
        "type": "string",
        "enum": [
          "INSIDE",
          "OUTSIDE",
          "UNKNOWN"
        ],
        "description": "attend.geofence_result — handset claim on input; server-recomputed verdict when coordinates and a configured geofence are available (ADR 0025, db 05 §1.1)."
      },
      "FaceResult": {
        "type": "string",
        "enum": [
          "MATCH",
          "NO_MATCH",
          "SPOOF_SUSPECTED",
          "SKIPPED"
        ],
        "description": "attend.face_result — handset claim on input; server-overridden verdict when a fresh photo and HR-approved ACTIVE enrolment are available (ADR 0025, db 05 §1.1)."
      },
      "OutOfZoneType": {
        "type": "string",
        "enum": [
          "OUT_OF_ZONE",
          "LOCATION_UNKNOWN",
          "MISSED_PUNCH",
          "LATE_IN",
          "EARLY_OUT"
        ],
        "description": "attend.out_of_zone_type (db 05 §1.1)."
      },
      "OutOfZoneStatus": {
        "type": "string",
        "enum": [
          "PENDING",
          "APPROVED",
          "REJECTED"
        ],
        "description": "attend.out_of_zone_status (db 05 §1.1)."
      },
      "ScheduleStatus": {
        "type": "string",
        "enum": [
          "DRAFT",
          "ACTIVE",
          "SUPERSEDED",
          "ARCHIVED"
        ],
        "description": "attend.schedule_status (db 05 §1.2)."
      },
      "ShiftAssignmentStatus": {
        "type": "string",
        "enum": [
          "SCHEDULED",
          "ACTIVE",
          "COMPLETED",
          "SWAPPED",
          "CANCELLED"
        ],
        "description": "attend.shift_assignment_status (db 05 §1.2)."
      },
      "OvertimeStatus": {
        "type": "string",
        "enum": [
          "DRAFT",
          "PENDING_APPROVAL",
          "APPROVED",
          "REJECTED",
          "CANCELLED",
          "PROCESSED"
        ],
        "description": "attend.overtime_status (db 05 §1.3)."
      },
      "RegularizationType": {
        "type": "string",
        "enum": [
          "MISSED_PUNCH",
          "WRONG_LOCATION",
          "WRONG_TIME",
          "DEVICE_FAILURE",
          "OTHER"
        ],
        "description": "attend.regularization_type (db 05 §1.3)."
      },
      "RegularizationStatus": {
        "type": "string",
        "enum": [
          "DRAFT",
          "PENDING_APPROVAL",
          "APPROVED",
          "REJECTED",
          "CANCELLED"
        ],
        "description": "attend.regularization_status (db 05 §1.3)."
      },
      "BalanceTxnType": {
        "type": "string",
        "enum": [
          "OPENING_BALANCE",
          "ACCRUAL",
          "DEBIT",
          "CREDIT_REVERSAL",
          "CARRY_FORWARD",
          "LAPSE",
          "COMP_OFF_CREDIT",
          "ENCASHMENT_DEBIT",
          "ADJUSTMENT"
        ],
        "description": "leave.balance_txn_type (db 05 §2.1)."
      },
      "HalfDaySession": {
        "type": "string",
        "enum": [
          "FIRST_HALF",
          "SECOND_HALF"
        ],
        "description": "leave.half_day_session (db 05 §2.2)."
      },
      "ApplicationStatus": {
        "type": "string",
        "enum": [
          "DRAFT",
          "PENDING_APPROVAL",
          "APPROVED",
          "REJECTED",
          "CANCELLED",
          "WITHDRAWN"
        ],
        "description": "leave.application_status (db 05 §2.2)."
      },
      "ApprovalDecision": {
        "type": "string",
        "enum": [
          "PENDING",
          "APPROVED",
          "REJECTED",
          "RETURNED"
        ],
        "description": "leave.approval_decision (db 05 §2.2)."
      },
      "CompOffStatus": {
        "type": "string",
        "enum": [
          "EARNED",
          "PENDING_APPROVAL",
          "APPROVED",
          "AVAILED",
          "EXPIRED",
          "REJECTED"
        ],
        "description": "leave.comp_off_status (db 05 §2.3)."
      },
      "EncashmentStatus": {
        "type": "string",
        "enum": [
          "DRAFT",
          "PENDING_APPROVAL",
          "APPROVED",
          "REJECTED",
          "PROCESSED",
          "PAID",
          "CANCELLED"
        ],
        "description": "leave.encashment_status (db 05 §2.4)."
      },
      "HolidayType": {
        "type": "string",
        "enum": [
          "NATIONAL",
          "REGIONAL",
          "OPTIONAL"
        ],
        "description": "org.holiday_calendars holiday classification, as surfaced on LVE-S04 (\"date · name · type: national/regional/optional\")."
      },
      "GeofenceCapture": {
        "type": "object",
        "description": "Geofence evaluation for one punch (attend.geofence_attestations, db 05 §1.1). The handset computes `result`/`distance_m`, but under ADR 0025 the server RECOMPUTES containment from `latitude`/`longitude` against the resolved org.geofences boundary and overrides both; a client/server disagreement is flagged and marks the day exceptional. With no coordinates (offline UNKNOWN) the client result stands.",
        "required": [
          "result",
          "captured_at"
        ],
        "additionalProperties": false,
        "properties": {
          "work_location_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "geofence_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "result": {
            "$ref": "#/components/schemas/GeofenceResult"
          },
          "latitude": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,6})?$",
            "description": "numeric(9,6) decimal degrees, as a string."
          },
          "longitude": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,6})?$",
            "description": "numeric(9,6) decimal degrees, as a string."
          },
          "accuracy_m": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,2})?$",
            "description": "numeric(9,2) GPS accuracy radius in metres, as a string."
          },
          "distance_m": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,2})?$",
            "description": "numeric(9,2) distance from the geofence boundary in metres, as a string."
          },
          "captured_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "device_meta": {
            "type": "object",
            "description": "On-device capture context (device_id, os, app_version, is_mock_location, network, battery_pct) — schemaless snapshot, never queried/joined (db 05 §1.1).",
            "additionalProperties": true
          }
        }
      },
      "FaceCapture": {
        "type": "object",
        "description": "Face-attestation payload for one punch (attend.face_attestations, db 05 §1.1). The handset still runs its own match and reports it in `result` — but under **ADR 0025** that is a CLAIM, not the verdict. When `photo_file_id` is present the server re-derives the match against the employee's stored enrolment (`attend.face_enrollments`) and **overrides** `result` and `confidence` with its own. When it is absent — an offline punch, or a client built before ADR 0025 — the client's claim is kept and the punch is flagged `FACE_UNVERIFIED_SERVER`. A raw image or biometric template is still **never** sent inline: `photo_storage_key` names an object already uploaded through `xc.file.request_upload_url`.\n",
        "required": [
          "result",
          "captured_at"
        ],
        "additionalProperties": false,
        "properties": {
          "result": {
            "$ref": "#/components/schemas/FaceResult"
          },
          "photo_storage_key": {
            "type": "string",
            "maxLength": 512,
            "description": "Object key of the punch photo the app already uploaded via `xc.file.request_upload_url` (under the caller's own `<tenantUid>/uploads/` prefix). Present ⇒ the server embeds it and OVERRIDES `result`/`confidence`. Absent ⇒ the client's claim stands, flagged `FACE_UNVERIFIED_SERVER`, and the day is exceptional (ADR 0025 §5). **Each photo is single-use.** A photo whose bytes the server has already accepted for this employee — including the enrolment image itself — is a replay, not a capture: it yields `SPOOF_SUSPECTED` + `FACE_REPLAY_SUSPECTED` and an exceptional day. Send a fresh capture per punch; never re-send a stored one (ADR 0025 §5a).\n"
          },
          "liveness_passed": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "The device's own liveness verdict. **Required only when the CLIENT claims `MATCH`/ `NO_MATCH` in `result`** — it backs the claim the handset is making. A client that does no on-device matching and lets the server decide (`result: SKIPPED` + `photo_storage_key`) omits it, and the server-computed `MATCH`/`NO_MATCH` is stored with a NULL liveness and `verified_by: server`: liveness is observable only at capture, so the server has no verdict to record and will not synthesise one (#1530, migration `0216`).\n"
          },
          "confidence": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^-?\\d+(\\.\\d{1,6})?$",
            "description": "Match confidence 0–1, numeric(9,6) as a string. Overwritten by the server verdict when `photo_storage_key` is supplied."
          },
          "captured_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "attestation_meta": {
            "type": "object",
            "description": "SDK/result metadata (sdk, sdk_version, model_on_device, liveness_method, threshold, duration_ms) — never image bytes or a biometric template. The server adds its own `server_verification` block (verified_by, similarity, distance, threshold, model_version) on the stored row (db 05 §1.1, ADR 0025).",
            "additionalProperties": true
          }
        }
      },
      "AttendancePunchInput": {
        "type": "object",
        "description": "One on-device punch attestation, keyed by the client-generated offline-queue idempotency id.",
        "required": [
          "client_punch_id",
          "punch_type",
          "geofence",
          "face"
        ],
        "additionalProperties": false,
        "properties": {
          "client_punch_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "punch_type": {
            "$ref": "#/components/schemas/PunchType"
          },
          "geofence": {
            "$ref": "#/components/schemas/GeofenceCapture"
          },
          "face": {
            "$ref": "#/components/schemas/FaceCapture"
          }
        }
      },
      "AttendancePunchSyncRequest": {
        "type": "object",
        "required": [
          "punches"
        ],
        "additionalProperties": false,
        "properties": {
          "punches": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/AttendancePunchInput"
            }
          }
        }
      },
      "AttendancePunchResult": {
        "description": "Punch result. With the tenant's `absent_appeal` setting ON (#1878), a first IN after the absence cutoff (shift start + `absence_after_minutes`) is refused `ABSENT_APPEAL_REQUIRED` with an `absent_appeal_id`; plain late before the cutoff is accepted and marked late. HR_APPROVAL_REQUIRED remains only for historical responses.",
        "type": "object",
        "readOnly": true,
        "properties": {
          "client_punch_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "attendance_record_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "punch_type": {
            "$ref": "#/components/schemas/PunchType"
          },
          "geofence_result": {
            "$ref": "#/components/schemas/GeofenceResult"
          },
          "face_result": {
            "$ref": "#/components/schemas/FaceResult"
          },
          "face_matched": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "**The flag to gate a clock-in confirmation on** (#1530). `face_result` alone cannot carry that decision: it records the stored verdict without saying who reached it, so a `MATCH` the server computed and a `MATCH` the handset asserted that the server merely kept — no enrolment, an enrolment still awaiting HR, a model-version mismatch, an engine failure — are the same three letters on the wire. `face_matched` is `true` ONLY for a verdict the server itself reached. `true` — the server embedded the punch photo, compared it against the employee's ACTIVE `attend.face_enrollments` template, and it matched. `false` — the server compared and it did NOT match, or refused the photo as a replay (`SPOOF_SUSPECTED`); a real, server-backed negative. `null` — the server could not check (no `photo_storage_key`, no enrolment, an enrolment awaiting HR, `FACE_MODEL_CHANGED`, an engine failure, or the web rail, which has no camera). **`null` is \"unknown\", not \"failed\"** — it is deliberately distinct from `false` so a client cannot collapse \"we could not tell\" into \"this is not them\" and lock an employee out of their own shift. `face_verified_by` and `warnings` say which case it is.\n"
          },
          "face_verified_by": {
            "type": "string",
            "enum": [
              "server",
              "client"
            ],
            "description": "Provenance of `face_result` (#1530, ADR 0025 §Consequences — stored on the row itself as `attend.face_attestations.verified_by`, migration `0216`). `server` when the API embedded the supplied photo and compared it against the enrolment, or established on the web rail that there was nothing to check; `client` when it kept the handset's claim. Only a `server` row's `face_result` is evidence.\n"
          },
          "attendance_status": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AttendanceStatus"
              },
              {
                "type": "null"
              }
            ]
          },
          "has_exception": {
            "type": "boolean",
            "description": "True when the DAY has at least one unresolved (`PENDING`) out-of-zone / exception row — that is the whole definition, and it is derived rather than accumulated (#1177), so a reviewed exception, or a day that never had one, reads `false`. It is **not** a report on this punch's verification: a punch raises an exception when the server's verdict is bad (`OUTSIDE`, `NO_MATCH`, `SPOOF_SUSPECTED`), when the degrade is one the employee owns (no face enrolment, or one still awaiting HR), or when the server could check NEITHER half — no coordinates AND no photo, the forged-enum case ADR 0025 §5 closes. A punch whose location the server recomputed and accepted is NOT flagged merely because no photo accompanied it, and a tenant that has configured no geofence still warns without penalising the employee. Per-punch verification detail stays in `warnings` and on the attestation rows.\n"
          },
          "worked_hours": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Total across closed sessions, updated by manual or automatic clock-out; the daily allowance and shift break apply."
          },
          "late_entry_request_id": {
            "type": "string",
            "format": "uuid",
            "description": "Historical blocked-attempt request. Superseded by `absent_appeal_id` (#1878)."
          },
          "absent_appeal_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "#1878 — present only with `refusal_reason: ABSENT_APPEAL_REQUIRED`. The appeal to submit (`POST /late-entry-requests/{id}/submit {reason, version}`) and to poll on `GET /late-entry-requests/me`. A refused appeal punch never carries `face_matched`, `face_result` or `geofence_result`: it is refused before the face is verified.\n"
          },
          "absent_appeal_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "BLOCKED",
              "PENDING",
              "APPROVED",
              "REJECTED",
              "CONSUMED",
              "SUPERSEDED",
              "LAPSED",
              null
            ],
            "description": "#1878 — the appeal's state at the time of the refusal (`BLOCKED` = not yet sent)."
          },
          "absent_appeal_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "#1878 — 00:00 local on the day after the work date; an undecided appeal lapses then."
          },
          "accepted": {
            "type": "boolean",
            "description": "Present and `false` on a punch refused by placement, session-state, or daily-allotment policy — absent on every accepted punch, so a client that predates this reads the response exactly as before. A refused punch writes nothing: `attendance_record_id` and `attendance_status` are `null`, no attestation row is stored, and no out-of-zone event is filed. Refusal is per PUNCH so an offline batch still drains around one bad item; when EVERY punch in the request was refused the call is a `403` instead, which is what a single interactive punch normally gets. The absence cutoff is not an admission refusal.\n"
          },
          "refusal_reason": {
            "type": "string",
            "enum": [
              "OUTSIDE_ASSIGNED_FENCE",
              "LOCATION_EVIDENCE_REQUIRED",
              "SESSION_ALREADY_OPEN",
              "NO_OPEN_SESSION",
              "OT_NOT_APPROVED",
              "HR_APPROVAL_REQUIRED",
              "ABSENT_APPEAL_REQUIRED"
            ],
            "description": "Why this punch was refused. The first two are PLACEMENT refusals (#1317) and pair with the `403`'s problem `type` (`urn:groundit:problem:attend:punch-outside-assigned-fence` / `…:punch-location-evidence-required`); the human sentence naming the location, the overshoot and the remedy is the last entry of `warnings`.\n\n`SESSION_ALREADY_OPEN` and `NO_OPEN_SESSION` are SESSION-STATE refusals (#1625) and pair with a `409` carrying the declared `punch_session_refusal` member and the `urn:groundit:problem:attend:punch-session-already-open` / `…:punch-no-open-session` `type` URIs. An IN is refused only while a stretch is OPEN; an OUT only when there is none to close. Both are refusals the server used to SWALLOW — the punch was accepted, folded into the day pair and lost. `HR_APPROVAL_REQUIRED` is retained only for compatibility with historical responses and is no longer emitted by sync.\n\n`ABSENT_APPEAL_REQUIRED` (#1878) — the tenant's `absent_appeal` setting is ON, the employee is not MUSTER-mode, this is the day's first IN and it arrived after the absence cutoff. The response is a `200` (the appeal row it created must persist), with `accepted: false`, `absent_appeal_id` and no face fields. Submit the appeal, wait for the manager's decision (`GET /late-entry-requests/me`), then take a FRESH clock-in (new `client_punch_id`, `captured_at` after the decision) before the appeal lapses at local midnight.\n"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "e.g. \"OUT_OF_ZONE — submit an out-of-zone attestation\" (routes the client to attend.out_of_zone_event.create, ATT-S07). Verification warnings under ADR 0025: `FACE_UNVERIFIED_SERVER`, `FACE_NOT_ENROLLED`, `FACE_MODEL_CHANGED`, `FACE_REPLAY_SUSPECTED`, `FACE_CLIENT_MISMATCH`, `GEOFENCE_UNVERIFIED_SERVER`, `GEOFENCE_NOT_CONFIGURED`, `GEOFENCE_CLIENT_MISMATCH`. Placement warnings under #1317 (ADR 0067): `PUNCH_LOCATION_NOT_SITE_BOUND`, `PUNCH_BEAT_PLAN_NOT_EVALUATED`, `PUNCH_WITHOUT_LOCATION_EVIDENCE`, `PUNCH_ENFORCEMENT_UNVERIFIED`, `PLACEMENT_UNREADABLE`.\n"
          }
        }
      },
      "AttendancePunchSyncResponse": {
        "type": "object",
        "required": [
          "results"
        ],
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AttendancePunchResult"
            }
          }
        }
      },
      "FaceEnrollmentRequest": {
        "type": "object",
        "description": "Names a face photo already uploaded through `xc.file.request_upload_url`. The bytes are never inlined.",
        "required": [
          "storage_key"
        ],
        "additionalProperties": false,
        "properties": {
          "storage_key": {
            "type": "string",
            "maxLength": 512,
            "description": "The key `xc.file.request_upload_url` returned for this upload. Must sit under the caller's own `<tenantUid>/uploads/` prefix — an enrolment cannot be pointed at an export, a payslip or a tax proof.\n"
          }
        }
      },
      "FaceEnrollmentStatus": {
        "type": "string",
        "enum": [
          "PENDING",
          "ACTIVE",
          "REJECTED",
          "RETIRED"
        ],
        "description": "`attend.face_enrollment_status` (migration `0150`, D12/GAP-39/`#967`) — `PENDING` awaiting HR (never matched against a punch) · `ACTIVE` the one live, HR-approved reference face · `REJECTED` HR declined it (retired immediately) · `RETIRED` superseded by re-enrolment, employee-withdrawn, or erased on exit.\n"
      },
      "FaceEnrollment": {
        "type": "object",
        "readOnly": true,
        "description": "One `attend.face_enrollments` row as the API is willing to describe it. The `embedding` column is a biometric template and is deliberately **absent from this schema** — there is no operation in this contract that returns it.\n",
        "required": [
          "id",
          "employee_id",
          "status",
          "model_version",
          "enrolled_at",
          "version"
        ],
        "additionalProperties": false,
        "properties": {
          "capture_channel": {
            "type": "string",
            "enum": [
              "SELF",
              "SUPERVISOR"
            ]
          },
          "captured_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "capture_meta": {
            "type": [
              "object",
              "null"
            ],
            "description": "Server-derived channel, team_id, marker_work_location_id; marker_location contains latitude, longitude, accuracy_m and captured_at."
          },
          "hr_confirmed_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "hr_confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "file_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "status": {
            "$ref": "#/components/schemas/FaceEnrollmentStatus"
          },
          "model_version": {
            "type": "string",
            "description": "Which matcher produced the stored template. Templates from different models are not comparable, so a model change re-enrols everyone."
          },
          "enrolled_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "retired_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "reviewed_by": {
            "description": "The HR reviewer's employee id — set once the row leaves PENDING (approved or rejected).",
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "reviewed_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "review_note": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "version": {
            "type": "integer",
            "minimum": 1,
            "description": "Optimistic-lock version supplied as `If-Match` when HR decides the enrolment."
          }
        }
      },
      "FaceEnrollmentDetail": {
        "type": "object",
        "readOnly": true,
        "description": "The active enrolment plus a short-lived presigned URL for the enrolment PHOTO (never the template).",
        "allOf": [
          {
            "$ref": "#/components/schemas/FaceEnrollment"
          },
          {
            "type": "object",
            "properties": {
              "photo": {
                "type": "object",
                "description": "Presigned download for the enrolment photo, minted per request.",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "expires_at": {
                    "$ref": "#/components/schemas/TimestampRef"
                  }
                }
              }
            }
          }
        ]
      },
      "TimestampRef": {
        "$ref": "#/components/schemas/Timestamp"
      },
      "DecimalHoursRef": {
        "$ref": "#/components/schemas/DecimalHours"
      },
      "RateRef": {
        "$ref": "#/components/schemas/Rate"
      },
      "MoneyRef": {
        "$ref": "#/components/schemas/Money"
      },
      "LocalizedTextRef": {
        "$ref": "#/components/schemas/LocalizedText"
      },
      "HijriDisplayRef": {
        "$ref": "#/components/schemas/HijriDisplay"
      },
      "BusinessNoRef": {
        "$ref": "#/components/schemas/BusinessNo"
      },
      "AuditMetaRef": {
        "$ref": "#/components/schemas/AuditMeta"
      },
      "AppendOnlyMetaRef": {
        "$ref": "#/components/schemas/AppendOnlyMeta"
      },
      "CursorPageRef": {
        "$ref": "#/components/schemas/CursorPage"
      },
      "AttendanceRecord": {
        "description": "attend.attendance_records — the daily reconciled attendance row (db 05 §1.1, Partitioned).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "work_date",
              "status",
              "source"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "shift_assignment_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "schedule_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "work_location_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "work_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "clock_in_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "clock_out_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "worked_hours": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DecimalHoursRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_overtime_hours": {
                "$ref": "#/components/schemas/DecimalHoursRef",
                "description": "Present on list and detail reads; approved/processed hours only."
              },
              "requested_overtime_hours": {
                "$ref": "#/components/schemas/DecimalHoursRef",
                "description": "Present on list and detail reads; pending-approval hours only."
              },
              "overtime_requests": {
                "type": "array",
                "readOnly": true,
                "description": "Date-addressed requests for this employee-day, including their current approval/processing state. Pending hours are excluded from approved_overtime_hours.",
                "items": {
                  "type": "object",
                  "required": [
                    "id",
                    "request_no",
                    "status",
                    "ot_hours",
                    "approved_hours",
                    "ot_multiplier"
                  ],
                  "properties": {
                    "id": {
                      "$ref": "#/components/schemas/UuidRef"
                    },
                    "request_no": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "DRAFT",
                        "PENDING_APPROVAL",
                        "APPROVED",
                        "REJECTED",
                        "CANCELLED",
                        "PROCESSED"
                      ]
                    },
                    "ot_hours": {
                      "$ref": "#/components/schemas/DecimalHoursRef"
                    },
                    "approved_hours": {
                      "anyOf": [
                        {
                          "$ref": "#/components/schemas/DecimalHoursRef"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "ot_multiplier": {
                      "anyOf": [
                        {
                          "$ref": "#/components/schemas/RateRef"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              },
              "worked_minutes_live": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "List snapshot of all closed stretches plus the open stretch, capped at the daily allowance with the break deducted once. Null for records without sessions."
              },
              "as_of": {
                "$ref": "#/components/schemas/TimestampRef"
              },
              "latitude": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^-?\\d+(\\.\\d{1,6})?$",
                "description": "Latest saved latitude from the employee's own mobile geofence attestation; null when no punch coordinates were recorded."
              },
              "longitude": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^-?\\d+(\\.\\d{1,6})?$",
                "description": "Latest saved longitude from the employee's own mobile geofence attestation; null when no punch coordinates were recorded."
              },
              "expected_hours": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DecimalHoursRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "status": {
                "$ref": "#/components/schemas/AttendanceStatus"
              },
              "source": {
                "$ref": "#/components/schemas/AttendanceSource"
              },
              "is_regularized": {
                "type": "boolean"
              },
              "late_minutes": {
                "type": "integer",
                "minimum": 0
              },
              "early_out_minutes": {
                "type": "integer",
                "minimum": 0
              },
              "scoped_holiday_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The month-grid scoped holiday a HOLIDAY day carries (#1879) — shown as \"Holiday — <name>\" on the grid and in ESS. NULL for every other day."
              },
              "scoped_holiday_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "attend.scoped_holidays row that made this day HOLIDAY (#1879)."
              },
              "has_exception": {
                "type": "boolean",
                "description": "The day has at least one unresolved (PENDING) out-of-zone / exception row (#1177). Derived, not sticky: it clears when the exception is decided, and the nightly day-close sweep re-derives it — including raising MISSED_PUNCH on a matured day that holds only one of the two punches."
              },
              "pending_exception": {
                "description": "The PENDING attend.out_of_zone_events row that sets has_exception, or null. Same tenant read as the ledger row, so a flagged day can name its exception without a second queue.",
                "anyOf": [
                  {
                    "type": "null"
                  },
                  {
                    "type": "object",
                    "required": [
                      "event_type",
                      "reason",
                      "punch_type",
                      "distance_m"
                    ],
                    "properties": {
                      "event_type": {
                        "type": "string",
                        "enum": [
                          "OUT_OF_ZONE",
                          "LOCATION_UNKNOWN",
                          "MISSED_PUNCH",
                          "LATE_IN",
                          "EARLY_OUT"
                        ]
                      },
                      "reason": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "punch_type": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "IN",
                          "OUT",
                          null
                        ]
                      },
                      "distance_m": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Metres outside the assigned fence, decimal string. Null when the capture recorded none."
                      }
                    }
                  }
                ]
              },
              "shift_end_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "assignment_date": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnlyRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "shift_grace_out_minutes": {
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": 0
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The assignment's shift out-grace. A clock-out is not missing until shift end + grace."
              },
              "sessions": {
                "type": "array",
                "readOnly": true,
                "description": "Complete punch stretches for the day, oldest first. Legacy sessions without client punch ids have null coordinates.",
                "items": {
                  "$ref": "#/components/schemas/PunchSession"
                }
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "WorkLocationRef": {
        "type": "object",
        "description": "Minimal `org.work_locations` projection (own read-model — org owns the write model, 00 §5).",
        "readOnly": true,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "name": {
            "type": "string"
          },
          "city": {
            "type": "string"
          }
        }
      },
      "AttendanceRecordDetail": {
        "description": "ATT-S05 daily detail — the record plus a resolved office-location snapshot and the day's overtime roll-up.",
        "allOf": [
          {
            "$ref": "#/components/schemas/AttendanceRecord"
          },
          {
            "type": "object",
            "properties": {
              "work_location": {
                "$ref": "#/components/schemas/WorkLocationRef"
              },
              "overtime_hours_today": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DecimalHoursRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Legacy alias of approved_overtime_hours, derived from APPROVED/PROCESSED requests for the employee and work date (including requests with null attendance_record_id)."
              }
            }
          }
        ]
      },
      "AttendanceRecordPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttendanceRecord"
                }
              }
            }
          }
        ]
      },
      "PunchSession": {
        "type": "object",
        "readOnly": true,
        "description": "ONE clock-in/clock-out stretch inside an attendance day (`attend.punch_sessions`, migration 0227, #1625). `out_at: null` is the OPEN stretch — the employee is working right now — and there is at most one open session per employee-day.\n",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "attendance_record_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "in_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "out_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "NULL = the stretch is still open. Automatic clock-out records the exact allowance mark. At most one open session per employee-day."
          },
          "source": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AttendanceSource"
              }
            ],
            "description": "How the OPENING punch was captured. The closing punch is `out_source`."
          },
          "out_source": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AttendanceSource"
              },
              {
                "type": "null"
              }
            ],
            "description": "How the CLOSING punch was captured (`attend.punch_sessions.out_source`, migration 0267). `null` while the stretch is still open, or for a legacy/automatic close whose OUT channel was never recorded. App-to-app clock-outs write the punch's own channel (`WEB` / `MOBILE_GEOFENCE`); day-close and overtime auto-stop write `SYSTEM`; a registered-reader OUT writes `DEVICE`; a regularisation close writes `REGULARIZED`.\n"
          },
          "face_matched": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "The SERVER's own face verdict for the punch that opened this stretch, in the same three-valued shape the punch result uses (#1530): `true` the server compared against the ACTIVE enrolment and matched, `false` it compared and did not (or refused the photo as a replay), `null` it could NOT check — no photo, no enrolment, one awaiting HR, a model-version mismatch, or the web rail, which has no camera. `null` is deliberately distinct from `false` so a client cannot collapse \"we could not tell\" into \"this is not them\". A close only ever DOWNGRADES it: a server-verified mismatch on the way out overwrites, anything else leaves the opening verdict alone.\n"
          },
          "geofence_ok": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`true` the server placed the opening punch INSIDE a fence, `false` OUTSIDE, `null` it could not tell (no coordinates, no configured fence, the web rail). Never the handset's claim — ADR 0025. Same downgrade-on-close rule as `face_matched`.\n"
          },
          "work_location_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "client_punch_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Client id of the IN that opened this stretch; null for historical/backfilled sessions."
          },
          "out_client_punch_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Client id of the OUT that closed this stretch; null for historical/backfilled or automatic closes."
          },
          "in_latitude": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opening punch latitude resolved by exact client id."
          },
          "in_longitude": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opening punch longitude resolved by exact client id."
          },
          "out_latitude": {
            "type": [
              "string",
              "null"
            ],
            "description": "Closing punch latitude resolved by exact client id."
          },
          "out_longitude": {
            "type": [
              "string",
              "null"
            ],
            "description": "Closing punch longitude resolved by exact client id."
          },
          "worked_minutes": {
            "type": "integer",
            "description": "Whole minutes this stretch has run for, resolved by the SERVER against `as_of`: a closed session's own length, an open one's run so far. NOT re-derived by the client — the whole of #1630 is that the browser and the handset stopped being a second opinion about worked time. The shift's break is NOT deducted here; it is a once-per-DAY deduction and belongs to the day's total (`worked_hours`, `worked_minutes_live`), not to any one stretch.\n"
          }
        }
      },
      "PunchSessionRefusalProblem": {
        "description": "The `409` `attend.punch.sync` / `attend.punch.web` answer when a punch does not fit the day's session state machine (#1625). A standard problem+json body — `code: STATE_TRANSITION_INVALID`, `type` one of the two `urn:groundit:problem:attend:punch-session-*` URIs — plus ONE declared extra member carrying the refusal enum.\n\n**A declared member, not two new `code`s.** `code` is a closed, cross-cutting enum (`03 §1`), and per-operation lifecycle vocabulary does not belong in it; that doc's own escape hatch is the one taken here — *\"a problem body may still carry extra, declared members alongside it\"* — exactly as `ess_invite_refusal` (`02-people.openapi.yaml#/components/schemas/EssInviteRefusalProblem`) and the approval decide-race's `decided_by` already do. `#1631`'s `FACE_MISMATCH` DID mint a code, and the distinction is the test for the next one: that refusal is a cross-cutting security verdict every identity-bearing surface has reason to switch on; these two are one endpoint's states.\n\n**Both are retryable, and differently.** `SESSION_ALREADY_OPEN` means the employee is already clocked in — the client's next punch is an OUT, and its CTA should already say so. `NO_OPEN_SESSION` means they never clocked in; the remedy is a regularisation, not a retry of the same punch. A client that renders only `detail` cannot tell those apart, which is why the member exists.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Problem"
          },
          {
            "type": "object",
            "properties": {
              "punch_session_refusal": {
                "type": "string",
                "enum": [
                  "SESSION_ALREADY_OPEN",
                  "NO_OPEN_SESSION"
                ],
                "description": "`SESSION_ALREADY_OPEN` — an `IN` arrived while this employee already has an open stretch on the work date. Held as a database fact by `punch_sessions_open_day_key`, so a racing second `IN` that slips past the service's own read is refused with this same body rather than a lookalike. `NO_OPEN_SESSION` — an `OUT` arrived with no open stretch to close, and the day has no clock-in to adopt one from either. A day that DOES have an unclosed `clock_in_at` and no sessions (a day that predates the ledger, or one left open by the deploy) is NOT refused: the server synthesises that stretch and closes it.\n"
              }
            }
          }
        ]
      },
      "PunchFaceMismatchProblem": {
        "description": "The `409` answer when the server compared the supplied punch photo with the employee's HR-approved ACTIVE enrolment and reached `NO_MATCH` or `SPOOF_SUSPECTED`. Nothing for that punch is persisted. `detail` is safe to display verbatim and gives the retry/remediation instruction; it intentionally exposes no similarity score or threshold.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Problem"
          },
          {
            "type": "object",
            "required": [
              "code",
              "type"
            ],
            "properties": {
              "code": {
                "type": "string",
                "const": "FACE_MISMATCH"
              },
              "type": {
                "type": "string",
                "const": "urn:groundit:problem:attend:punch-face-mismatch"
              }
            }
          }
        ]
      },
      "PunchFaceVerificationRequiredProblem": {
        "description": "The `409` answer when a mobile `IN` did not produce a server `MATCH`. This is not a claim that the capture belongs to somebody else; it means the employee needs an approved enrolment and a fresh usable capture, or must retry after a verifier failure. Nothing for that punch is persisted. A mobile `OUT` is not refused for this condition, so it can close an open session.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Problem"
          },
          {
            "type": "object",
            "required": [
              "code",
              "type"
            ],
            "properties": {
              "code": {
                "type": "string",
                "const": "FACE_VERIFICATION_REQUIRED"
              },
              "type": {
                "type": "string",
                "const": "urn:groundit:problem:attend:punch-face-verification-required"
              }
            }
          }
        ]
      },
      "PunchSessionPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PunchSession"
                }
              },
              "as_of": {
                "$ref": "#/components/schemas/TimestampRef",
                "description": "The instant every OPEN session's `worked_minutes` was measured against — the true `now()`. #1632 does NOT clamp it: the stretches are the employee's own account of their day and are returned RAW. The day's allotment governs what is COUNTED, and that is the `allowance` block below (and `worked_hours` on the day row).\n"
              },
              "allowance": {
                "$ref": "#/components/schemas/AllowanceBlock"
              }
            }
          }
        ]
      },
      "AllowanceBlock": {
        "type": "object",
        "readOnly": true,
        "additionalProperties": false,
        "required": [
          "working_hours",
          "break_minutes",
          "allowance_minutes",
          "allowance_minutes_remaining",
          "overtime"
        ],
        "description": "THE DAY'S ALLOTMENT and its overtime posture (#1632) — every figure decided server-side so a client renders \"X of 8h worked, Yh remaining\" and the overtime badge with no arithmetic of its own.\n\nThe regular allotment is at most eight net hours, or a shorter configured shift, plus approved overtime. Without a shift it defaults to eight hours. Shift start/end times do not determine the allowance. All stretches on the work date share the allowance. `allowance_minutes_remaining: 0` means the day is spent: automatic clock-out closes the open stretch and further IN requires approved overtime. Approval requires a fresh IN to resume, and does not count the paused interval.\n",
        "properties": {
          "working_hours": {
            "type": [
              "string",
              "null"
            ],
            "description": "Effective regular decimal hours: at most 8.00, with shorter shifts retained; defaults to 8.00."
          },
          "break_minutes": {
            "type": "integer",
            "description": "The once-per-day deduction ALREADY applied to the worked figure — never to be subtracted again by a client."
          },
          "allowance_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "NET minutes the day allows: `working_hours` plus any APPROVED overtime for the date. A PENDING request adds nothing — that is the whole of \"only after approval OT should go\".\n"
          },
          "allowance_minutes_remaining": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What is left of it, measured against the same live worked figure this block is returned beside. Floored at zero. `0` means the allotment is SPENT — a further clock-in answers `409 OT_NOT_APPROVED` ; null remains accepted for compatibility with older responses.\n"
          },
          "overtime": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "state",
              "requested_hours",
              "approved_hours",
              "request_id"
            ],
            "description": "The day's overtime posture, in the owner's three words.",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "NONE",
                  "REQUESTED",
                  "APPROVED"
                ],
                "description": "`APPROVED` wins over a still-pending sibling: a day that has SOME approved overtime has had its allowance extended, and a second request pending on top does not undo it.\n"
              },
              "requested_hours": {
                "type": "string",
                "description": "Σ still-PENDING `ot_hours`, decimal string."
              },
              "approved_hours": {
                "type": "string",
                "description": "Σ APPROVED/PROCESSED hours — the extension the allowance gained."
              },
              "request_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The newest live request on the day, so a client can deep-link straight at it."
              }
            }
          }
        }
      },
      "AttendanceRecordExportRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "work_date_from": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "work_date_to": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "status": {
            "$ref": "#/components/schemas/AttendanceStatus"
          },
          "team_id": {
            "description": "A squad (`work.teams`) — only its live members' records; `none` = people on no squad. The CSV always carries `employee_code`, `employee_name`, `team` (live squads, `; `-joined) and `site` (the record's work location, else the person's home location).\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "string",
                "enum": [
                  "none"
                ]
              }
            ]
          }
        }
      },
      "AttendanceTeamMemberSummary": {
        "type": "object",
        "readOnly": true,
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "employee_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "ref→people.employees.full_name, by a same-transaction LEFT JOIN (#986, GAP-40-adjacent #960) — the team-pulse screen needs a displayable name server-side, without a second round-trip. Confined to the same manager/direct-report row set this operation already returns (people.employees carries the identical TEAM-scope RESTRICTIVE ownership policy attend.attendance_records does), so the join widens columns only, never the row set. Null only if the employee row itself is unreachable."
          },
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "status": {
            "$ref": "#/components/schemas/AttendanceStatus"
          },
          "clock_in_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "clock_out_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "has_exception": {
            "type": "boolean"
          }
        }
      },
      "AttendanceTeamSummaryPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "required": [
              "counts"
            ],
            "properties": {
              "counts": {
                "type": "object",
                "description": "Live roll-up counts for the requested day, computed in the SAME query as the member rows below (not a job-built/precomputed aggregate — `attend.attendance_team_summary. list` is `x-sync-async: sync`). `present`/`absent`/`on_leave` are the row's `status` bucketed (`present` includes both `PRESENT` and `HALF_DAY`). **`late` is a PEER bucket, not a subset of `present`**: it is `count(*) FILTER (WHERE late_minutes > 0)` over the same filtered row set, independent of `status` — a day that is `PRESENT` AND late (clocked in late, worked the full shift) is counted in BOTH `present` and `late`. This matches how the payroll cycle finalize aggregate already treats lateness as independent of a day's status (`attendance-cycle.service.ts`'s `late_marks`). Consequently `present + absent + on_leave + late` does **not** sum to the row count, and a client (the PWA team-pulse \"4 counts\" tile, design-pwa/03 §9) must not assume it does. Before 2026-08-15 (#986 follow-up) `late` was wired to nothing and stayed a permanent 0 — a lying field the tile could never populate; it is now honest.\n",
                "properties": {
                  "present": {
                    "type": "integer",
                    "description": "Rows with status PRESENT or HALF_DAY."
                  },
                  "absent": {
                    "type": "integer",
                    "description": "Rows with status ABSENT."
                  },
                  "late": {
                    "type": "integer",
                    "description": "Rows with late_minutes > 0 — a peer bucket that MAY overlap present. See the counts description above."
                  },
                  "on_leave": {
                    "type": "integer",
                    "description": "Rows with status ON_LEAVE."
                  }
                }
              },
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttendanceTeamMemberSummary"
                }
              }
            }
          }
        ]
      },
      "OutOfZoneEvent": {
        "description": "attend.out_of_zone_events — an out-of-zone/missed/late/early exception (db 05 §1.1).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "event_type",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "attendance_record_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "work_date": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnlyRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "geofence_attestation_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "employee_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "By projection, and only on `attend.out_of_zone_event.list` — `people.employees.full_name` for `employee_id`. The manager's queue is a list of OTHER people's exceptions and carried no identity but the uuid (issue #1640); the submitter's own `get` omits it, because the subject there is the caller. Null (never an error) when the name cannot be resolved.\n"
              },
              "event_type": {
                "$ref": "#/components/schemas/OutOfZoneType"
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "latitude": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^-?\\d+(\\.\\d{1,6})?$"
              },
              "longitude": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^-?\\d+(\\.\\d{1,6})?$"
              },
              "status": {
                "$ref": "#/components/schemas/OutOfZoneStatus"
              },
              "reviewed_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "reviewed_by_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "By projection, on `attend.out_of_zone_event.get` and — since issue #1640 — on `attend.out_of_zone_event.list` too: `people.employees.full_name` for `reviewed_by`. Null while the request is still `PENDING`, and null (never an error) if the reviewer's employee row has since been soft-deleted. The decision responses omit the field entirely rather than carry a second per-row join.\n"
              },
              "reviewed_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "review_note": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "evidence": {
                "type": "array",
                "description": "Every punch, beat-plan stop and app submission behind this employee-day, OLDEST FIRST (#1633, migration `0225`). One exceptional day used to raise one ROW per occurrence — an out-of-fence clock-in and clock-out were already two requests, a beat plan added one per stop, and every re-submission added another — so the approver saw the same day several times over. There is now ONE `PENDING` row per `(employee_id, work_date)`, enforced by a partial unique index, and this list is what makes that lossless: a console renders one card per employee-day with a line per occurrence. The scalar `latitude`/`longitude`/`geofence_attestation_id` name the FIRST occurrence; this names all of them. Empty on a row written before `0225`.\n",
                "items": {
                  "$ref": "#/components/schemas/OutOfZoneEvidence"
                }
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "OutOfZoneEvidence": {
        "type": "object",
        "description": "One occurrence behind an out-of-zone request — a punch, a beat-plan stop, or the employee's own submission from the app. Coordinates and distances are DECIMAL STRINGS, never JSON numbers. Every member but `kind` and `at` may be null: a rail that captured nothing (a web punch records no location at all) still contributes an entry, because WHEN it happened is itself evidence.\n",
        "required": [
          "kind",
          "at"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "PUNCH",
              "FIELD_VISIT",
              "SUBMISSION"
            ],
            "description": "Which rail raised it. `SUBMISSION` is the employee explaining the day."
          },
          "at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "event_type": {
            "$ref": "#/components/schemas/OutOfZoneType"
          },
          "punch_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "IN",
              "OUT",
              null
            ],
            "description": "`IN`/`OUT` on the punch rail; null on every other."
          },
          "geofence_attestation_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "latitude": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^-?\\d+(\\.\\d{1,6})?$"
          },
          "longitude": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^-?\\d+(\\.\\d{1,6})?$"
          },
          "distance_m": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^-?\\d+(\\.\\d{1,2})?$",
            "description": "Metres from the fence this occurrence was measured at."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "OutOfZoneEventCreate": {
        "type": "object",
        "description": "A standalone out-of-zone submission is allowed when the client blocks clock-in before an attendance record exists. In that form the client supplies the current latitude, longitude, and reason; the server assigns `event_type` and the current work date. The legacy linked form may instead reference an existing punch/day context.\n",
        "required": [
          "reason"
        ],
        "additionalProperties": false,
        "properties": {
          "geofence_attestation_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "attendance_record_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "latitude": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,6})?$"
          },
          "longitude": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,6})?$"
          },
          "reason": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "OutOfZoneEventPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OutOfZoneEvent"
                }
              }
            }
          }
        ]
      },
      "FaceMismatchAttempt": {
        "type": "object",
        "description": "One refused punch attempt (#1631). Read-only evidence projected from `audit.audit_log`; it has no lifecycle, no `version` and no decision endpoint.\n",
        "required": [
          "id",
          "refused_at",
          "employee_id",
          "work_date",
          "punch_type",
          "face_result"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "refused_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the refusal was recorded."
          },
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "employee_name": {
            "type": "string",
            "nullable": true
          },
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "punch_type": {
            "type": "string",
            "enum": [
              "IN",
              "OUT"
            ]
          },
          "face_result": {
            "type": "string",
            "enum": [
              "NO_MATCH",
              "SPOOF_SUSPECTED"
            ],
            "description": "The server-computed verdict that refused the punch."
          },
          "face_enrollment_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "The ACTIVE enrolment the photo was compared against."
          },
          "distance": {
            "type": "number",
            "nullable": true,
            "description": "Matcher distance. `null` on a replay refusal, which is decided before any embedding is computed."
          },
          "threshold": {
            "type": "number",
            "nullable": true,
            "description": "The configured `FACE_MATCH_THRESHOLD` ceiling the distance was judged against."
          },
          "source": {
            "type": "string",
            "nullable": true,
            "enum": [
              "MOBILE_GEOFENCE",
              "WEB",
              null
            ],
            "description": "Which capture rail the attempt arrived on."
          },
          "device": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "The handset's `geofence.device_meta`",
            "as submitted.": null
          },
          "captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "FaceMismatchAttemptPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FaceMismatchAttempt"
                }
              }
            }
          }
        ]
      },
      "ApprovalDecisionInput": {
        "type": "object",
        "description": "Generic optional-note decision body shared by every approve/reject/return/cancel action in this file.",
        "additionalProperties": false,
        "properties": {
          "note": {
            "type": "string"
          },
          "refused_for_business_reasons": {
            "type": "boolean",
            "description": "**Leave rejection only** (ADR 0035 §(b), db 05 §2.2). `true` records that the refusal was the EMPLOYER's operational call rather than a policy failure — days on such an application carry forward past the cap at year end, because an employer that refuses leave for its own reasons may not then let that leave expire. Stored on the application, not the step, so a multi-step chain has one answer; ignored by every other decision action.\n"
          }
        }
      },
      "OvertimeRequest": {
        "description": "attend.overtime_requests (db 05 §1.3).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "request_no",
              "employee_id",
              "ot_hours",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "request_no": {
                "$ref": "#/components/schemas/BusinessNoRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "attendance_record_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "work_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "ot_hours": {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              "approved_hours": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DecimalHoursRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "ot_multiplier": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/RateRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "$ref": "#/components/schemas/OvertimeStatus"
              },
              "submitted_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "pay_period": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "processed_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "OvertimeRequestCreate": {
        "type": "object",
        "description": "`ot_type`/`project_link` are FSD-flagged DB gaps (fsd 04 §1.4, ATT-S08) with no matching columns — OT type resolves from policy server-side; project context folds into `reason`.\n",
        "required": [
          "work_date",
          "ot_hours"
        ],
        "additionalProperties": false,
        "properties": {
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "attendance_record_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "ot_hours": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "OvertimeApprovalInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "approved_hours": {
            "$ref": "#/components/schemas/DecimalHoursRef",
            "description": "Optional. Omitted (as the unified /approvals rail does) = approve the claimed `ot_hours`."
          },
          "note": {
            "type": "string"
          }
        }
      },
      "OvertimeRequestPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OvertimeRequest"
                }
              }
            }
          }
        ]
      },
      "Regularization": {
        "description": "attend.regularizations — maker-checker correction, original + corrected both retained (db 05 §1.3).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "request_no",
              "employee_id",
              "attendance_record_id",
              "regularization_type",
              "reason",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "request_no": {
                "$ref": "#/components/schemas/BusinessNoRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "attendance_record_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "work_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "regularization_type": {
                "$ref": "#/components/schemas/RegularizationType"
              },
              "original_snapshot": {
                "type": "object",
                "readOnly": true,
                "description": "Frozen pre-correction values (clock_in_at, clock_out_at, status, worked_hours, source) — db 05 §1.3.",
                "additionalProperties": true
              },
              "corrected_clock_in_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "corrected_clock_out_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "corrected_status": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/AttendanceStatus"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "reason": {
                "type": "string"
              },
              "supporting_note": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "$ref": "#/components/schemas/RegularizationStatus"
              },
              "submitted_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "applied_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "RegularizationCreate": {
        "description": "#1634 — the correction addresses an EMPLOYEE-DAY, and `work_date` is how it does so. When no `attend.attendance_records` row exists for that day the server MATERIALISES one in the same transaction — the CALENDAR-EQUIVALENT `SYSTEM` row the nightly `attend.materialize_daily_records` planner would have written, `is_regularized = false`, status resolved from the employee's own holiday calendar, schedule and shift assignments — and attaches the request to it, so \"I worked that day and there is no record of it\", the one case regularisation exists for, is filable. The day must not be in the future, settled in the employee's own work-date timezone.\n`attendance_record_id` REMAINS SUPPORTED for the mobile client and for queued offline writes: sent alone it resolves and ownership-checks the day exactly as before. Exactly one of the two is required; sending both is allowed only when they describe the same day (a mismatch is a 422 at `/work_date`).\n",
        "type": "object",
        "required": [
          "regularization_type",
          "reason"
        ],
        "anyOf": [
          {
            "required": [
              "work_date"
            ]
          },
          {
            "required": [
              "attendance_record_id"
            ]
          }
        ],
        "additionalProperties": false,
        "properties": {
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "attendance_record_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "regularization_type": {
            "$ref": "#/components/schemas/RegularizationType"
          },
          "corrected_clock_in_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "corrected_clock_out_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "corrected_status": {
            "$ref": "#/components/schemas/AttendanceStatus"
          },
          "reason": {
            "type": "string",
            "minLength": 1
          },
          "supporting_note": {
            "type": "string"
          }
        }
      },
      "RegularizationUpdate": {
        "description": "Employee edit of their OWN still-pending correction (ESS gate G1, GAP-ESS-24). Every field is optional — an omitted field keeps its stored value. `attendance_record_id` is deliberately not editable: re-pointing a submitted correction at a different day is a new request, not an edit.\n",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "regularization_type": {
            "$ref": "#/components/schemas/RegularizationType"
          },
          "corrected_clock_in_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "corrected_clock_out_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "corrected_status": {
            "$ref": "#/components/schemas/AttendanceStatus"
          },
          "reason": {
            "type": "string",
            "minLength": 1
          },
          "supporting_note": {
            "type": "string"
          }
        }
      },
      "OvertimeRequestUpdate": {
        "description": "Employee edit of their OWN still-pending overtime request (ESS gate G1, GAP-ESS-24). The work date and its attendance record are fixed at submit — changing the day is a new request.\n",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "ot_hours": {
            "type": "string",
            "description": "Decimal hours, > 0."
          },
          "reason": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "RegularizationPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Regularization"
                }
              }
            }
          }
        ]
      },
      "Schedule": {
        "description": "attend.schedules — enforces (never redefines) org roster config, ORG-F04 (db 05 §1.2).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "effective_from",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "employee_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "roster_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "work_location_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "schedule_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "effective_from": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "effective_to": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnlyRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "weekly_pattern": {
                "type": "object",
                "description": "Per-weekday shift + rest-day map, snapshot from org (db 05 §1.2).",
                "additionalProperties": true
              },
              "weekly_off_days": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "e.g. [\"SAT\",\"SUN\"] India · [\"FRI\",\"SAT\"] KSA — pack-driven (XC-F01)."
              },
              "status": {
                "$ref": "#/components/schemas/ScheduleStatus"
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "ScheduleCreate": {
        "type": "object",
        "required": [
          "effective_from"
        ],
        "additionalProperties": false,
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "roster_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "work_location_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "schedule_name": {
            "type": "string"
          },
          "effective_from": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "effective_to": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "weekly_pattern": {
            "type": "object",
            "additionalProperties": true
          },
          "weekly_off_days": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "status": {
            "$ref": "#/components/schemas/ScheduleStatus"
          }
        }
      },
      "ScheduleUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "schedule_name": {
            "type": "string"
          },
          "effective_to": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "weekly_pattern": {
            "type": "object",
            "additionalProperties": true
          },
          "weekly_off_days": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "status": {
            "$ref": "#/components/schemas/ScheduleStatus"
          }
        }
      },
      "SchedulePage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          }
        ]
      },
      "ShiftAssignment": {
        "description": "attend.shift_assignments — the concrete roster row a day's punch is measured against (db 05 §1.2).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "shift_id",
              "assignment_date",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "schedule_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "shift_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "work_location_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "geofence_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "assignment_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "assignment_end_date": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnlyRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "shift_start_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "shift_end_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "expected_hours": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DecimalHoursRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "is_night_shift": {
                "type": "boolean"
              },
              "status": {
                "$ref": "#/components/schemas/ShiftAssignmentStatus"
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "ShiftAssignmentCreate": {
        "type": "object",
        "required": [
          "employee_id",
          "shift_id",
          "assignment_date"
        ],
        "additionalProperties": false,
        "properties": {
          "schedule_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "shift_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "work_location_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "geofence_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "assignment_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "assignment_end_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "shift_start_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "shift_end_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "expected_hours": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "is_night_shift": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "ShiftAssignmentUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "shift_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "work_location_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "geofence_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "assignment_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "assignment_end_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "shift_start_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "shift_end_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "expected_hours": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "is_night_shift": {
            "type": "boolean"
          },
          "status": {
            "$ref": "#/components/schemas/ShiftAssignmentStatus"
          }
        }
      },
      "ShiftAssignmentPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ShiftAssignment"
                }
              }
            }
          }
        ]
      },
      "LeaveBalanceTxn": {
        "description": "leave.leave_balances — append-only ledger transaction row; never edited in place (db 05 §2.1).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "leave_type_id",
              "txn_type",
              "quantity_days",
              "balance_after",
              "effective_date"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "leave_type_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "leave_type_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.leave_types.name, by projection (not a join) — English preferred, Arabic-only fallback."
              },
              "leave_policy_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "txn_type": {
                "$ref": "#/components/schemas/BalanceTxnType"
              },
              "quantity_days": {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              "balance_after": {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              "fiscal_year": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "effective_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "source_type": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "APPLICATION",
                  "ENCASHMENT",
                  "COMP_OFF",
                  "ACCRUAL_JOB",
                  "ADJUSTMENT",
                  null
                ]
              },
              "source_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "note": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "accrual_period_key": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "`<KIND>:<period>` on every row a scheduled writer posted (`ACCRUAL:2026-04`, `YEAR_END_LAPSE:2025`, …), `null` on every hand-posted one. The database's partial unique index over it is what makes ADR 0035's \"a re-run posts nothing\" structural rather than a race-prone check.\n"
              }
            }
          },
          {
            "$ref": "#/components/schemas/AppendOnlyMetaRef"
          }
        ]
      },
      "LeaveBalanceTxnPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/LeaveBalanceTxn"
                }
              }
            }
          }
        ]
      },
      "LeaveRunRequest": {
        "type": "object",
        "description": "Optional body for `leave.leave_accrual.run`; an empty body means \"today\".",
        "additionalProperties": false,
        "properties": {
          "as_of": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateOnlyRef"
              }
            ],
            "description": "The civil date the run resolves periods against. Defaults to today. **Date-only** — a datetime is `422`, because the jobs tier parses the same `YYYY-MM-DD` shape and would otherwise fail every delivery after a `202`. Bounded to `[today - 400 days, today]`: a future date would post entitlement for time nobody has worked, and a date beyond one fiscal year back would sweep a year already closed and carried forward.\n"
          }
        }
      },
      "LeaveYearEndRunRequest": {
        "type": "object",
        "description": "Optional body for `leave.leave_year_end.run`.",
        "additionalProperties": false,
        "properties": {
          "as_of": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateOnlyRef"
              }
            ],
            "description": "The civil date the run resolves the fiscal boundary against. Defaults to today. **Date-only** and bounded to `[today - 400 days, today]`, exactly as `LeaveRunRequest.as_of` — closing a boundary the sweep has already moved past is what `force` is for, not an unbounded backdate.\n"
          },
          "force": {
            "type": "boolean",
            "description": "Close a boundary the scheduled sweep has already moved past. Deliberately explicit — the closing balance it reads has since been moved by the new fiscal year's accruals.\n"
          }
        }
      },
      "LeaveRunAccepted": {
        "type": "object",
        "required": [
          "run_id",
          "job_name",
          "as_of",
          "status"
        ],
        "properties": {
          "run_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              }
            ],
            "description": "ref→xc.job_runs."
          },
          "job_name": {
            "type": "string",
            "enum": [
              "leave.run_accruals",
              "leave.run_year_end"
            ]
          },
          "as_of": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "force": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "QUEUED"
            ]
          }
        }
      },
      "WorkedDayCount": {
        "description": "leave.worked_day_counts — the rebuildable accrual input (db 05 §2.5).",
        "type": "object",
        "required": [
          "id",
          "employee_id",
          "cycle_start",
          "cycle_end",
          "days_worked",
          "fiscal_year",
          "source_event_id"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "cycle_start": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "cycle_end": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "days_worked": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DecimalHoursRef"
              }
            ],
            "description": "The snapshot's own count — worked days, not payable days and not present days."
          },
          "fiscal_year": {
            "type": "integer"
          },
          "pay_group_ref": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "snapshot_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "Which signed snapshot this count came from."
          },
          "amendment_seq": {
            "type": "integer",
            "description": "A later amendment supersedes an earlier count."
          },
          "source_event_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              }
            ],
            "description": "The outbox event id — provenance and the delivery guard."
          },
          "projected_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WorkedDayCountPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WorkedDayCount"
                }
              }
            }
          }
        ]
      },
      "LeaveBalanceSummaryItem": {
        "type": "object",
        "readOnly": true,
        "description": "Aggregated from the latest leave.leave_balances rows per leave type (LVE-S01) — read-model, not a stored balance. A type the caller may apply for but has no ledger in is returned zero-filled (`accrued_days`/`used_days`/`remaining_days` all `0`, `as_of` null), so the balance screen and the applicable-types picker always list the same set. For a type with an applicable monthly_limit_days policy, remaining_days is capped by the current calendar month's unreserved allowance in the tenant timezone. Accrued and used remain ledger totals; pending applications reserve monthly allowance without a ledger debit. Clients must not block unpaid types against a zero balance, or validate another month's dates against this month's remaining_days; application submission validates the chosen dates.\nDISPLAY (#1627, #1628): render `available` — remaining minus the days already awaiting approval — never `remaining_days`, and branch on the two flags rather than on `is_paid`: `has_ledger: false` means NO opening balance or accrual has ever been posted for this type, which is not the same fact as a spent entitlement and must render as \"no balance posted yet\", never as `0`; `unlimited: true` (an unpaid type) has no entitlement at all, so `entitled`, `remaining_days` and `available` are all null and `used` is the only quantity the card shows. No surface may render a negative remaining: the paid arm is clamped at zero and the unpaid one is null. `pending` is derived from the employee's own PENDING_APPROVAL applications in the same statement — nothing is reserved, so a rejection, cancellation or withdrawal restores `available` on the next read. The same shape is returned field-for-field by `xc.employee_home.get` (13-xc.openapi.yaml, EmployeeHomeLeaveBalance), which runs this exact statement.\n",
        "properties": {
          "is_paid": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "False means no balance is required to apply; approved days are unpaid. Prefer `unlimited`, which states the consequence a client acts on."
          },
          "monthly_limit_days": {
            "type": [
              "string",
              "null"
            ],
            "description": "Applicable calendar-month ceiling, or null for ordinary accrued leave."
          },
          "leave_type_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "leave_type_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "ref→org.leave_types.name, by projection (not a join) — English preferred, Arabic-only fallback."
          },
          "leave_type_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "ref→org.leave_types.type_code, by projection — the stable key (`IN-CL`) a client should match on rather than the display name, which two master rows can share."
          },
          "unit": {
            "type": [
              "string",
              "null"
            ],
            "description": "ref→org.leave_types.unit — DAY or HOUR. What the three quantities are counted in."
          },
          "fiscal_year": {
            "type": "integer"
          },
          "accrued_days": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "used_days": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "remaining_days": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Latest ledger balance_after, capped by any monthly allowance and clamped at zero. Null for an `unlimited` type, which has no remainder. Kept for compatibility — display `available`."
          },
          "as_of": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the latest ledger row for this type was written. Null on a zero-filled type — there is no ledger row to date it."
          },
          "entitled": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Credits posted for this type and fiscal year (the `accrued_days` figure). Null when nothing has been posted (`has_ledger: false`) and on an `unlimited` type, which asserts no entitlement."
          },
          "used": {
            "$ref": "#/components/schemas/DecimalHoursRef",
            "description": "Σ of the ledger DEBIT partition — the same figure as `used_days`, and the ONLY quantity an `unlimited` type has."
          },
          "pending": {
            "$ref": "#/components/schemas/DecimalHoursRef",
            "description": "Σ total_days of this employee's PENDING_APPROVAL applications for this type and fiscal year, derived in the same statement. No reservation row exists: a rejection, cancellation or withdrawal simply stops counting."
          },
          "available": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "remaining − pending, clamped at zero — THE figure to display, and the figure application submission checks against. Null when there is no number to show: nothing posted (`has_ledger: false`) or an `unlimited` type."
          },
          "has_ledger": {
            "type": "boolean",
            "description": "False when the employee has no leave.leave_balances row for this type and fiscal year at all. Render \"no balance posted yet\" — never a 0, which reads as a spent entitlement."
          },
          "unlimited": {
            "type": "boolean",
            "description": "True for an unpaid type (is_paid = false): no entitlement, no remaining figure, `used` is the whole story. This flag is the contract — a client must not test `is_paid` itself."
          }
        }
      },
      "LeaveBalanceAdminItem": {
        "type": "object",
        "readOnly": true,
        "description": "Employee × leave-type balance row for HR administration (LVE-S08).",
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "leave_type_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "leave_type_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "ref→org.leave_types.name, by projection (not a join) — English preferred, Arabic-only fallback. Added #1158 so per-employee consumers (PPL-S14) need no second, separately-tokened `org.leave_type.list` read just to name a row."
          },
          "fiscal_year": {
            "type": "integer"
          },
          "accrued_days": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "used_days": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "remaining_days": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "as_of": {
            "$ref": "#/components/schemas/TimestampRef"
          }
        }
      },
      "LeaveBalanceAdminItemPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/LeaveBalanceAdminItem"
                }
              }
            }
          }
        ]
      },
      "LeaveBalanceAdjustmentCreate": {
        "type": "object",
        "description": "Posts an ADJUSTMENT ledger row; `note` is mandatory per the check constraint (db 05 §2.1).",
        "required": [
          "employee_id",
          "leave_type_id",
          "quantity_days",
          "effective_date",
          "note"
        ],
        "additionalProperties": false,
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "leave_type_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "quantity_days": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "effective_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "note": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "LeaveApplication": {
        "description": "leave.leave_applications (db 05 §2.2).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "application_no",
              "employee_id",
              "leave_type_id",
              "start_date",
              "end_date",
              "total_days",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "application_no": {
                "$ref": "#/components/schemas/BusinessNoRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "employee_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→people.employees.full_name, by projection (not a join) — populated only on the manager approval queue (`leave.leave_application.list_for_approval`); null elsewhere (the caller already knows who they are)."
              },
              "leave_type_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "leave_type_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ref→org.leave_types.name, by projection (not a join) — English preferred, Arabic-only fallback."
              },
              "leave_policy_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "start_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "end_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "is_half_day": {
                "type": "boolean"
              },
              "half_day_session": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/HalfDaySession"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "total_days": {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "$ref": "#/components/schemas/ApplicationStatus"
              },
              "balance_txn_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "is_comp_off": {
                "type": "boolean"
              },
              "document_file_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "submitted_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "decided_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "refused_for_business_reasons": {
                "type": "boolean",
                "description": "The rejection was the employer's operational call (ADR 0035 §(b)). `true` only on `REJECTED` applications — a database check constraint keeps it from becoming a general-purpose flag — and those days carry forward uncapped at year end.\n"
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "LeaveApplicationCreate": {
        "type": "object",
        "required": [
          "leave_type_id",
          "start_date",
          "end_date"
        ],
        "additionalProperties": false,
        "properties": {
          "leave_type_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "start_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "end_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "is_half_day": {
            "type": "boolean",
            "default": false
          },
          "half_day_session": {
            "$ref": "#/components/schemas/HalfDaySession"
          },
          "reason": {
            "type": "string"
          },
          "document_file_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional supporting document, uploaded beforehand via xc.files (XC-F07, LVE-S02)."
          }
        }
      },
      "LeaveApplicationPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/LeaveApplication"
                }
              }
            }
          }
        ]
      },
      "CompOff": {
        "description": "leave.comp_off (db 05 §2.3).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "comp_off_no",
              "employee_id",
              "worked_date",
              "earned_days",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "comp_off_no": {
                "$ref": "#/components/schemas/BusinessNoRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "worked_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "attendance_record_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "leave_type_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "earned_days": {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              "expires_on": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DateOnlyRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "status": {
                "$ref": "#/components/schemas/CompOffStatus"
              },
              "balance_txn_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "availed_application_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "submitted_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "CompOffCreate": {
        "type": "object",
        "required": [
          "worked_date"
        ],
        "additionalProperties": false,
        "properties": {
          "worked_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "attendance_record_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "leave_type_id": {
            "$ref": "#/components/schemas/UuidRef"
          }
        }
      },
      "CompOffPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CompOff"
                }
              }
            }
          }
        ]
      },
      "LeaveEncashment": {
        "description": "leave.leave_encashments — feeds payroll by event, PAY-F04 / full-and-final PAY-F07 (db 05 §2.4).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "encashment_no",
              "employee_id",
              "leave_type_id",
              "encashed_days",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "encashment_no": {
                "$ref": "#/components/schemas/BusinessNoRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "leave_type_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "leave_policy_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "encashed_days": {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              "rate_per_day_amount": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/MoneyRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "encashment_amount": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/MoneyRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "is_final_settlement": {
                "type": "boolean"
              },
              "status": {
                "$ref": "#/components/schemas/EncashmentStatus"
              },
              "balance_txn_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "submitted_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "pay_period": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "processed_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "LeaveEncashmentCreate": {
        "type": "object",
        "description": "`rate_per_day_amount` and `encashment_amount` are server-computed from leave-policy config (ORG-F05) — never client-supplied.",
        "required": [
          "leave_type_id",
          "encashed_days"
        ],
        "additionalProperties": false,
        "properties": {
          "leave_type_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "encashed_days": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "is_final_settlement": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "LeaveEncashmentPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/LeaveEncashment"
                }
              }
            }
          }
        ]
      },
      "AttendanceCycleStatus": {
        "type": "string",
        "enum": [
          "DRAFT",
          "FINALIZED",
          "AMENDED",
          "REOPENED"
        ],
        "description": "attend.cycle_finalization_status (ADR 0028 §(h); REOPENED appended by migration 0198, issue #1302). DRAFT and REOPENED are both UNSIGNED — finalize signs either — but REOPENED says the snapshot was signed once and pulled back by `reopen` before the pay period locked, which is what lets a batch re-sign it without mistaking its signed siblings for a refusal. ADR 0028 §(h)'s \"no fourth option\" is about the OPERATIONS: a closed cycle is still never edited in place, and the two correction paths are still reopen (pre-lock) and amend (post-lock)."
      },
      "MusterRollStatus": {
        "type": "string",
        "enum": [
          "DRAFT",
          "SUBMITTED",
          "APPROVED",
          "REJECTED"
        ],
        "description": "attend.muster_roll_status (ADR 0032 §(b)). Approval writes the attendance ledger in the same transaction."
      },
      "BeatPlanStatus": {
        "type": "string",
        "enum": [
          "DRAFT",
          "ACTIVE",
          "COMPLETED",
          "CANCELLED"
        ],
        "description": "attend.beat_plan_status (db 05 §1.4) — unchanged by the promotion out of `(Later)`."
      },
      "FieldVisitStatus": {
        "type": "string",
        "enum": [
          "PLANNED",
          "CHECKED_IN",
          "CHECKED_OUT",
          "MISSED",
          "CANCELLED"
        ],
        "description": "attend.field_visit_status (db 05 §1.4) — unchanged by the promotion out of `(Later)`."
      },
      "DeviceStatus": {
        "type": "string",
        "enum": [
          "ACTIVE",
          "OFFLINE",
          "RETIRED"
        ],
        "description": "attend.device_status (ADR 0032 §(c)). `ACTIVE ↔ OFFLINE` is owned by the `attend.device_health_sweep` job; `RETIRED` by `attend.device.retire`."
      },
      "DeviceEventNormalizationStatus": {
        "type": "string",
        "enum": [
          "PENDING",
          "NORMALIZED",
          "IGNORED",
          "FAILED"
        ],
        "description": "`attend.device_event_status` (db 05 §1.6) — the authoritative spelling, adopted here in place of this file's earlier draft `RECEIVED · NORMALIZED · REJECTED · DUPLICATE` *(revised, issue #583)*. The two surplus members were never row states: `RECEIVED` is `PENDING` under another name, and `DUPLICATE` is a per-request **count** (see `DeviceEventIngestResponse.duplicates`) — a replayed flush inserts nothing at all, because `(tenant_id, device_id, external_event_id)` is unique, so no row can ever hold it. Staging is append-only: a `FAILED` raw row is retained with its reason as evidence, never deleted, and stays re-drainable once the badge is mapped or the normalizer patched. `IGNORED` is the different case — an event that is legitimately not attendance-bearing (a heartbeat, a door-open, a debounced re-scan).\n"
      },
      "DecimalCount": {
        "type": "string",
        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
        "description": "numeric(9,2) count carried as a DECIMAL STRING, never a JSON number (db-docs/00 §6). Every count on a finalization snapshot and every `units_done` uses this shape, because a float day-count is a wrong payslip.\n"
      },
      "AttendanceCycleFinalization": {
        "description": "'attend.attendance_cycle_finalizations — the signed snapshot of one `(employee, cycle)` that is the contract between attendance and payroll (ADR 0028 §(h)).' The counts are what payroll pays on; they are frozen here and never re-read live from `attend` at populate time. `snapshot_hash` is computed over the count set so a payslip can prove which snapshot it was computed from.\n",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "pay_group_ref",
              "cycle_start",
              "cycle_end",
              "status",
              "payable_days",
              "present_days",
              "half_days",
              "paid_leave_days",
              "lop_days",
              "weekly_offs",
              "holidays",
              "ot_hours_approved",
              "late_marks",
              "piece_units_total",
              "days_worked",
              "amendment_seq",
              "snapshot_hash",
              "version"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "pay_group_ref": {
                "$ref": "#/components/schemas/UuidRef",
                "description": "Soft `ref→org.pay_groups` — never a join (db-docs/00 §13)."
              },
              "cycle_start": {
                "$ref": "#/components/schemas/DateOnlyRef",
                "description": "First day MEASURED. Deliberately distinct from the pay period's `period_start`, which is what is PAID FOR — the two are usually different windows, and conflating them is the market's largest silent-error class (ADR 0028 Context)."
              },
              "cycle_end": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "status": {
                "$ref": "#/components/schemas/AttendanceCycleStatus"
              },
              "payable_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "present_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "half_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "paid_leave_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "lop_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "weekly_offs": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "holidays": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "ot_hours_approved": {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              "late_marks": {
                "type": "integer",
                "minimum": 0
              },
              "piece_units_total": {
                "$ref": "#/components/schemas/DecimalCount",
                "description": "Σ `attend.muster_entries.units_done` over the cycle — the piece-rate quantity ADR 0033 §(e) prices. No separate feed exists or is needed."
              },
              "days_worked": {
                "$ref": "#/components/schemas/DecimalCount",
                "description": "Carried specifically for **Labour-Code 1-day-per-20-worked leave accrual**. The accrual rule itself lives in `leave` and is not implemented by this wave."
              },
              "amendment_seq": {
                "type": "integer",
                "minimum": 0,
                "description": "Bumped by `attend.attendance_cycle.amend`; `0` until the first amendment."
              },
              "snapshot_hash": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "Hash over the count set. A payslip cites it so a two-year-old figure is still provably derived from this exact snapshot."
              },
              "finalized_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "finalized_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "AttendanceCycleFinalizationPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttendanceCycleFinalization"
                }
              }
            }
          }
        ]
      },
      "AttendanceCyclePreviewRow": {
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "pay_group_ref",
              "cycle_start",
              "cycle_end",
              "status",
              "payable_days",
              "present_days",
              "half_days",
              "paid_leave_days",
              "lop_days",
              "weekly_offs",
              "holidays",
              "ot_hours_approved",
              "late_marks",
              "piece_units_total",
              "days_worked",
              "amendment_seq",
              "snapshot_hash",
              "is_preview",
              "recorded_days",
              "expected_days",
              "version"
            ],
            "properties": {
              "id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "pay_group_ref": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "cycle_start": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "cycle_end": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "status": {
                "$ref": "#/components/schemas/AttendanceCycleStatus"
              },
              "payable_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "present_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "half_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "paid_leave_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "lop_days": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "weekly_offs": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "holidays": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "ot_hours_approved": {
                "$ref": "#/components/schemas/DecimalHoursRef"
              },
              "late_marks": {
                "type": "integer",
                "minimum": 0
              },
              "piece_units_total": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "days_worked": {
                "$ref": "#/components/schemas/DecimalCount"
              },
              "amendment_seq": {
                "type": "integer",
                "minimum": 0
              },
              "snapshot_hash": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$"
              },
              "finalized_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "finalized_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "is_preview": {
                "type": "boolean",
                "description": "True when counts are live and unsigned; false when a persisted signed snapshot is returned unchanged."
              },
              "recorded_days": {
                "type": "integer",
                "minimum": 0
              },
              "expected_days": {
                "type": "integer",
                "minimum": 0
              },
              "version": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        ]
      },
      "AttendanceCyclePreview": {
        "type": "object",
        "required": [
          "data",
          "coverage"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AttendanceCyclePreviewRow"
            }
          },
          "coverage": {
            "type": "object",
            "required": [
              "expected_days",
              "recorded_days",
              "complete"
            ],
            "properties": {
              "expected_days": {
                "type": "integer",
                "minimum": 0
              },
              "recorded_days": {
                "type": "integer",
                "minimum": 0
              },
              "complete": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "AttendanceMonthClose": {
        "type": "object",
        "description": "Issue",
        "required": [
          "month",
          "cycle_start",
          "cycle_end",
          "pay_group_id",
          "period_status",
          "payroll_month_status",
          "live_payroll",
          "pay_teams_enabled",
          "teams",
          "members",
          "not_in_payroll",
          "checklist"
        ],
        "properties": {
          "month": {
            "type": "string",
            "example": "2026-09"
          },
          "cycle_start": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "cycle_end": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "pay_group_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "period_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "OPEN",
              "CUTOFF_PASSED",
              "INPUTS_LOCKED",
              "RUN_LINKED",
              "CLOSED",
              null
            ],
            "description": "The legacy pay period for the window, or — on live payroll — the payroll month (`OPEN` while DRAFT or not yet prepared, `INPUTS_LOCKED` once past DRAFT). `null` ⇒ nothing owns the window and finalize refuses."
          },
          "payroll_month_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "`payroll.months.status` for the window, `null` when not prepared."
          },
          "live_payroll": {
            "type": "boolean",
            "description": "`payroll.settings` exists for the tenant."
          },
          "pay_teams_enabled": {
            "type": "boolean"
          },
          "teams": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "pay_team_id",
                "team_id",
                "name",
                "kind",
                "default_daily_rate_amount",
                "settlement_method",
                "contractor",
                "member_count",
                "rate_missing"
              ],
              "properties": {
                "pay_team_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "team_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "name": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "CONTRACTOR",
                    "STAFF"
                  ]
                },
                "default_daily_rate_amount": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "settlement_method": {
                  "type": "string"
                },
                "contractor": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "employee_id": {
                      "$ref": "#/components/schemas/UuidRef"
                    },
                    "employee_no": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "full_name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "member_count": {
                  "type": "integer",
                  "minimum": 0
                },
                "rate_missing": {
                  "type": "boolean",
                  "description": "CONTRACTOR team with no default rate and at least one member with no own rate."
                }
              }
            }
          },
          "members": {
            "type": "array",
            "description": "One per employee in the close population (the preview's population).",
            "items": {
              "type": "object",
              "required": [
                "employee_id",
                "pay_team_id",
                "daily_wage",
                "daily_rate_amount",
                "missing_days",
                "open"
              ],
              "properties": {
                "employee_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "pay_team_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "daily_wage": {
                  "type": "boolean"
                },
                "daily_rate_amount": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "missing_days": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Days in the month with no attendance record that finalize would refuse on."
                },
                "open": {
                  "type": "object",
                  "required": [
                    "absent_appeals",
                    "regularizations",
                    "overtime",
                    "missed_punches"
                  ],
                  "properties": {
                    "absent_appeals": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "regularizations": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "overtime": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "missed_punches": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "not_in_payroll": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UuidRef"
            }
          },
          "checklist": {
            "type": "object",
            "required": [
              "absent_appeals",
              "regularizations",
              "overtime",
              "missed_punches",
              "muster_draft",
              "muster_submitted",
              "missing_days",
              "missing_days_people",
              "contractor_teams_missing_rate",
              "not_in_payroll"
            ],
            "properties": {
              "absent_appeals": {
                "type": "integer",
                "minimum": 0
              },
              "regularizations": {
                "type": "integer",
                "minimum": 0
              },
              "overtime": {
                "type": "integer",
                "minimum": 0
              },
              "missed_punches": {
                "type": "integer",
                "minimum": 0
              },
              "muster_draft": {
                "type": "integer",
                "minimum": 0
              },
              "muster_submitted": {
                "type": "integer",
                "minimum": 0
              },
              "missing_days": {
                "type": "integer",
                "minimum": 0
              },
              "missing_days_people": {
                "type": "integer",
                "minimum": 0
              },
              "contractor_teams_missing_rate": {
                "type": "integer",
                "minimum": 0
              },
              "not_in_payroll": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        }
      },
      "AttendanceCycleFinalizeRequest": {
        "type": "object",
        "description": "Selects the population and the window. **Exactly one of `pay_group_id` or `org_unit_id` is required** (neither, or both, is a `422` with rule `cross-field`) — OpenAPI cannot express \"exactly one of\", the same limitation the platform surface works around with an `anyOf`.\n",
        "additionalProperties": false,
        "required": [
          "cycle_start",
          "cycle_end"
        ],
        "properties": {
          "pay_group_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "Soft `ref→org.pay_groups`. The normal selector — the pay group IS the population segment (ADR 0028 §(a))."
          },
          "org_unit_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "Alternative selector for a tenant finalizing by reporting structure rather than by pay group."
          },
          "cycle_start": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "cycle_end": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "note": {
            "type": "string",
            "maxLength": 2000,
            "description": "Recorded on every snapshot the call produces."
          },
          "employee_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5000,
            "items": {
              "$ref": "#/components/schemas/UuidRef"
            },
            "description": "Scopes the batch to a SUBSET of the resolved population (issue #1302) — the operator re-signing exactly the rows they reopened. Omitted, the batch reaches every unsigned row in the population, which is the default and the pre-existing behaviour. An id the selector did not resolve is a `422` with rule `cross-field`, never a silently-dropped member. Duplicates are collapsed.\n"
          }
        }
      },
      "AttendanceCycleFinalizeAccepted": {
        "type": "object",
        "description": "The accepted finalization batch, so the caller can poll or reconcile against the emitted event.",
        "required": [
          "batch_id",
          "employee_count",
          "to_sign",
          "already_signed"
        ],
        "properties": {
          "batch_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "employee_count": {
            "type": "integer",
            "minimum": 0,
            "description": "Employees in the selected population at acceptance time, AFTER any `employee_ids` scoping."
          },
          "to_sign": {
            "type": "integer",
            "minimum": 1,
            "description": "Unsigned rows this batch will sign — `DRAFT` + `REOPENED` + employees with no snapshot yet (issue #1302). Zero is not an accepted state; it is the `409`."
          },
          "already_signed": {
            "type": "integer",
            "minimum": 0,
            "description": "Signed (`FINALIZED`/`AMENDED`) siblings the batch leaves exactly as they are. Non-zero is the ordinary mixed-population case a reopen creates, not an error."
          },
          "cycle_start": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "cycle_end": {
            "$ref": "#/components/schemas/DateOnlyRef"
          }
        }
      },
      "AttendanceCycleReopenRequest": {
        "type": "object",
        "description": "Refused (`409`) once the target `pay.pay_periods` row has reached `INPUTS_LOCKED` — after that the only correction is `amend`. The reopened row lands in `REOPENED`, not `DRAFT` (issue #1302).",
        "additionalProperties": false,
        "required": [
          "reason"
        ],
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          }
        }
      },
      "AttendanceCycleAmendRequest": {
        "type": "object",
        "description": "The post-lock correction. A mandatory reason, plus the counts that changed — the service recomputes `snapshot_hash`, bumps `amendment_seq` and emits `attend.attendance_cycle.amended`, which `pay` turns into an `ARREAR` input and an arrear receipt rather than a rewrite of a paid summary.\n",
        "additionalProperties": false,
        "required": [
          "reason"
        ],
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          },
          "payable_days": {
            "$ref": "#/components/schemas/DecimalCount"
          },
          "present_days": {
            "$ref": "#/components/schemas/DecimalCount"
          },
          "half_days": {
            "$ref": "#/components/schemas/DecimalCount"
          },
          "paid_leave_days": {
            "$ref": "#/components/schemas/DecimalCount"
          },
          "lop_days": {
            "$ref": "#/components/schemas/DecimalCount"
          },
          "ot_hours_approved": {
            "$ref": "#/components/schemas/DecimalHoursRef"
          },
          "piece_units_total": {
            "$ref": "#/components/schemas/DecimalCount"
          },
          "days_worked": {
            "$ref": "#/components/schemas/DecimalCount"
          }
        }
      },
      "MusterTemplateStatus": {
        "type": "string",
        "enum": [
          "ACTIVE",
          "ARCHIVED"
        ],
        "description": "attend.muster_template_status (#1298). There is no delete — a template a roll was built from is provenance, so retiring one is `ARCHIVED`, exactly as a declined roll is `REJECTED` rather than gone. Archiving also releases the template's name to a successor."
      },
      "MusterTemplateMember": {
        "description": "attend.muster_template_members — one pinned crew member and what a new roll is pre-seeded with.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "default_day_status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "default_day_status": {
                "$ref": "#/components/schemas/AttendanceStatus",
                "description": "The status a roll opened from this template asserts for this person. Never `PENDING` (database CHECK): a template carries a decision forward, and `PENDING` is the absence of one."
              },
              "unit_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The piece-rate unit this member is normally counted against, so a quantity column arrives already pointed at a priced unit (ADR 0033 §(c))."
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "MusterTemplate": {
        "description": "attend.muster_templates — one supervisor's saved crew for one site. The entity that turns a roll's crew from a derivation into an assertion (#1298).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "name",
              "work_location_id",
              "owner_employee_id",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128
              },
              "work_location_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "shift_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The shift this crew works. A roll had NO shift before #1298, which is why a pre-filled in/out time had nothing to mean — a roll opened from a template with a shift arrives with each entry's clock times derived from the shift and the site's own timezone."
              },
              "owner_employee_id": {
                "$ref": "#/components/schemas/UuidRef",
                "description": "The supervisor the template belongs to, and the RLS ownership arm. Written from the authenticated actor, never from the body."
              },
              "notes": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 2000
              },
              "member_count": {
                "type": "integer",
                "minimum": 0
              },
              "last_used_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Stamped when a roll is opened from this template — the most-recently-used signal the create modal defaults from."
              },
              "status": {
                "$ref": "#/components/schemas/MusterTemplateStatus"
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "MusterTemplateDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MusterTemplate"
          },
          {
            "type": "object",
            "properties": {
              "members": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/MusterTemplateMember"
                }
              }
            }
          }
        ]
      },
      "MusterTemplatePage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/MusterTemplate"
                }
              }
            }
          }
        ]
      },
      "MusterTemplateMemberInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "employee_id"
        ],
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "default_day_status": {
            "$ref": "#/components/schemas/AttendanceStatus",
            "description": "Defaults to `PRESENT`. `PENDING` is refused (`422`)."
          },
          "unit_code": {
            "type": "string",
            "description": "Validated against the tenant's ACTIVE `org.piece_rate_items`, exactly as a muster entry's is."
          }
        }
      },
      "MusterTemplateCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "work_location_id",
          "members"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Unique per site among ACTIVE templates."
          },
          "work_location_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "shift_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "Optional. Must be an ACTIVE `org.shifts` row."
          },
          "owner_employee_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "Optional — defaults to the authenticated actor. Naming another supervisor requires the same token plus team scope over them (`403 SCOPE_DENIED` otherwise), exactly as `attend.muster.create` does."
          },
          "notes": {
            "type": "string",
            "maxLength": 2000
          },
          "members": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "items": {
              "$ref": "#/components/schemas/MusterTemplateMemberInput"
            }
          }
        }
      },
      "MusterTemplateUpdate": {
        "type": "object",
        "description": "At least one field. `members` REPLACES the crew wholesale — a partial member patch would leave the caller unable to remove anybody.",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "shift_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` clears the shift."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "status": {
            "$ref": "#/components/schemas/MusterTemplateStatus"
          },
          "members": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "items": {
              "$ref": "#/components/schemas/MusterTemplateMemberInput"
            }
          }
        }
      },
      "MusterRoll": {
        "description": "attend.muster_rolls — one supervisor's record of one site's day, unique per `(tenant, work_location_id, work_date, supervisor_employee_id)`.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "muster_no",
              "work_location_id",
              "work_date",
              "supervisor_employee_id",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "muster_no": {
                "type": "string",
                "description": "Tenant-unique human-readable reference, `MUS-000042` (db 05 §1.5/§1.10)."
              },
              "client_roll_id": {
                "$ref": "#/components/schemas/UuidRef",
                "description": "The capture-time offline-queue idempotency key (`attend.muster.sync`), independent of the HTTP `Idempotency-Key` — `04 §1.1`'s two-key shape. Server-minted when a roll is opened online."
              },
              "work_location_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "work_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "supervisor_employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "site": {
                "description": "LIST only (2026-09-28, Muster approvals page) — the site label, so a queue grouped by site needs no second read.",
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "location_code": {
                        "type": "string"
                      },
                      "name": {
                        "type": "object",
                        "additionalProperties": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "submitted_by_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "LIST only — who locked the roll."
              },
              "shift_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The shift this site-day was worked on (#1298). The roll carried no shift at all before that, which is why a pre-filled in/out time had nothing to mean. `null` for a roll opened without one, including every roll that predates the column."
              },
              "muster_template_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The saved crew this roll was opened from (#1298), or `null` for one opened from the bare modal. Provenance, which is why archiving a template never deletes it."
              },
              "status": {
                "$ref": "#/components/schemas/MusterRollStatus"
              },
              "entry_count": {
                "type": "integer",
                "minimum": 0
              },
              "submitted_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The MAKER. Four-eyes: `submitted_by <> approved_by` is a database CHECK, not only a service rule."
              },
              "submitted_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_by": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "approved_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "applied_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "When the approved roll was written into `attendance_records` — same transaction as the approval."
              },
              "notes": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 2000,
                "description": "The supervisor's note for the approver; a decision note is appended to it."
              },
              "handlers": {
                "type": "array",
                "description": "Active employees delegated to capture this draft roll. The supervisor remains accountable for submission and approval.",
                "items": {
                  "$ref": "#/components/schemas/MusterRollHandler"
                }
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "MusterSelfPunchSummary": {
        "type": "object",
        "required": [
          "first_in_at",
          "last_out_at",
          "open",
          "face_matched",
          "geofence_ok"
        ],
        "properties": {
          "first_in_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "last_out_at": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "open": {
            "type": "boolean"
          },
          "face_matched": {
            "type": "boolean"
          },
          "geofence_ok": {
            "type": "boolean"
          }
        }
      },
      "MusterEntry": {
        "description": "attend.muster_entries — one employee's day on a roll. `recorded_by` is NOT NULL and is written from the authenticated actor, never from the request body (ADR 0032 §(b)).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "day_status",
              "recorded_by"
            ],
            "properties": {
              "capture_channel": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "SITE",
                  "TEAM",
                  "OVERRIDE",
                  null
                ]
              },
              "recorded_via_team_id": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "team_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The TEAM-channel squad name, if present."
              },
              "team_lead_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The current lead of that squad, if present."
              },
              "face": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/MusterFaceVerdict"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "marker_fence_result": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "INSIDE",
                  "OUTSIDE",
                  "UNKNOWN",
                  "SITE_NOT_GEOMAPPED",
                  null
                ]
              },
              "face_enrolment": {
                "type": "string",
                "enum": [
                  "ACTIVE",
                  "PENDING",
                  "NONE"
                ],
                "description": "The worker's face registration (ACTIVE / awaiting HR approval / none)."
              },
              "full_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "employee_code": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "override_reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "day_status": {
                "$ref": "#/components/schemas/AttendanceStatus",
                "description": "The existing `attend.attendance_status` domain — muster does not introduce a parallel vocabulary."
              },
              "in_time": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Wire name for the `attend.muster_entries.in_at` column."
              },
              "out_time": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Wire name for the `attend.muster_entries.out_at` column."
              },
              "unit_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The stable machine key from the employee's `org.piece_rate_catalogs` items — capture and pricing share one vocabulary (ADR 0033 §(c)). Validated against the tenant's ACTIVE items on record."
              },
              "units_done": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/DecimalCount"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Decimal string, never a float; stays one all the way through pricing. A JSON number is refused (`422`) rather than coerced — a float that has already lost precision in the parser cannot be recovered downstream."
              },
              "remarks": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 2000
              },
              "recorded_by": {
                "$ref": "#/components/schemas/UuidRef",
                "description": "Who asserted this person's day. The ADR 0025 analogue of `verified_by` — the accountability that makes on-behalf capture defensible at all."
              },
              "applied_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "When this entry was upserted into `attendance_records` (set by the approval, in its own transaction). **Stays `null` on an APPROVED roll when the provenance guard refused the day** (#1172) — the entry never became a day, and the roll says so rather than claiming a write that did not happen."
              },
              "superseded": {
                "type": "boolean",
                "description": "The before-image marker (#582, as-built): **derived, not stored** — true when a LATER applied muster entry for the same person and day has overwritten the `attendance_records` row this one wrote. The superseded entry stays on its roll rather than being deleted (db 05 §1.5)."
              },
              "self_punch": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/MusterSelfPunchSummary"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "V2 worker self-punch evidence, read only. A marker must not overwrite this day."
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "MusterRollHandler": {
        "type": "object",
        "required": [
          "id",
          "employee_id",
          "assigned_by",
          "assigned_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "assigned_by": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "assigned_at": {
            "$ref": "#/components/schemas/TimestampRef"
          }
        }
      },
      "MusterLedgerConflict": {
        "description": "One entry an approved roll **refused to write** because the day already carried a stronger record (#1172). It names what won, so the refusal is legible without a second lookup. The entry stays on its roll with `applied_at: null` — it never became a day.\n",
        "type": "object",
        "required": [
          "muster_entry_id",
          "employee_id",
          "work_date"
        ],
        "properties": {
          "muster_entry_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "existing_source": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AttendanceSource"
              },
              {
                "type": "null"
              }
            ],
            "description": "The provenance already on the day — `MOBILE_GEOFENCE` / `WEB` / `DEVICE` / `BIOMETRIC` / `IMPORT` / `REGULARIZED`. Never `MUSTER` or `SYSTEM`: those two are the rows a roll IS allowed to write."
          },
          "existing_status": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AttendanceStatus"
              },
              {
                "type": "null"
              }
            ]
          },
          "existing_is_regularized": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "True when an approved regularization already decided this day — a human decision a later roll must not quietly undo."
          }
        }
      },
      "MusterLedgerOutcome": {
        "description": "**What the approval actually wrote (#1172).** A muster roll is a supervisor's on-behalf assertion and ADR 0032 calls it *a weaker evidentiary class than a verified punch*, so the ledger upsert overwrites only rows this mechanism owns — its own earlier `MUSTER` assertion (ADR 0032 §(b)'s intended re-approval upsert) and the nightly materializer's `SYSTEM` placeholder. A day already captured by a verified punch, a device push, an import or an approved regularization keeps its status, its clock times and its provenance, and is reported here instead of being silently replaced — ADR 0032 §(e)'s muster-vs-punch disagreement case. In v2, PENDING entries are counted separately as `self_attended` or `not_marked` and write no ledger row.\n",
        "type": "object",
        "required": [
          "applied",
          "skipped_verified",
          "conflicts"
        ],
        "properties": {
          "applied": {
            "type": "integer",
            "minimum": 0,
            "description": "Entries that became an `attend.attendance_records` day."
          },
          "skipped_verified": {
            "type": "integer",
            "minimum": 0,
            "description": "Entries the provenance guard refused. **Not an error** — the roll is still `APPROVED`; these days simply already had a stronger record."
          },
          "not_marked": {
            "type": "integer",
            "minimum": 0,
            "description": "V2 only; PENDING allocations that wrote no ledger day."
          },
          "self_attended": {
            "type": "integer",
            "minimum": 0,
            "description": "V2 only; PENDING allocations with a final worker self-punch."
          },
          "conflicts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MusterLedgerConflict"
            }
          }
        }
      },
      "MusterRollDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MusterRoll"
          },
          {
            "type": "object",
            "properties": {
              "site_geomapped": {
                "type": "boolean"
              },
              "entries": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/MusterEntry"
                }
              },
              "ledger": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/MusterLedgerOutcome"
                  }
                ],
                "description": "Present **only on the `attend.muster.approve` response** (#1172) — the read paths (`attend.muster.get`, `attend.muster.record`) omit it, because nothing was written by them."
              }
            }
          }
        ]
      },
      "MusterRollPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/MusterRoll"
                }
              }
            }
          }
        ]
      },
      "MusterRollCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "work_location_id",
          "work_date"
        ],
        "properties": {
          "work_location_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "supervisor_employee_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "Optional — defaults to the authenticated actor. Naming another supervisor requires the same token plus team scope over them (`403 SCOPE_DENIED` otherwise)."
          },
          "client_roll_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "Optional capture-time key for a roll first created offline; server-minted when omitted. A UUID, matching the `attend.muster_rolls.client_roll_id` column (#582, as-built)."
          },
          "notes": {
            "type": "string",
            "maxLength": 2000,
            "description": "The supervisor's note for the approver (#582, as-built — the column exists in db 05 §1.5 and this is the op that sets it)."
          },
          "template_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "Open the roll from a saved crew (#1298). The template''s members are pre-seeded as `attend.muster_entries` **in the same transaction as the roll insert**, each with the member''s `default_day_status` and `unit_code`, and — when the template or the body names a shift — with `in_time`/`out_time` derived from the shift and the site''s own timezone. A member who is no longer an ACTIVE employee is silently NOT seeded: a separated worker must never arrive with a pre-asserted present day.\n\n**Seeding entries is recording**, so this additionally requires `attend.muster.record` (`403 TOKEN_DENIED` otherwise) — saving a crew and asserting that crew worked stay two grants. The template''s `work_location_id` must equal `work_location_id` (`422` otherwise): a template pins a crew to a site, and using it at another site is a mistake, not a widening.\n"
          },
          "shift_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "The shift this site-day is worked on (#1298). Defaults to the template's when `template_id` is supplied and this is omitted. Must be an ACTIVE `org.shifts` row."
          }
        }
      },
      "MusterV2Problem": {
        "description": "Muster v2's operation-specific `type` URIs on the platform's existing closed-set `code`s. Each `detail` is a complete user-facing sentence. Face comparison scores, distance and thresholds are never returned. The first 19 types use the attend prefix; the final two are ESS refusals in the pay and leave namespaces.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Problem"
          },
          {
            "type": "object",
            "required": [
              "type",
              "code",
              "detail"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "urn:groundit:problem:attend:muster-self-attendance-disabled",
                  "urn:groundit:problem:attend:muster-self-attendance-mobile-only",
                  "urn:groundit:problem:attend:muster-no-site-today",
                  "urn:groundit:problem:attend:muster-day-marked-by-supervisor",
                  "urn:groundit:problem:attend:muster-site-not-geomapped",
                  "urn:groundit:problem:attend:muster-location-required",
                  "urn:groundit:problem:attend:muster-face-check-required",
                  "urn:groundit:problem:attend:muster-face-mismatch",
                  "urn:groundit:problem:attend:muster-face-check-invalid",
                  "urn:groundit:problem:attend:muster-worker-not-enrolled",
                  "urn:groundit:problem:attend:muster-worker-already-enrolled",
                  "urn:groundit:problem:attend:muster-face-enrolment-pending",
                  "urn:groundit:problem:attend:muster-site-supervisor-submits",
                  "urn:groundit:problem:attend:muster-marker-location-required",
                  "urn:groundit:problem:attend:muster-marker-outside-site",
                  "urn:groundit:problem:attend:muster-override-not-allowed",
                  "urn:groundit:problem:attend:muster-allocation-cannot-mark",
                  "urn:groundit:problem:attend:muster-team-marking-disabled",
                  "urn:groundit:problem:attend:muster-marker-not-at-any-site",
                  "urn:groundit:problem:attend:muster-site-ambiguous",
                  "urn:groundit:problem:attend:muster-site-roll-closed",
                  "urn:groundit:problem:pay:payslips-not-offered",
                  "urn:groundit:problem:leave:not-offered"
                ]
              }
            }
          }
        ]
      },
      "MusterTeamList": {
        "type": "object",
        "required": [
          "work_date",
          "teams",
          "sites_here",
          "settings"
        ],
        "properties": {
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "teams": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "members"
              ],
              "properties": {
                "id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "name": {
                  "type": "string"
                },
                "members": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "employee_id",
                      "employee_code",
                      "full_name",
                      "face_enrolled",
                      "face_enrolment",
                      "state",
                      "site",
                      "roll",
                      "mark",
                      "self_punch",
                      "here",
                      "actions"
                    ],
                    "properties": {
                      "employee_id": {
                        "$ref": "#/components/schemas/UuidRef"
                      },
                      "employee_code": {
                        "type": "string"
                      },
                      "full_name": {
                        "type": "string"
                      },
                      "face_enrolled": {
                        "type": "boolean"
                      },
                      "face_enrolment": {
                        "type": "string",
                        "enum": [
                          "ACTIVE",
                          "PENDING",
                          "NONE"
                        ],
                        "description": "PENDING = registration awaiting HR approval; `actions` then offers only MARK_ABSENT."
                      },
                      "actions": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "enum": [
                            "VERIFY_AND_MARK",
                            "REGISTER_FACE",
                            "MARK_ABSENT",
                            "UNMARK"
                          ]
                        },
                        "description": "Empty for a member on no site roll today — My team never allocates (2026-09-28); POST marks for such a member answer 409 `muster-no-site-today`."
                      },
                      "state": {
                        "type": "string",
                        "enum": [
                          "SUPERVISOR_MARKED",
                          "SELF_PUNCHED",
                          "NOT_ALLOCATED",
                          "SITE_NOT_GEOMAPPED",
                          "ALLOCATED"
                        ]
                      },
                      "site": {
                        "oneOf": [
                          {
                            "type": "object",
                            "required": [
                              "work_location_id",
                              "location_code",
                              "name",
                              "geomapped",
                              "maps_url"
                            ],
                            "properties": {
                              "work_location_id": {
                                "$ref": "#/components/schemas/UuidRef"
                              },
                              "location_code": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "geomapped": {
                                "type": "boolean"
                              },
                              "maps_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            }
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "roll": {
                        "oneOf": [
                          {
                            "type": "object",
                            "required": [
                              "id",
                              "status",
                              "version"
                            ],
                            "properties": {
                              "id": {
                                "$ref": "#/components/schemas/UuidRef"
                              },
                              "status": {
                                "$ref": "#/components/schemas/MusterRollStatus"
                              },
                              "version": {
                                "type": "integer"
                              }
                            }
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "mark": {
                        "oneOf": [
                          {
                            "type": "object",
                            "required": [
                              "day_status",
                              "channel",
                              "recorded_by_name",
                              "at"
                            ],
                            "properties": {
                              "day_status": {
                                "$ref": "#/components/schemas/AttendanceStatus"
                              },
                              "channel": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "SITE",
                                  "TEAM",
                                  "OVERRIDE",
                                  null
                                ]
                              },
                              "recorded_by_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "at": {
                                "$ref": "#/components/schemas/TimestampRef"
                              }
                            }
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "self_punch": {
                        "oneOf": [
                          {
                            "$ref": "#/components/schemas/MusterSelfPunchSummary"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "here": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          },
          "sites_here": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "work_location_id",
                "location_code",
                "name"
              ],
              "properties": {
                "work_location_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "location_code": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              }
            }
          },
          "settings": {
            "type": "object",
            "required": [
              "supervisor_face",
              "marker_geofence"
            ],
            "properties": {
              "supervisor_face": {
                "type": "string",
                "enum": [
                  "OFF",
                  "ADVISORY",
                  "REQUIRED"
                ]
              },
              "marker_geofence": {
                "type": "string",
                "enum": [
                  "ADVISORY",
                  "BLOCKING"
                ]
              }
            }
          }
        }
      },
      "MusterTeamMarkRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "work_date",
          "marks"
        ],
        "properties": {
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "marker_location": {
            "$ref": "#/components/schemas/MusterMarkerLocation"
          },
          "marks": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "employee_id",
                "day_status"
              ],
              "properties": {
                "employee_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "day_status": {
                  "type": "string",
                  "enum": [
                    "PENDING",
                    "PRESENT",
                    "HALF_DAY",
                    "ABSENT"
                  ]
                },
                "face_check_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "work_location_id": {
                  "$ref": "#/components/schemas/UuidRef"
                }
              }
            }
          }
        }
      },
      "MusterTeamMarkResponse": {
        "type": "object",
        "required": [
          "results",
          "rolls"
        ],
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "employee_id",
                "muster_roll_id",
                "muster_no",
                "day_status"
              ],
              "properties": {
                "employee_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "muster_roll_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "muster_no": {
                  "type": "string"
                },
                "day_status": {
                  "type": "string",
                  "enum": [
                    "PENDING",
                    "PRESENT",
                    "HALF_DAY",
                    "ABSENT"
                  ]
                }
              }
            }
          },
          "rolls": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "version",
                "status"
              ],
              "properties": {
                "id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "version": {
                  "type": "integer"
                },
                "status": {
                  "$ref": "#/components/schemas/MusterRollStatus"
                }
              }
            }
          }
        }
      },
      "MusterDay": {
        "type": "object",
        "description": "Own worker-day projection. `applicable:false` is the entire response for a non-MUSTER employee. For a muster worker, state precedence is supervisor mark, self punch, no allocation, unmapped site, allocation. Coordinate decimals are strings; a Maps link is null without a fence.\n",
        "required": [
          "applicable"
        ],
        "properties": {
          "applicable": {
            "type": "boolean"
          },
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "state": {
            "type": "string",
            "enum": [
              "NOT_MUSTER",
              "SUPERVISOR_MARKED",
              "SELF_PUNCHED",
              "NOT_ALLOCATED",
              "SITE_NOT_GEOMAPPED",
              "ALLOCATED"
            ]
          },
          "site": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "work_location_id": {
                "type": "string",
                "format": "uuid"
              },
              "location_code": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "address": {
                "type": [
                  "object",
                  "null"
                ]
              },
              "geomapped": {
                "type": "boolean"
              },
              "fence": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "shape": {
                    "type": "string",
                    "enum": [
                      "CIRCLE",
                      "POLYGON"
                    ]
                  },
                  "center_lat": {
                    "type": "string"
                  },
                  "center_lng": {
                    "type": "string"
                  },
                  "radius_m": {
                    "type": "integer"
                  },
                  "polygon": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              },
              "maps_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              }
            }
          },
          "roll": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "muster_no": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "APPROVED"
                ]
              }
            }
          },
          "supervisor_mark": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "day_status": {
                "type": "string",
                "enum": [
                  "PRESENT",
                  "HALF_DAY",
                  "ABSENT"
                ]
              },
              "channel": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "SITE",
                  "TEAM",
                  "OVERRIDE",
                  null
                ]
              },
              "recorded_by_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "recorded_at": {
                "type": "string",
                "format": "date-time"
              },
              "in_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "out_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "confirmed": {
                "type": "boolean"
              }
            }
          },
          "self_punch": {
            "type": "object",
            "properties": {
              "allowed": {
                "type": "boolean"
              },
              "next": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "IN",
                  "OUT",
                  null
                ]
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "SELF_ATTENDANCE_OFF",
                  "NOT_ALLOCATED",
                  "SITE_NOT_GEOMAPPED",
                  "MARKED_BY_SUPERVISOR",
                  "FACE_NOT_ENROLLED",
                  null
                ]
              },
              "first_in_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "open_session_since": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "face_required": {
                "type": "boolean"
              }
            }
          },
          "shift": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "shift_id": {
                "type": "string",
                "format": "uuid"
              },
              "code": {
                "type": "string"
              },
              "start_time": {
                "type": "string"
              },
              "end_time": {
                "type": "string"
              },
              "grace_in_minutes": {
                "type": "integer"
              },
              "absence_after_minutes": {
                "type": "integer"
              }
            }
          },
          "face_enrolment": {
            "type": "string",
            "enum": [
              "ACTIVE",
              "PENDING",
              "REJECTED",
              "NONE"
            ]
          }
        }
      },
      "AttendSettings": {
        "type": "object",
        "required": [
          "daily_site_rolls",
          "version"
        ],
        "properties": {
          "daily_site_rolls": {
            "type": "boolean",
            "description": "One shared muster roll per site per day, kept by site supervisors and super admins; a worker may be on only one live roll per date (`409 urn:groundit:problem:attend:muster-worker-on-another-roll` at record, submit, approve, sync and assign); crew templates hidden; the MUSTER envelope routed at the rule's ROLE step so either super admin decides it.\n"
          },
          "holiday_scope_types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HolidayScopeType"
            },
            "description": "Scope types the month-grid holiday dialog offers (#1879, migration 0272). Default all six; S&A Energies uses `[TEAM]`. `POST /attendance-holidays` refuses any other type with `422`.\n"
          },
          "absent_appeal": {
            "type": "boolean",
            "description": "#1878, migration 0270. ON: a first IN after the absence cutoff (non-MUSTER employees) is refused `ABSENT_APPEAL_REQUIRED` until the reporting manager approves an absent appeal. Default false.\n"
          },
          "absence_leave_conversion": {
            "type": "boolean",
            "description": "#1878. ON: after local midnight a salaried employee's ABSENT day spends 1 day of `absence_leave_type_id` (CL), a HALF_DAY 0.5, capped by balance and the policy's `monthly_limit_days`; the rest is LOP. Daily-wage and contractor-team workers are never converted. Default false.\n"
          },
          "absence_leave_type_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "#1878 — the leave type an absence converts into (resolved per legal entity by `type_code`); null → the entity's CASUAL type."
          },
          "muster_self_attendance": {
            "type": "boolean",
            "default": false,
            "description": "Allows self-punch only at the allocated site."
          },
          "muster_supervisor_face": {
            "type": "string",
            "enum": [
              "OFF",
              "ADVISORY",
              "REQUIRED"
            ],
            "default": "OFF"
          },
          "muster_marker_geofence": {
            "type": "string",
            "enum": [
              "ADVISORY",
              "BLOCKING"
            ],
            "default": "ADVISORY"
          },
          "muster_team_marking": {
            "type": "boolean",
            "default": false,
            "description": "Requires daily_site_rolls=true."
          },
          "muster_default_shift_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Display-only active shift."
          },
          "muster_ess_restricted": {
            "type": "boolean",
            "default": false
          },
          "muster_override_employee_ids": {
            "type": "array",
            "maxItems": 5,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "muster_self_approval": {
            "type": "boolean",
            "default": false,
            "description": "#1908 — the submitter of a muster roll may also approve/reject it (four-eyes lifted for this workspace; audited as self_approval)."
          },
          "updated_at": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "version": {
            "type": "integer"
          }
        }
      },
      "HolidayScopeType": {
        "type": "string",
        "enum": [
          "ALL",
          "TEAM",
          "DEPARTMENT",
          "DESIGNATION",
          "ROLE",
          "EMPLOYEE"
        ]
      },
      "ScopedHoliday": {
        "description": "attend.scoped_holidays — a holiday declared for a scope on one date from the month grid (#1879, migration 0272).",
        "type": "object",
        "required": [
          "id",
          "work_date",
          "name",
          "salaried_paid",
          "scope_type",
          "scope_ids",
          "employee_count",
          "cancelled_at",
          "version"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "work_date": {
            "type": "string",
            "format": "date"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "salaried_paid": {
            "type": "boolean",
            "description": "true → HOLIDAY is payable for salaried staff; false → it counts as one LOP day (no leave used). Daily-wage workers are paid for worked days only either way."
          },
          "scope_type": {
            "$ref": "#/components/schemas/HolidayScopeType"
          },
          "scope_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "work.teams / org.departments / org.designations / admin.roles / people.employees ids; empty for ALL."
          },
          "employee_count": {
            "type": "integer",
            "description": "People the scope resolved to on the date (frozen at declare time)."
          },
          "cancelled_at": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "cancelled_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "updated_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "version": {
            "type": "integer"
          }
        }
      },
      "MusterWorkerHit": {
        "type": "object",
        "required": [
          "employee_id",
          "full_name",
          "allocation"
        ],
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "employee_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "home_work_location_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "allocation": {
            "description": "The live roll already holding the worker on `work_date`; `null` when none or no date given.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "muster_roll_id",
                  "muster_no",
                  "work_location_id",
                  "status",
                  "movable"
                ],
                "properties": {
                  "muster_roll_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "muster_no": {
                    "type": "string"
                  },
                  "work_location_id": {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  "site_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "status": {
                    "$ref": "#/components/schemas/MusterRollStatus"
                  },
                  "movable": {
                    "type": "boolean"
                  },
                  "reason": {
                    "description": "Why `movable` is false; `null` when the worker can be moved here.",
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "ON_SUBMITTED_ROLL",
                      "ON_APPROVED_ROLL",
                      "ALREADY_MARKED",
                      null
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "MusterAllocationBoard": {
        "type": "object",
        "required": [
          "work_date",
          "daily_site_rolls",
          "sites",
          "unallocated",
          "totals"
        ],
        "properties": {
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "daily_site_rolls": {
            "type": "boolean"
          },
          "sites": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "geomapped": {
                  "type": "boolean"
                },
                "work_location_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "location_code": {
                  "type": "string"
                },
                "name": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                },
                "timezone": {
                  "type": "string",
                  "description": "IANA zone for the site-local self-punch time."
                },
                "worker_count": {
                  "type": "integer"
                },
                "rolls": {
                  "type": "array",
                  "description": "One roll per site in daily-site-roll mode; several per-supervisor rolls otherwise.",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "$ref": "#/components/schemas/UuidRef"
                      },
                      "muster_no": {
                        "type": "string"
                      },
                      "status": {
                        "$ref": "#/components/schemas/MusterRollStatus"
                      },
                      "version": {
                        "type": "integer"
                      },
                      "supervisor_employee_id": {
                        "$ref": "#/components/schemas/UuidRef"
                      },
                      "last_edited_by": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Display name of whoever last changed the roll."
                      },
                      "last_edited_at": {
                        "oneOf": [
                          {
                            "$ref": "#/components/schemas/TimestampRef"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      },
                      "worker_count": {
                        "type": "integer"
                      },
                      "pending_submission_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "V2 only; marked entries on a DRAFT roll once its configured site-local shift end has passed."
                      },
                      "workers": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "employee_id": {
                              "$ref": "#/components/schemas/UuidRef"
                            },
                            "employee_code": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "full_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "day_status": {
                              "$ref": "#/components/schemas/AttendanceStatus"
                            },
                            "capture_channel": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "SITE",
                                "TEAM",
                                "OVERRIDE",
                                null
                              ],
                              "description": "Muster v2 (#1897) — the same safe evidence the roll detail projects."
                            },
                            "recorded_via_team_id": {
                              "oneOf": [
                                {
                                  "$ref": "#/components/schemas/UuidRef"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "team_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "team_lead_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "face": {
                              "oneOf": [
                                {
                                  "$ref": "#/components/schemas/MusterFaceVerdict"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "marker_fence_result": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "INSIDE",
                                "OUTSIDE",
                                "UNKNOWN",
                                "SITE_NOT_GEOMAPPED",
                                null
                              ]
                            },
                            "face_enrolment": {
                              "type": "string",
                              "enum": [
                                "ACTIVE",
                                "PENDING",
                                "NONE"
                              ]
                            },
                            "override_reason": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "self_punch": {
                              "oneOf": [
                                {
                                  "$ref": "#/components/schemas/MusterSelfPunchSummary"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "unallocated": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "employee_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "employee_code": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "full_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "home_work_location_id": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/UuidRef"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "allocated": {
                "type": "integer"
              },
              "unallocated": {
                "type": "integer"
              }
            }
          }
        }
      },
      "MusterWorkerHistory": {
        "type": "object",
        "required": [
          "employee_id",
          "from",
          "to",
          "days"
        ],
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "from": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "to": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "work_date": {
                  "$ref": "#/components/schemas/DateOnlyRef"
                },
                "work_location_id": {
                  "$ref": "#/components/schemas/UuidRef"
                },
                "site_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "basis": {
                  "type": "string",
                  "enum": [
                    "MUSTER",
                    "LEDGER"
                  ]
                },
                "muster_roll_id": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/UuidRef"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "muster_no": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "roll_status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "day_status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "self_punch": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/MusterSelfPunchSummary"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "MusterMarkerLocation": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "latitude",
          "longitude",
          "accuracy_m",
          "captured_at"
        ],
        "properties": {
          "latitude": {
            "type": [
              "string",
              "number"
            ],
            "description": "Decimal degrees in [-90,90], up to 6 decimals."
          },
          "longitude": {
            "type": [
              "string",
              "number"
            ],
            "description": "Decimal degrees in [-180,180], up to 6 decimals."
          },
          "accuracy_m": {
            "type": [
              "string",
              "number"
            ],
            "description": "Non-negative decimal metres, maximum 9999999."
          },
          "captured_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MusterFaceVerdict": {
        "type": "object",
        "required": [
          "result",
          "matched"
        ],
        "properties": {
          "result": {
            "type": "string",
            "enum": [
              "MATCH",
              "NO_MATCH",
              "SPOOF_SUSPECTED",
              "NO_FACE",
              "NOT_ENROLLED",
              "ENGINE_ERROR",
              "ENROLLED_NOW"
            ]
          },
          "matched": {
            "type": [
              "boolean",
              "null"
            ]
          }
        }
      },
      "MusterFaceCheck": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MusterFaceVerdict"
          },
          {
            "type": "object",
            "required": [
              "id",
              "face_enrolled",
              "face_enrolment",
              "expires_at"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "face_enrolled": {
                "type": "boolean"
              },
              "face_enrolment": {
                "type": "string",
                "enum": [
                  "ACTIVE",
                  "PENDING",
                  "NONE"
                ],
                "description": "A NOT_ENROLLED check against a PENDING registration answers 409 `muster-face-enrolment-pending` instead."
              },
              "expires_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "MusterFaceCheckRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "employee_id",
          "work_date",
          "storage_key"
        ],
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "storage_key": {
            "type": "string",
            "description": "Key returned by files/upload-url in this tenant uploads prefix."
          },
          "marker_location": {
            "$ref": "#/components/schemas/MusterMarkerLocation"
          }
        }
      },
      "MusterSupervisorEnrollmentRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "work_date",
          "storage_key"
        ],
        "properties": {
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "storage_key": {
            "type": "string"
          },
          "marker_location": {
            "$ref": "#/components/schemas/MusterMarkerLocation"
          }
        }
      },
      "MusterSupervisorEnrollment": {
        "type": "object",
        "required": [
          "enrollment",
          "face_check_id"
        ],
        "properties": {
          "enrollment": {
            "type": "object",
            "required": [
              "id",
              "status",
              "capture_channel"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "status": {
                "const": "PENDING"
              },
              "capture_channel": {
                "const": "SUPERVISOR"
              }
            }
          },
          "face_check_id": {
            "type": "null",
            "description": "Always null since 2026-09-28 — nothing is marked on an unapproved registration."
          },
          "detail": {
            "type": "string",
            "description": "Sentence to show the marker (waiting for HR approval)."
          }
        }
      },
      "MusterEntryInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "employee_id",
          "day_status"
        ],
        "properties": {
          "face_check_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "override_reason": {
            "type": "string",
            "minLength": 5,
            "maxLength": 500
          },
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "day_status": {
            "$ref": "#/components/schemas/AttendanceStatus"
          },
          "in_time": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "out_time": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "unit_code": {
            "type": "string"
          },
          "units_done": {
            "$ref": "#/components/schemas/DecimalCount"
          },
          "remarks": {
            "type": "string",
            "maxLength": 2000
          }
        }
      },
      "MusterEntryRecordRequest": {
        "type": "object",
        "description": "Upserts entries on a `DRAFT` roll, keyed by `employee_id`. `recorded_by` is not accepted (`422`) — it is stamped from the authenticated actor.",
        "additionalProperties": false,
        "required": [
          "entries"
        ],
        "properties": {
          "marker_location": {
            "$ref": "#/components/schemas/MusterMarkerLocation"
          },
          "entries": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/MusterEntryInput"
            }
          }
        }
      },
      "MusterRollSyncItem": {
        "type": "object",
        "description": "One offline-captured roll inside a sync batch.",
        "additionalProperties": false,
        "required": [
          "client_roll_id",
          "work_location_id",
          "work_date",
          "entries"
        ],
        "properties": {
          "marker_location": {
            "$ref": "#/components/schemas/MusterMarkerLocation"
          },
          "client_roll_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "Generated at capture time on the handset. The per-item idempotency key — a replay upserts the same roll and never double-counts. A UUID, matching the `attend.muster_rolls.client_roll_id` column (#582)."
          },
          "work_location_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "work_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "supervisor_employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "submit": {
            "type": "boolean",
            "default": false,
            "description": "When true the roll arrives `SUBMITTED` and raises its `MUSTER` inbox request. Approval is never implied by a sync."
          },
          "captured_at": {
            "$ref": "#/components/schemas/TimestampRef",
            "description": "When the crew was actually marked, which is not when the flush arrived. `attend.muster_rolls` has no column for it (db 05 §1.5), so #584 preserves it on the **audit row** for the sync rather than dropping it — the roll's `created_at` only records when the signal came back.\n"
          },
          "entries": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/MusterEntryInput"
            }
          }
        }
      },
      "MusterRollSyncRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "rolls"
        ],
        "properties": {
          "marker_location": {
            "$ref": "#/components/schemas/MusterMarkerLocation"
          },
          "rolls": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/MusterRollSyncItem"
            }
          }
        }
      },
      "MusterRollSyncResult": {
        "type": "object",
        "description": "Per-item outcome — a batch may legitimately mix clean and conflicting results.",
        "required": [
          "client_roll_id",
          "outcome"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "code": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "client_roll_id": {
            "type": "string"
          },
          "muster_roll_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "outcome": {
            "type": "string",
            "enum": [
              "CREATED",
              "UPDATED",
              "REPLAYED",
              "CONFLICT",
              "REJECTED"
            ],
            "description": "`CREATED` — this capture id opened a new roll · `UPDATED` — an existing roll gained or corrected at least one entry, or was submitted · **`REPLAYED`** — this `client_roll_id` was already reconciled and every entry in the item already matched the stored row, so nothing changed and nothing double-counted (#584, as-built: the comparison is field-by-field, so a replay produces no write at all — no re-stamped `recorded_by`, no version bump) · `CONFLICT` — the world moved (the roll is already routed for approval, the site-day is taken, or the capture id names a different site-day) · `REJECTED` — the item itself is refused on the merits (a terminated employee, an unpriceable `unit_code`, a supervisor outside the caller's team scope). `CONFLICT` and `REJECTED` items are rolled back alone.\n"
          },
          "entries_written": {
            "type": "integer",
            "minimum": 0
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "The refusal, in the server's own words. A field-level refusal keeps its **JSON pointer** (`/rolls/2/entries/7/unit_code — …`, #584 as-built) so a device queue can attribute the rejection to the row that caused it rather than to the roll as a whole.\n"
          }
        }
      },
      "MusterRollSyncResponse": {
        "type": "object",
        "required": [
          "results"
        ],
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MusterRollSyncResult"
            }
          }
        }
      },
      "BeatPlanStop": {
        "type": "object",
        "description": "One ordered stop in `attend.beat_plans.stops` (db 05 §1.4 JSONB shape, unchanged).",
        "properties": {
          "seq": {
            "type": "integer",
            "minimum": 0
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "latitude": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,6})?$",
            "description": "numeric(9,6) latitude."
          },
          "longitude": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,6})?$",
            "description": "numeric(9,6) longitude."
          },
          "planned_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TimestampRef"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "BeatPlan": {
        "description": "attend.beat_plans — a route of stops for a field employee (db 05 §1.4).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "beat_no",
              "employee_id",
              "plan_date",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "beat_no": {
                "$ref": "#/components/schemas/BusinessNoRef",
                "description": "Tenant-unique `(tenant_id, beat_no)`. Read-model field, never a path key."
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef",
                "description": "Assignee — REQUIRED (FSD 04, ATT-F07). `attend.beat_plans.employee_id` is NOT NULL: an unassigned plan would be invisible under the SELF/TEAM ownership policy."
              },
              "plan_date": {
                "$ref": "#/components/schemas/DateOnlyRef"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "$ref": "#/components/schemas/BeatPlanStatus"
              },
              "stops": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BeatPlanStop"
                }
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "BeatPlanCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "beat_no",
          "employee_id",
          "plan_date",
          "stops"
        ],
        "properties": {
          "beat_no": {
            "$ref": "#/components/schemas/BusinessNoRef"
          },
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "plan_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "stops": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/BeatPlanStop"
            }
          }
        }
      },
      "BeatPlanUpdate": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "employee_id": {
            "$ref": "#/components/schemas/UuidRef",
            "description": "Reassigns the plan. Never nullable — the assignee is required (FSD 04, ATT-F07)."
          },
          "plan_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255,
            "description": "Present-key semantics: an explicit `null` clears the name."
          },
          "status": {
            "$ref": "#/components/schemas/BeatPlanStatus"
          },
          "stops": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BeatPlanStop"
            }
          }
        }
      },
      "BeatPlanPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BeatPlan"
                }
              }
            }
          }
        ]
      },
      "FieldVisit": {
        "description": "attend.field_visits — one stop actually visited (db 05 §1.4). Under ADR 0025 the server RECOMPUTES containment from the reported coordinates; the handset's own verdict is an input, not the answer.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "employee_id",
              "client_visit_id",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "client_visit_id": {
                "$ref": "#/components/schemas/UuidRef",
                "description": "Capture-time idempotency key, reconciled exactly like `client_punch_id` (`04 §1.1`). Tenant-unique `(tenant_id, client_visit_id)`."
              },
              "beat_plan_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "stop_seq": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              },
              "employee_id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "purpose": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "check_in_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "check_out_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "check_in_lat": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^-?\\d+(\\.\\d{1,6})?$",
                "description": "numeric(9,6) latitude."
              },
              "check_in_lng": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^-?\\d+(\\.\\d{1,6})?$",
                "description": "numeric(9,6) longitude."
              },
              "status": {
                "$ref": "#/components/schemas/FieldVisitStatus"
              },
              "geofence_attestation_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The server-recomputed containment record. A visit is a claim, and the server re-derives it — field visits are NOT exempt from ADR 0025."
              },
              "notes": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "FieldVisitCheckinRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "client_visit_id",
          "punch_type",
          "geofence"
        ],
        "properties": {
          "client_visit_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "beat_plan_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "stop_seq": {
            "type": "integer",
            "minimum": 0
          },
          "purpose": {
            "type": "string",
            "maxLength": 2000
          },
          "notes": {
            "type": "string",
            "maxLength": 2000
          },
          "punch_type": {
            "$ref": "#/components/schemas/PunchType",
            "description": "`IN` opens the visit (`CHECKED_IN`); `OUT` closes it (`CHECKED_OUT`). One operation, two legs — the same shape the punch path uses."
          },
          "geofence": {
            "$ref": "#/components/schemas/GeofenceCapture"
          }
        }
      },
      "FieldVisitPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FieldVisit"
                }
              }
            }
          }
        ]
      },
      "AttendanceDevice": {
        "description": "attend.devices — the fixed-reader registry. `webhook_secret_ref` is a write-only POINTER into the platform KMS/secret store; the secret material is never serialised onto any response in this contract.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "id",
              "serial_no",
              "status"
            ],
            "properties": {
              "id": {
                "$ref": "#/components/schemas/UuidRef"
              },
              "serial_no": {
                "type": "string",
                "description": "Tenant-unique reader serial."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Operator-facing label (\"Gate 3 — main entrance\")."
              },
              "vendor": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "eSSL / ZKTeco / Matrix / …"
              },
              "model": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "e.g. eSSL / ZKTeco / Matrix model designation."
              },
              "work_location_id": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/UuidRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "webhook_secret_ref": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "OPTIONAL reference into an external KMS/secret store, on the `org.statutory_config.credential_ref` precedent. NEVER the secret, and never dereferenced by this API."
              },
              "status": {
                "$ref": "#/components/schemas/DeviceStatus"
              },
              "last_heartbeat_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "last_event_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "registered_at": {
                "$ref": "#/components/schemas/TimestampRef"
              },
              "retired_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "secret_rotated_at": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/TimestampRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "When the signing secret was last rotated. The secret itself is never serialised on a read."
              }
            }
          },
          {
            "$ref": "#/components/schemas/AuditMetaRef"
          }
        ]
      },
      "AttendanceDeviceMaybeSecret": {
        "description": "The `PATCH` response. `webhook_secret` is present **only** when the request carried `rotate_secret: true`; a plain edit returns a bare `AttendanceDevice`, so an ordinary nameplate correction can never surprise a caller (or a log) with credential material.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/AttendanceDevice"
          },
          {
            "type": "object",
            "properties": {
              "webhook_secret": {
                "type": "string",
                "description": "The NEW HMAC signing secret, shown ONCE. Configure it on the reader before the drain window closes; it is not retrievable afterwards."
              }
            }
          }
        ]
      },
      "AttendanceDeviceWithSecret": {
        "description": "The registration/rotation response — **the only two places the signing secret ever leaves the server**. It is 32 bytes of CSPRNG, base64url, and only its bcrypt hash is stored, so it cannot be re-read, re-sent or recovered: an operator who loses it rotates rather than looks it up. It is deliberately absent from every read model above, and these two responses are never written to the idempotency replay cache (that would put the plaintext at rest in a second table).\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/AttendanceDevice"
          },
          {
            "type": "object",
            "required": [
              "webhook_secret"
            ],
            "properties": {
              "webhook_secret": {
                "type": "string",
                "description": "The HMAC signing secret, shown ONCE. Configure it on the reader now; it is not retrievable afterwards."
              }
            }
          }
        ]
      },
      "AttendanceDeviceRegister": {
        "type": "object",
        "description": "Supplying raw secret material — any `*secret*` / `*password*` / `*key*` / `*token*` / `*credential*` key other than `webhook_secret_ref` — is a `422`: the registry holds a reference and a hash, never material. The signing secret is **issued by the platform**, not chosen by the caller, so `webhook_secret_ref` is optional and names an externally-held credential only.\n",
        "additionalProperties": false,
        "required": [
          "serial_no"
        ],
        "properties": {
          "serial_no": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "name": {
            "type": "string",
            "maxLength": 128
          },
          "vendor": {
            "type": "string",
            "maxLength": 128
          },
          "model": {
            "type": "string",
            "maxLength": 128
          },
          "work_location_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "webhook_secret_ref": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512
          }
        }
      },
      "AttendanceDeviceUpdate": {
        "type": "object",
        "description": "`status` is deliberately absent — health is the sweep job's to own and retirement is its own operation, so a dead reader can never be talked back online by an edit. `rotate_secret` is the one field with a side effect beyond the row, and it lives here rather than on a route of its own because this operation is ALREADY defined as the one that rotates the HMAC secret: the permission catalogue is append-only and `attend.device.update` is the token an operator holds for it.\n",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 128
          },
          "vendor": {
            "type": "string",
            "maxLength": 128
          },
          "model": {
            "type": "string",
            "maxLength": 128
          },
          "work_location_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "webhook_secret_ref": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "Re-points the EXTERNAL secret-store reference. The material itself never moves through this API."
          },
          "rotate_secret": {
            "type": "boolean",
            "description": "Issue a NEW signing secret and return its plaintext once in this response. The PREVIOUS secret keeps verifying until the drain window closes. A `RETIRED` device has no credential to rotate (register a replacement) and answers `409`.\n"
          },
          "drain_minutes": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1440,
            "default": 60,
            "description": "Only meaningful with `rotate_secret: true`. How long the PREVIOUS secret keeps verifying — a reader on a factory floor cannot be re-keyed at the same instant the server rotates, so a zero-overlap rotation means dropped punches, while an unbounded overlap means the old key never actually dies. `0` revokes immediately.\n"
          }
        }
      },
      "AttendanceDevicePage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPageRef"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttendanceDevice"
                }
              }
            }
          }
        ]
      },
      "DeviceEventInput": {
        "type": "object",
        "description": "One raw reader event. **Nothing is interpreted at the edge** — the payload is staged verbatim and normalized on the jobs tier. `event_at` is the DEVICE'S OWN original timestamp and may be in the past: an offline-buffered reader flushes its history on reconnect and each event is attributed to the day it happened, not the day it arrived.\n",
        "additionalProperties": true,
        "required": [
          "device_event_id",
          "event_at"
        ],
        "properties": {
          "device_event_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "The reader's own event id — the per-item idempotency key that makes a replayed flush a no-op."
          },
          "event_at": {
            "$ref": "#/components/schemas/TimestampRef"
          },
          "employee_ref": {
            "type": [
              "string",
              "null"
            ],
            "description": "The reader's enrolment identifier (badge/finger id). Resolved to an employee during normalization, never at the edge."
          },
          "punch_type": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PunchType"
              },
              {
                "type": "null"
              }
            ]
          },
          "raw": {
            "type": "object",
            "additionalProperties": true,
            "description": "The vendor payload verbatim. Retained append-only so a disputed punch has evidence and a normalizer bug is replayable rather than lossy."
          }
        }
      },
      "DeviceEventIngestRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "events"
        ],
        "properties": {
          "events": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/DeviceEventInput"
            }
          },
          "heartbeat_at": {
            "$ref": "#/components/schemas/TimestampRef",
            "description": "Optional liveness stamp carried on the same push; updates `attend.devices.last_heartbeat_at`. A reader that only heartbeats sends an empty-ish batch with this set."
          }
        }
      },
      "DeviceEventIngestResponse": {
        "type": "object",
        "description": "Fast acknowledgement — the reader is never made to wait on normalization.",
        "required": [
          "accepted",
          "duplicates"
        ],
        "properties": {
          "accepted": {
            "type": "integer",
            "minimum": 0
          },
          "duplicates": {
            "type": "integer",
            "minimum": 0,
            "description": "Events whose `device_event_id` was already staged. Counted, not an error — a replayed flush is the normal case."
          },
          "rejected": {
            "type": "integer",
            "minimum": 0,
            "description": "Events staged as `FAILED` because they were structurally invalid (no `device_event_id`, no readable `event_at`). They are counted here and RETAINED with a reason — never dropped and never a 4xx for the whole batch, because one bad row in a thousand-event flush must not cost the other 999 their evidence.\n"
          }
        }
      },
      "LeaveDayPreviewRequest": {
        "type": "object",
        "description": "The dates to price, and whether it is a half day. A DELIBERATE SUBSET of `LeaveApplicationCreate`: the day count depends on the employee's schedule and holiday calendar and on nothing about the leave type, so naming a type here would imply the answer varied by type.\n",
        "additionalProperties": false,
        "required": [
          "start_date",
          "end_date"
        ],
        "properties": {
          "start_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "end_date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "is_half_day": {
            "type": "boolean",
            "description": "A half day is single-day by definition — `end_date` must equal `start_date`, the same refusal the apply path makes."
          }
        }
      },
      "LeaveDayExclusion": {
        "type": "object",
        "description": "One date inside the requested range that costs nothing, and why.",
        "additionalProperties": false,
        "required": [
          "date",
          "reason"
        ],
        "properties": {
          "date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "reason": {
            "type": "string",
            "enum": [
              "WEEK_OFF",
              "HOLIDAY"
            ],
            "description": "`WEEK_OFF` — a rest day under the employee's own `attend.schedules` week-off pattern (or, with no schedule, their legal entity's `work_week`). `HOLIDAY` — an ACTIVE holiday on their legal entity's or work location's calendar, or an optional holiday they have claimed. A date that is both is reported as `HOLIDAY`, the same precedence `attend.materialize_daily_records` labels the attendance day by.\n"
          }
        }
      },
      "LeaveDayPreview": {
        "type": "object",
        "description": "What a range costs, server-computed (#1626). `days` is the very `total_days` the application would carry — same code path, so the preview and the charge cannot differ.\n",
        "additionalProperties": false,
        "required": [
          "days",
          "excluded",
          "week_off_source"
        ],
        "properties": {
          "days": {
            "type": "string",
            "description": "Chargeable days as an exact decimal string (`\"6.00\"`, `\"0.50\"`) — the `DecimalDays` convention `total_days` already uses, never a JSON float (00 §1). `\"0.00\"` means the whole range is non-working, which `POST /leave-applications` refuses.\n"
          },
          "excluded": {
            "type": "array",
            "description": "Ascending. Every date in the range that costs nothing.",
            "items": {
              "$ref": "#/components/schemas/LeaveDayExclusion"
            }
          },
          "week_off_source": {
            "type": "string",
            "enum": [
              "SCHEDULE",
              "SHIFT_ASSIGNMENT",
              "LEGAL_ENTITY_WORK_WEEK",
              "MARKET_DEFAULT"
            ],
            "description": "Which configuration decided the week-off pattern, most specific first — the honest answer to \"why does it think my Friday is a week off\". `MARKET_DEFAULT` means neither a schedule nor an entity work week was configured and the market's own pattern was used.\n"
          }
        }
      },
      "EmployeeHoliday": {
        "type": "object",
        "description": "One holiday on the caller's own resolved calendar. Deliberately flatter than the authoring `HolidayCalendar` shape: the client no longer picks a calendar, filters it by status or flattens several of them — the server has already done all three, which is what made the web and mobile lists disagree before #1637.\n",
        "additionalProperties": false,
        "required": [
          "date",
          "name",
          "type",
          "is_optional",
          "calendar_id",
          "fiscal_year",
          "is_claimed",
          "claim_application_id"
        ],
        "properties": {
          "date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          },
          "name": {
            "description": "The authored localized name, handed on verbatim (`{ en, ar, … }`); a legacy entry may carry a bare string.",
            "anyOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "type": {
            "description": "`PUBLIC` · `RELIGIOUS` · `NATIONAL` · `REGIONAL` · `RESTRICTED`, or null on a legacy entry.",
            "type": [
              "string",
              "null"
            ]
          },
          "is_optional": {
            "type": "boolean",
            "description": "True for a RESTRICTED / optional date — one the employee MAY claim, not one they are given."
          },
          "calendar_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "fiscal_year": {
            "type": "integer"
          },
          "is_claimed": {
            "type": "boolean",
            "description": "This employee has claimed this optional date. Always false on a non-optional date."
          },
          "claim_application_id": {
            "description": "The claim's leave-application id — what `DELETE /optional-holidays/claims/{id}` takes.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/UuidRef"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "OptionalHolidayQuota": {
        "type": "object",
        "description": "The employee's ledger on the tenant's optional-holiday leave type. Day quantities are exact decimal strings, never floats (00 §1).\n",
        "additionalProperties": false,
        "required": [
          "leave_type_id",
          "leave_type_code",
          "leave_type_name",
          "quota_total",
          "quota_remaining"
        ],
        "properties": {
          "leave_type_id": {
            "$ref": "#/components/schemas/UuidRef"
          },
          "leave_type_code": {
            "type": "string"
          },
          "leave_type_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "quota_total": {
            "type": "string",
            "description": "What was granted — 5 on sysmedac."
          },
          "quota_remaining": {
            "type": "string",
            "description": "Still claimable."
          },
          "claimed_count": {
            "type": "integer",
            "description": "Optional dates in the requested fiscal year this employee has claimed. Present on `GET /optional-holidays/me`, omitted on `/home/me`."
          }
        }
      },
      "OptionalHolidayClaimCreate": {
        "type": "object",
        "description": "One civil date and nothing else — the leave type is resolved from the tenant's `OPTIONAL_HOLIDAY` category, never named by the client.",
        "additionalProperties": false,
        "required": [
          "date"
        ],
        "properties": {
          "date": {
            "$ref": "#/components/schemas/DateOnlyRef"
          }
        }
      },
      "Uuid": {
        "type": "string",
        "format": "uuid",
        "description": "UUIDv7 surrogate primary key (db-docs/00 §3). Never the business identifier."
      },
      "DateOnly": {
        "type": "string",
        "format": "date",
        "description": "Calendar-only value (pay-period date, leave date, due date)."
      },
      "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"
          }
        }
      },
      "Timestamp": {
        "type": "string",
        "format": "date-time",
        "description": "timestamptz, serialized UTC ISO-8601. Presentation timezone is a client concern."
      },
      "DecimalHours": {
        "type": "string",
        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
        "description": "numeric(9,2) decimal hours/days as a string (OT hours, leave days). Never a float."
      },
      "Rate": {
        "type": "string",
        "pattern": "^-?\\d+(\\.\\d{1,6})?$",
        "description": "numeric(9,6) fraction as a string, e.g. \"0.120000\" for the 12% EPF rate. Never a float; percentages are stored as fractions."
      },
      "Money": {
        "type": "object",
        "description": "Exact decimal money (db-docs/00 §6). `amount` is a STRING so float never enters the wire.",
        "required": [
          "amount",
          "currency_code"
        ],
        "additionalProperties": false,
        "properties": {
          "amount": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,2})?$",
            "description": "numeric(18,2) as a string, e.g. \"45000.00\"."
          },
          "currency_code": {
            "$ref": "#/components/schemas/CurrencyCode"
          }
        }
      },
      "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"
        }
      },
      "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"
      },
      "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"
      },
      "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."
          }
        }
      },
      "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"
              }
            ]
          }
        }
      },
      "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"
              }
            }
          }
        }
      },
      "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"
          }
        }
      },
      "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"
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "CurrencyCode": {
        "type": "string",
        "enum": [
          "INR",
          "SAR"
        ],
        "description": "ISO-4217. A legal entity operates in a single currency (db-docs/00 §6)."
      },
      "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": {
      "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"
        }
      },
      "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"
        }
      },
      "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"
        }
      },
      "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
        }
      },
      "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"
            }
          }
        }
      },
      "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"
            }
          }
        }
      },
      "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."
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "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"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Per-principal/route rate limit exceeded.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "PreconditionRequired": {
        "description": "If-Match header absent on a mutation of a versioned mutable entity (428).",
        "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"
            }
          }
        }
      }
    },
    "headers": {
      "ETag": {
        "description": "Strong validator of the row `version` (e.g. `\"v7\"`). Use as `If-Match` on the next mutation.",
        "schema": {
          "type": "string"
        }
      },
      "Location": {
        "description": "URI of the created/affected resource.",
        "schema": {
          "type": "string",
          "format": "uri-reference"
        }
      },
      "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"
        }
      }
    }
  }
}