<x-layouts.docs title="Approvals & Human Tasks" description="Human-in-the-loop review and structured human input for AI workflows." page="approvals">
  <h1 class="text-3xl font-bold tracking-tight text-gray-900">Approvals &amp; Human Tasks</h1>
  <p class="mt-4 text-lg text-gray-600">Put humans in the loop at any point in an AI workflow. Approvals let a person review and authorise an AI decision before it takes effect. Human Tasks present structured forms that collect input from a user and feed the response into downstream workflow nodes.</p>

  {{-- Overview --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="overview">Overview</h2>
  <p class="mt-4 text-gray-600">Both Approvals and Human Tasks are backed by the same <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">ApprovalRequest</code> model. The distinction is in how they are created and completed:</p>
  <ul class="mt-4 list-disc list-inside space-y-2 text-gray-600">
    <li><strong>Approvals</strong> — generated automatically by the platform when an action requires human sign-off. A reviewer approves or rejects the request.</li>
    <li><strong>Human Tasks</strong> — generated by a workflow <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">human_task</code> node. A form schema defines the fields the user must fill in. The submitted data is passed as input to downstream nodes.</li>
  </ul>
  <x-docs.callout type="info" title="Same model, different intent">
    An <code>ApprovalRequest</code> becomes a Human Task when it is linked to a workflow node and carries a <code>form_schema</code>. Standard approvals have no form schema — they only require an approve or reject decision.
  </x-docs.callout>

  {{-- When Approvals are Required --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="when-required">When Approvals are Required</h2>
  <p class="mt-4 text-gray-600">Approvals are triggered in the following scenarios:</p>
  <ul class="mt-4 list-disc list-inside space-y-2 text-gray-600">
    <li><strong>Experiment <code>AwaitingApproval</code> state</strong> — when an experiment transitions to <code>AwaitingApproval</code>, execution is halted until a team member approves the plan. On approval the experiment advances to <code>Approved</code> and then to <code>Executing</code>. On rejection it returns to <code>Planning</code>.</li>
    <li><strong>Outbound delivery</strong> — certain outbound connectors can be configured to require approval before messages are sent, preventing automated sending without human review.</li>
    <li><strong>Workflow <code>human_task</code> nodes</strong> — a node of this type pauses the workflow graph and creates a Human Task for a designated user or role.</li>
    <li><strong>Budget threshold alerts</strong> — when spending approaches plan limits the platform may pause an experiment and request manual review before continuing.</li>
  </ul>

  {{-- Approval Inbox --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="inbox">Approval Inbox</h2>
  <p class="mt-4 text-gray-600">The Approval Inbox is available at <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">/approvals</code>. It lists all pending requests for the current team, ordered by creation time.</p>
  <ul class="mt-4 list-disc list-inside space-y-2 text-gray-600">
    <li>Each request shows the context payload — what the AI was about to do and why approval is needed.</li>
    <li>Inline <strong>Approve</strong> and <strong>Reject</strong> buttons act immediately without a page reload.</li>
    <li>For Human Tasks, an <strong>Open Form</strong> button opens the <code>HumanTaskForm</code> Livewire component in a slide-over panel.</li>
    <li>A status filter lets you review historical decisions (approved, rejected, expired).</li>
  </ul>
  <x-docs.callout type="tip" title="Notification Bell">
    Pending approvals also appear in the notification bell in the top navigation bar. Clicking a notification takes you directly to the relevant request.
  </x-docs.callout>

  {{-- Approval Statuses --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="statuses">Approval Statuses</h2>
  <p class="mt-4 text-gray-600">Every <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">ApprovalRequest</code> moves through the following statuses:</p>
  <div class="mt-6 overflow-hidden rounded-xl border border-gray-200">
    <table class="min-w-full divide-y divide-gray-200 text-sm">
      <thead class="bg-gray-50">
        <tr>
          <th class="px-4 py-3 text-left font-semibold text-gray-700">Status</th>
          <th class="px-4 py-3 text-left font-semibold text-gray-700">Meaning</th>
          <th class="px-4 py-3 text-left font-semibold text-gray-700">Next action</th>
        </tr>
      </thead>
      <tbody class="divide-y divide-gray-200 bg-white">
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">pending</td>
          <td class="px-4 py-3 text-gray-600">Awaiting a human decision.</td>
          <td class="px-4 py-3 text-gray-600">Approve, reject, or wait for expiry.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">approved</td>
          <td class="px-4 py-3 text-gray-600">A team member approved the request. The blocked workflow or experiment resumes automatically.</td>
          <td class="px-4 py-3 text-gray-600">Terminal — no further action.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">rejected</td>
          <td class="px-4 py-3 text-gray-600">A team member rejected the request. Dependent experiments return to their previous state for revision.</td>
          <td class="px-4 py-3 text-gray-600">Terminal — no further action.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">expired</td>
          <td class="px-4 py-3 text-gray-600">No decision was made within the configured window. The platform treats this as a rejection.</td>
          <td class="px-4 py-3 text-gray-600">Re-trigger the source workflow or experiment if needed.</td>
        </tr>
      </tbody>
    </table>
  </div>
  <p class="mt-4 text-gray-600">The <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">approvals:expire-stale</code> scheduled command runs every hour and moves overdue <code>pending</code> requests to <code>expired</code>.</p>

  {{-- Human Tasks --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="human-tasks">Human Tasks</h2>
  <p class="mt-4 text-gray-600">A Human Task is an <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">ApprovalRequest</code> that carries a <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">form_schema</code> JSON Schema. The schema defines the fields that must be completed before the workflow can continue.</p>
  <p class="mt-4 text-gray-600">When a workflow reaches a <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">human_task</code> node:</p>
  <ol class="mt-4 list-decimal list-inside space-y-2 text-gray-600">
    <li>The platform creates an <code>ApprovalRequest</code> linked to the node, with the node's <code>form_schema</code> and the assigned user or role.</li>
    <li>The workflow execution pauses — no downstream nodes run.</li>
    <li>The assigned user sees the task in the Approval Inbox and opens the <code>HumanTaskForm</code> Livewire slide-over.</li>
    <li>On submission the form data is validated against <code>form_schema</code> and stored on the request.</li>
    <li>The workflow resumes, and the next node receives the submitted data as its input payload.</li>
  </ol>

  <h3 class="mt-8 text-lg font-semibold text-gray-900">Example form_schema</h3>
  <x-docs.code language="json">
{
  "type": "object",
  "required": ["decision", "notes"],
  "properties": {
    "decision": {
      "type": "string",
      "title": "Decision",
      "enum": ["approve", "revise", "discard"],
      "description": "Select the outcome for this campaign draft."
    },
    "notes": {
      "type": "string",
      "title": "Notes",
      "description": "Provide feedback for the AI agent.",
      "minLength": 10
    },
    "budget_override": {
      "type": "number",
      "title": "Budget Override (€)",
      "description": "Optional: override the proposed budget ceiling."
    }
  }
}
  </x-docs.code>
  <x-docs.callout type="tip" title="Rendering">
    The <code>HumanTaskForm</code> component renders each property as a form field using its JSON Schema type and title. String enums become dropdowns. Boolean fields become checkboxes.
  </x-docs.callout>

  {{-- SLA Enforcement --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="sla">SLA Enforcement</h2>
  <p class="mt-4 text-gray-600">Human Tasks can carry an SLA deadline. If the task is not completed by that deadline, the platform escalates or expires it automatically.</p>
  <p class="mt-4 text-gray-600">The <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">human-tasks:check-sla</code> scheduled command runs <strong>every 5 minutes</strong> and performs two checks:</p>
  <ul class="mt-4 list-disc list-inside space-y-2 text-gray-600">
    <li><strong>Escalation</strong> — if the task is approaching its deadline (configurable warning window), team admins and the assigned user receive a notification.</li>
    <li><strong>Expiry</strong> — if the deadline has passed, the task is marked <code>expired</code> and the blocked workflow node is failed, allowing the workflow to continue along its error path.</li>
  </ul>
  <x-docs.callout type="warning" title="Expired tasks fail the workflow node">
    When a Human Task expires the associated workflow node transitions to a failed state. Make sure your workflow graph includes an error edge from <code>human_task</code> nodes to a recovery path, or the workflow execution will terminate.
  </x-docs.callout>

  {{-- Webhook Notifications --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="webhooks">Webhook Notifications</h2>
  <p class="mt-4 text-gray-600">You can configure a webhook URL to receive real-time events whenever an approval is created, approved, rejected, or expires. This is useful for integrating approval state into external tools such as Slack, Jira, or a custom dashboard.</p>
  <p class="mt-4 text-gray-600">Events are sent as <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">POST</code> requests with a JSON body:</p>
  <x-docs.code language="json">
{
  "event": "approval.approved",
  "approval_request_id": "019501de-...",
  "status": "approved",
  "decided_by": "user@example.com",
  "decided_at": "2026-03-21T14:05:00Z",
  "context": { ... }
}
  </x-docs.code>
  <p class="mt-4 text-gray-600">Configure the webhook endpoint using the <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">approval_webhook_config</code> MCP tool or via the Team Settings page.</p>

  {{-- MCP Tools --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="mcp-tools">MCP Tools</h2>
  <p class="mt-4 text-gray-600">All approval and human-task operations are available to AI agents via the FleetQ MCP server.</p>
  <div class="mt-6 overflow-hidden rounded-xl border border-gray-200">
    <table class="min-w-full divide-y divide-gray-200 text-sm">
      <thead class="bg-gray-50">
        <tr>
          <th class="px-4 py-3 text-left font-semibold text-gray-700">Tool</th>
          <th class="px-4 py-3 text-left font-semibold text-gray-700">Description</th>
        </tr>
      </thead>
      <tbody class="divide-y divide-gray-200 bg-white">
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">approval_list</td>
          <td class="px-4 py-3 text-gray-600">List approval requests for the team. Filter by <code>status</code>, <code>type</code>, or date range.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">approval_approve</td>
          <td class="px-4 py-3 text-gray-600">Approve a pending request by ID. Accepts an optional <code>comment</code>.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">approval_reject</td>
          <td class="px-4 py-3 text-gray-600">Reject a pending request by ID. Accepts an optional <code>reason</code>.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">approval_complete_human_task</td>
          <td class="px-4 py-3 text-gray-600">Submit the completed form for a Human Task. Accepts a <code>form_data</code> object validated against the task's <code>form_schema</code>.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">approval_webhook_config</td>
          <td class="px-4 py-3 text-gray-600">Get or update the approval webhook URL and signing secret for the team.</td>
        </tr>
      </tbody>
    </table>
  </div>

  {{-- API Endpoints --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="api">API Endpoints</h2>
  <p class="mt-4 text-gray-600">All endpoints are under <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">/api/v1/approvals</code> and require a Sanctum bearer token.</p>
  <div class="mt-6 overflow-hidden rounded-xl border border-gray-200">
    <table class="min-w-full divide-y divide-gray-200 text-sm">
      <thead class="bg-gray-50">
        <tr>
          <th class="px-4 py-3 text-left font-semibold text-gray-700">Method</th>
          <th class="px-4 py-3 text-left font-semibold text-gray-700">Path</th>
          <th class="px-4 py-3 text-left font-semibold text-gray-700">Description</th>
        </tr>
      </thead>
      <tbody class="divide-y divide-gray-200 bg-white">
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">GET</td>
          <td class="px-4 py-3 font-mono text-gray-800">/api/v1/approvals</td>
          <td class="px-4 py-3 text-gray-600">List all approval requests. Supports <code>?status=</code>, <code>?type=</code>, and cursor pagination.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">GET</td>
          <td class="px-4 py-3 font-mono text-gray-800">/api/v1/approvals/{id}</td>
          <td class="px-4 py-3 text-gray-600">Retrieve a single approval request including its context and form schema.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">POST</td>
          <td class="px-4 py-3 font-mono text-gray-800">/api/v1/approvals/{id}/approve</td>
          <td class="px-4 py-3 text-gray-600">Approve a pending request. Body: <code>{ "comment": "..." }</code> (optional).</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">POST</td>
          <td class="px-4 py-3 font-mono text-gray-800">/api/v1/approvals/{id}/reject</td>
          <td class="px-4 py-3 text-gray-600">Reject a pending request. Body: <code>{ "reason": "..." }</code> (optional).</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">POST</td>
          <td class="px-4 py-3 font-mono text-gray-800">/api/v1/approvals/{id}/complete-human-task</td>
          <td class="px-4 py-3 text-gray-600">Submit a completed Human Task form. Body: <code>{ "form_data": { ... } }</code>, validated against <code>form_schema</code>.</td>
        </tr>
        <tr>
          <td class="px-4 py-3 font-mono text-gray-800">POST</td>
          <td class="px-4 py-3 font-mono text-gray-800">/api/v1/approvals/{id}/escalate</td>
          <td class="px-4 py-3 text-gray-600">Manually escalate a pending request, notifying team admins immediately.</td>
        </tr>
      </tbody>
    </table>
  </div>

  {{-- Integration with Workflows --}}
  <h2 class="mt-12 text-2xl font-semibold tracking-tight text-gray-900" id="workflow-integration">Integration with Workflows</h2>
  <p class="mt-4 text-gray-600">Human Task nodes are first-class citizens of the workflow graph. See the <a href="{{ route('docs.show', 'workflows') }}" class="text-primary-600 hover:underline">Workflows documentation</a> for full details on building visual DAGs.</p>
  <p class="mt-4 text-gray-600">Key points when using Human Tasks inside a workflow:</p>
  <ul class="mt-4 list-disc list-inside space-y-2 text-gray-600">
    <li>A <code class="text-sm font-mono bg-gray-100 px-1.5 py-0.5 rounded">human_task</code> node <strong>pauses workflow execution</strong>. No nodes downstream of it will run until the task is completed or expires.</li>
    <li>The <code>form_schema</code> is defined on the workflow node configuration and may reference upstream node outputs using template variables.</li>
    <li>Once the task is completed, the submitted <code>form_data</code> object becomes the output of the node and is available to all downstream nodes via the standard <code>@{{ node_id.field_name }}</code> template syntax.</li>
    <li>Add an error edge from every <code>human_task</code> node to handle the case where the task expires without a response.</li>
  </ul>
  <x-docs.code language="json">
{
  "id": "review-copy",
  "type": "human_task",
  "label": "Review campaign copy",
  "config": {
    "assigned_role": "admin",
    "sla_hours": 24,
    "form_schema": {
      "type": "object",
      "required": ["approved"],
      "properties": {
        "approved": { "type": "boolean", "title": "Approve this copy?" },
        "feedback": { "type": "string", "title": "Feedback for the writer" }
      }
    }
  }
}
  </x-docs.code>
  <x-docs.callout type="tip" title="Chaining Human Tasks">
    You can place multiple <code>human_task</code> nodes in sequence to implement multi-stage review processes — for example, a writer review followed by a legal sign-off.
  </x-docs.callout>
</x-layouts.docs>
