{
  "application": {
    "universalIdentifier": "e18866a9-afd0-440a-9855-1e7e9e693a74",
    "displayName": "Contract Approval",
    "description": "AI contract review plus a role-enforced approval workflow for Opportunities. An AI review reads the attached PDF and flags risky clauses (notably customer-vs-supplier payment-terms mismatches); only Manager members can approve or reject.",
    "logo": "public/contract-approval-icon.png",
    "category": "Sales",
    "author": "Excelium Tech",
    "applicationVariables": {
      "ANTHROPIC_API_KEY": {
        "universalIdentifier": "2f9824be-df6f-4065-aa4e-a8313d201ef8",
        "description": "Anthropic API key used by the AI contract-review step to read the attached PDF and flag risky clauses. When unset, the review degrades gracefully and contracts await manual manager review.",
        "isSecret": true
      },
      "LLM_MODEL": {
        "universalIdentifier": "43bc75b0-ef4c-42ec-848e-35297e90364e",
        "description": "Anthropic model id used for the contract review. Defaults to \"claude-3-5-sonnet-latest\" when unset. Must be a multimodal model that accepts PDF document blocks.",
        "isSecret": false
      }
    },
    "galleryImages": [
      "public/cover.generated.png"
    ],
    "defaultRoleUniversalIdentifier": "77fda223-b7a9-4f4a-844e-4a475f2adf0c",
    "aboutDescription": "# Contract Approval\n\nAI contract review plus a **role-enforced** approval workflow for Twenty Opportunities.\n\nA salesperson creates a **Contract** linked to an Opportunity, attaches the contract PDF, and submits it for review. An AI review reads the PDF and flags risky or inconsistent clauses — notably a **customer-vs-supplier payment-terms mismatch** (e.g. the customer pays monthly while the supplier must be paid in a single upfront installment). Managers/partners (**Gerente/Sócio**) then approve or reject, and **only that role can approve**.\n\n## What it installs\n\n- **Contract** (custom object, related to Opportunity): payment terms (customer/supplier), lifecycle `status`, AI review summary, structured AI findings, `aiPaymentMismatch`, approver, submitter, and timestamps. The contract PDF attaches to the Contract via the native Attachment relation.\n- **Renewal tracking** on Contract: `dataInicio` / `dataTermino` (vigência) and `estagioRenovacao` (`ATIVO` → `RENOVACAO_INICIADA` → `RENOVADO` / `ENCERRADO`, default `ATIVO`). See [Renewal tracking](#renewal-tracking).\n- **Approval Decision** (custom object, child of Contract): the append-only audit of who decided what, when, and why (`decidedBy`, `decision`, `comment`, `decidedAt`).\n- **Gerente/Sócio** role: assignable to users; the record-decision function checks this role before accepting an approval. A declarative field-permission on `Contract.status` is shipped as defense-in-depth.\n- **Logic functions**: `ai-review`, `submit-for-review`, and `record-decision` (all route-auth'd), plus a no-op `post-install`.\n- **Front-component actions** on a Contract: **Submit for review**, **Approve**, **Reject**.\n- **Pending approvals** view: contracts with `status = pending_manager_approval` — the manager's work queue.\n\n## How it works\n\n1. **Submit for review** sets `status = pending_ai_review`, records the submitter, then runs the AI review directly (no workflow required).\n2. **AI review** fetches the Contract's most-recent PDF attachment (signed URL), sends it to the Anthropic Messages API as a `document` block with a broad clause-review prompt, writes back `aiReviewSummary` / `aiFindings` / `aiPaymentMismatch`, and advances `status = pending_manager_approval`. Supplier payment terms absent from the contract fall back to the standard — see [Supplier payment default](#supplier-payment-default).\n3. **Approve / Reject** calls the route-auth'd `record-decision` function, which resolves the **invoking user** and verifies they hold the Gerente/Sócio role before writing an Approval Decision (`decidedBy` = the actor) and updating the contract. A non-manager attempt is refused with a clear message and nothing is written.\n\n## Setup\n\n1. Install the app.\n2. In **Settings → Apps**, set `ANTHROPIC_API_KEY` (required for AI review). Optionally set `LLM_MODEL` (defaults to a multimodal Claude model). Without the key, the AI step degrades gracefully: it writes a \"not configured\" summary and the contract awaits manual manager review.\n3. Assign the **Gerente/Sócio** role to the members who may approve.\n\n## Supplier payment default\n\nWhen a contract does not state how the supplier is paid, the review applies the Excelium standard: a **single payment (à vista) 30 days after signature** (`src/constants/payment-terms.ts`).\n\nThe *format* matters as much as the term. Paying the supplier in one shot is exactly what creates cash-flow exposure when we bill the customer in installments — money goes out at once while it comes in over months. That comparison is the point of the whole check.\n\nThe default is applied in two places, deliberately split:\n\n- **In the prompt** — the model is told the standard so it can still return a `match`/`mismatch` verdict instead of `unknown`, plus an explicit rule: customer paying in installments/recurring + supplier paid in a single installment ⇒ `mismatch`, with a `vermelho` finding flagged for manual review. It is also told *not* to invent a clause — when the contract is silent it must return `supplierPaymentTerms: null`.\n- **In code** — `paymentTermsSupplier` is then filled from that null with the default. Manual input is never overwritten, and an extracted clause always wins over the default.\n\nThe stored value carries its own label — `Parcela única (à vista) em 30 dias após a assinatura do contrato (padrão — não explícito no contrato)` — and the AI-review Note states the same. This is the point of the design: a presumption must never be readable as a contractual term. To change the standard, edit `DEFAULT_SUPPLIER_PAYMENT_TERMS_DAYS` / `DEFAULT_SUPPLIER_PAYMENT_TERMS_SHORT`; the prompt, the stored value and the Note all derive from them.\n\n### Mismatch backstop\n\nThe prompt is what actually reads the contract, so it is the primary mechanism. On top of it, `ai-review-contract` runs a narrow guard (`paysInInstallments`, a pt-br text heuristic): when the review returns **`unknown`**, the supplier side is the **applied default** (so it is known to be a single payment), and the customer terms read as installments/recurring, the verdict is escalated to `MISMATCH`.\n\nIt only ever escalates a non-verdict — an explicit `MATCH` or `MISMATCH` from the review is never overridden — and it stands down when supplier terms were filled by hand, since those may themselves be an installment plan. A false positive here costs one extra manager glance; a false negative costs cash.\n\n## Renewal tracking\n\n`dataInicio`, `dataTermino` and `estagioRenovacao` support renewal follow-up: `dataTermino` is the current term's expiry and is what any renewal alert should key off; `estagioRenovacao` is where the contract sits in the renewal cycle. Both are nullable — a contract with no `dataTermino` is invisible to every renewal report, so backfilling existing contracts is a prerequisite, not an optimisation.\n\n**These three fields were first created directly in the production workspace** (via the metadata API, under the workspace's own application) and only afterwards declared here. Their `universalIdentifier`s in `src/constants/universal-identifiers.ts` are therefore **pinned to the live UUIDs** rather than freshly generated — `scripts/generate-universal-identifiers.mjs` must not be allowed to overwrite them.\n\nConsequence for the first deploy that ships these declarations: the live fields belong to a *different* `applicationId` than the Contract object, so they are outside this app's metadata diff. The deploy will try to **create** them rather than adopt them, and creation collides with the existing fields on the `(object, name)` uniqueness constraint. Before that deploy, either confirm the migration adopts fields by `universalIdentifier`, or delete the three workspace-owned fields first and let the app recreate them. Deleting them also invalidates any dashboard widget that references their field-metadata IDs — those widgets must be repointed afterwards.\n\n## Graceful degradation\n\n- **No LLM key** → AI review writes a \"not configured\" summary and leaves the status for manual review.\n- **No downloadable PDF** → the AI review falls back to the structured payment-terms fields.\n\n## Development\n\n```bash\nyarn install\nyarn twenty dev:build\nyarn typecheck\nyarn lint\nyarn test        # unit tests (offline, fixtures/mocks)\n```\n\nRegenerate all universal identifiers:\n\n```bash\nnode scripts/generate-universal-identifiers.mjs\n```\n\n## Notes / out of scope\n\n- Live-instance validation (real PDF → LLM round trip, front-component→route end-user auth, install-time workflow seeding) is deferred; unit tests use mocks.\n- Email notifications require a connected account and are not install-seedable; the pending-approvals view is the notification surface.\n",
    "yarnLockChecksum": "fad70d2ae9ff19b29a97223746874dba",
    "packageJsonChecksum": "3547fbf02faa0d52c9b56ffcde47fc85",
    "requiredServerVersionRange": ">=2.20.0",
    "postInstallLogicFunction": {
      "universalIdentifier": "65b60977-844a-4230-aacc-0dff6e2eb69d",
      "shouldRunOnVersionUpgrade": false,
      "shouldRunSynchronously": true
    }
  },
  "objects": [
    {
      "universalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "nameSingular": "contract",
      "namePlural": "contracts",
      "labelSingular": "Contract",
      "labelPlural": "Contracts",
      "description": "A contract under review for an Opportunity, with its file attached (PDF, .docx, .doc or .rtf) and an AI clause review before manager approval.",
      "icon": "IconFileText",
      "labelIdentifierFieldMetadataUniversalIdentifier": "08a4f050-7c85-463e-9aa4-009d0213ba05",
      "fields": [
        {
          "universalIdentifier": "08a4f050-7c85-463e-9aa4-009d0213ba05",
          "type": "TEXT",
          "label": "Name",
          "description": "Contract title or reference.",
          "icon": "IconFileText",
          "name": "name"
        },
        {
          "universalIdentifier": "df60b392-c2bf-4460-a3ba-9846a7da98f7",
          "type": "TEXT",
          "label": "Payment terms (customer)",
          "description": "How the customer pays us (e.g. monthly over 12 months).",
          "icon": "IconCashBanknote",
          "name": "paymentTermsCustomer"
        },
        {
          "universalIdentifier": "94443e7c-c83e-4542-93f7-63dad2207a75",
          "type": "TEXT",
          "label": "Payment terms (supplier)",
          "description": "How we pay the supplier (e.g. single upfront installment). When neither this field nor the contract states it, the AI review fills in the standard: a single payment 30 days after signature.",
          "icon": "IconCashBanknoteOff",
          "name": "paymentTermsSupplier"
        },
        {
          "universalIdentifier": "128da9a5-ffba-4310-9792-5be2805de9f5",
          "type": "SELECT",
          "label": "Status",
          "description": "Lifecycle of the contract review and approval.",
          "icon": "IconProgressCheck",
          "name": "status",
          "defaultValue": "'DRAFT'",
          "options": [
            {
              "id": "fd147573-5cd0-455d-9c1d-5a1f1f3dd5b3",
              "value": "DRAFT",
              "label": "Draft",
              "position": 0,
              "color": "gray"
            },
            {
              "id": "3dea697a-77f4-49d4-aa44-f5d8662d260b",
              "value": "PENDING_AI_REVIEW",
              "label": "Pending AI review",
              "position": 1,
              "color": "blue"
            },
            {
              "id": "fbc4bab6-17ce-4544-a21d-aeb1d92d83ec",
              "value": "PENDING_MANAGER_APPROVAL",
              "label": "Pending manager approval",
              "position": 2,
              "color": "orange"
            },
            {
              "id": "660dce9e-d054-462a-b50f-0335d54446f1",
              "value": "APPROVED",
              "label": "Approved",
              "position": 3,
              "color": "green"
            },
            {
              "id": "7292e345-7d05-4564-8a56-8378f1359c96",
              "value": "REJECTED",
              "label": "Rejected",
              "position": 4,
              "color": "red"
            }
          ]
        },
        {
          "universalIdentifier": "e1b7f3eb-eadf-4644-92d4-ef44954ef179",
          "type": "TEXT",
          "label": "AI review summary",
          "description": "Human-readable summary produced by the AI review.",
          "icon": "IconSparkles",
          "isNullable": true,
          "name": "aiReviewSummary"
        },
        {
          "universalIdentifier": "2223a01a-25a1-4c2d-a13e-6044bfc3df62",
          "type": "SELECT",
          "label": "AI payment mismatch",
          "description": "Whether customer and supplier payment terms are consistent.",
          "icon": "IconAlertTriangle",
          "name": "aiPaymentMismatch",
          "defaultValue": "'UNKNOWN'",
          "options": [
            {
              "id": "16a115a7-7b44-4073-9715-89203605470b",
              "value": "UNKNOWN",
              "label": "Unknown",
              "position": 0,
              "color": "gray"
            },
            {
              "id": "55247810-5361-4a18-b094-84e3f1191a79",
              "value": "MATCH",
              "label": "Match",
              "position": 1,
              "color": "green"
            },
            {
              "id": "e783ade0-e7e9-42d4-89d9-68944eacdd61",
              "value": "MISMATCH",
              "label": "Mismatch",
              "position": 2,
              "color": "red"
            }
          ]
        },
        {
          "universalIdentifier": "40bb9271-9f33-4e52-b4b6-91cc58a412dc",
          "type": "SELECT",
          "label": "AI risk level",
          "description": "Nível de risco geral apurado pela revisão de IA (Verde/Amarelo/Vermelho).",
          "icon": "IconShieldCheck",
          "name": "aiRiskLevel",
          "isNullable": true,
          "options": [
            {
              "id": "98306cd9-e9cb-43fb-8a35-f2d1c8ce8d51",
              "value": "VERDE",
              "label": "Verde",
              "position": 0,
              "color": "green"
            },
            {
              "id": "4f64ec67-7b70-43d4-812a-1631964eb1b1",
              "value": "AMARELO",
              "label": "Amarelo",
              "position": 1,
              "color": "yellow"
            },
            {
              "id": "029e55fa-61fb-480f-b8f5-a585388da4e9",
              "value": "VERMELHO",
              "label": "Vermelho",
              "position": 2,
              "color": "red"
            }
          ]
        },
        {
          "universalIdentifier": "324d19ae-98c7-4c1c-ae81-258619222ec7",
          "type": "DATE_TIME",
          "label": "Submitted at",
          "description": "When the contract was submitted for review.",
          "icon": "IconClock",
          "isNullable": true,
          "defaultValue": null,
          "name": "submittedAt"
        },
        {
          "universalIdentifier": "e330567f-d443-4d21-ad7a-307576da6e19",
          "type": "DATE_TIME",
          "label": "Decided at",
          "description": "When a manager approved or rejected the contract.",
          "icon": "IconClockCheck",
          "isNullable": true,
          "defaultValue": null,
          "name": "decidedAt"
        },
        {
          "universalIdentifier": "7b1c9d24-3f68-4a05-9e13-c4d2a86f5b70",
          "type": "TEXT",
          "label": "AI payment terms snapshot",
          "description": "Internal: payment terms written by the last AI review, used to tell AI output apart from manual input on a re-review.",
          "icon": "IconHistory",
          "isNullable": true,
          "name": "aiPaymentTermsSnapshot"
        },
        {
          "universalIdentifier": "18dd3809-2832-484a-87b6-c0a74bf70c56",
          "type": "DATE",
          "label": "Data de início",
          "description": "Data de início de vigência do contrato.",
          "icon": "IconCalendarEvent",
          "isNullable": true,
          "defaultValue": null,
          "name": "dataInicio"
        },
        {
          "universalIdentifier": "28eb43b7-fcfb-4259-a894-cef533ba2292",
          "type": "DATE",
          "label": "Data de término",
          "description": "Data de vencimento da vigência atual do contrato — usada para disparar o alerta de renovação.",
          "icon": "IconCalendarDue",
          "isNullable": true,
          "defaultValue": null,
          "name": "dataTermino"
        },
        {
          "universalIdentifier": "83f2704a-bb13-4dd9-9bf2-ec792ba800b3",
          "type": "SELECT",
          "label": "Estágio de renovação",
          "description": "Onde o contrato está no ciclo de renovação.",
          "icon": "IconRefresh",
          "isNullable": true,
          "name": "estagioRenovacao",
          "defaultValue": "'ATIVO'",
          "options": [
            {
              "id": "f3f27751-d3ea-4d1b-b2db-4d36179125f3",
              "value": "ATIVO",
              "label": "Ativo",
              "position": 0,
              "color": "green"
            },
            {
              "id": "7517bf3d-b13e-4fa3-b199-b2280fe30267",
              "value": "RENOVACAO_INICIADA",
              "label": "Renovação iniciada",
              "position": 1,
              "color": "orange"
            },
            {
              "id": "5a7e5049-d115-4bb3-84b0-941b00c85f8d",
              "value": "RENOVADO",
              "label": "Renovado",
              "position": 2,
              "color": "blue"
            },
            {
              "id": "44755541-0569-418d-997c-8fcdb0025d2c",
              "value": "ENCERRADO",
              "label": "Encerrado",
              "position": 3,
              "color": "gray"
            }
          ]
        }
      ]
    },
    {
      "universalIdentifier": "6e6de038-df90-4dc3-a1bd-fb9c15b98308",
      "nameSingular": "approvalDecision",
      "namePlural": "approvalDecisions",
      "labelSingular": "Approval Decision",
      "labelPlural": "Approval Decisions",
      "description": "Append-only audit record of a manager approving or rejecting a Contract.",
      "icon": "IconGavel",
      "labelIdentifierFieldMetadataUniversalIdentifier": "80cd5548-93db-43eb-b3c3-953572e973fe",
      "fields": [
        {
          "universalIdentifier": "80cd5548-93db-43eb-b3c3-953572e973fe",
          "type": "TEXT",
          "label": "Name",
          "description": "Short label for the decision record.",
          "icon": "IconGavel",
          "name": "name"
        },
        {
          "universalIdentifier": "e8769cd6-4e43-43bd-86c9-18ed19d8e3c2",
          "type": "SELECT",
          "label": "Decision",
          "description": "The manager decision.",
          "icon": "IconCheckbox",
          "name": "decision",
          "options": [
            {
              "id": "35ff0df0-202b-4abd-8264-98834c48c502",
              "value": "APPROVE",
              "label": "Approve",
              "position": 0,
              "color": "green"
            },
            {
              "id": "cbb44164-7e65-468f-98ab-788315c77db1",
              "value": "REJECT",
              "label": "Reject",
              "position": 1,
              "color": "red"
            }
          ]
        },
        {
          "universalIdentifier": "e8263f3c-29c2-4fb9-b9e8-43e2a68dbdd2",
          "type": "TEXT",
          "label": "Comment",
          "description": "Optional reviewer comment.",
          "icon": "IconMessage",
          "isNullable": true,
          "name": "comment"
        },
        {
          "universalIdentifier": "becbc35b-dd19-4ca2-bbc7-801a65118abf",
          "type": "DATE_TIME",
          "label": "Decided at",
          "description": "When the decision was recorded.",
          "icon": "IconClockCheck",
          "isNullable": true,
          "defaultValue": null,
          "name": "decidedAt"
        }
      ]
    }
  ],
  "fields": [
    {
      "universalIdentifier": "0a9f9101-7093-4ef8-8087-671964f5b642",
      "objectUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
      "type": "RELATION",
      "name": "contracts",
      "label": "Contracts",
      "description": "Contracts under review for this Opportunity.",
      "icon": "IconFileText",
      "relationTargetObjectMetadataUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "relationTargetFieldMetadataUniversalIdentifier": "8abe646d-41d4-483d-8f22-09859a1bce26",
      "universalSettings": {
        "relationType": "ONE_TO_MANY"
      }
    },
    {
      "universalIdentifier": "310b6c4e-22ab-4af5-bfc0-caf059128bf5",
      "objectUniversalIdentifier": "20202020-3319-4234-a34c-82d5c0e881a6",
      "type": "RELATION",
      "name": "submittedContracts",
      "label": "Submitted contracts",
      "description": "Contracts this member submitted for review.",
      "icon": "IconFileUpload",
      "relationTargetObjectMetadataUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "relationTargetFieldMetadataUniversalIdentifier": "9288433a-6972-4a3f-b7d2-5bbf655ad0c2",
      "universalSettings": {
        "relationType": "ONE_TO_MANY"
      }
    },
    {
      "universalIdentifier": "67718dfb-37a1-46a6-961a-ec5d48a36c5f",
      "objectUniversalIdentifier": "20202020-3319-4234-a34c-82d5c0e881a6",
      "type": "RELATION",
      "name": "approvalDecisions",
      "label": "Approval decisions",
      "description": "Approve/reject decisions this member recorded.",
      "icon": "IconGavel",
      "relationTargetObjectMetadataUniversalIdentifier": "6e6de038-df90-4dc3-a1bd-fb9c15b98308",
      "relationTargetFieldMetadataUniversalIdentifier": "d554d5c1-48d4-4ee8-879d-bb7fe11fe925",
      "universalSettings": {
        "relationType": "ONE_TO_MANY"
      }
    },
    {
      "universalIdentifier": "7dde2294-c596-4234-8aff-54e28af08a1c",
      "objectUniversalIdentifier": "20202020-3319-4234-a34c-82d5c0e881a6",
      "type": "RELATION",
      "name": "approvedContracts",
      "label": "Approved contracts",
      "description": "Contracts this member approved or rejected.",
      "icon": "IconFileCheck",
      "relationTargetObjectMetadataUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "relationTargetFieldMetadataUniversalIdentifier": "cd548383-5560-4b90-a8ac-fb88f6483fe2",
      "universalSettings": {
        "relationType": "ONE_TO_MANY"
      }
    },
    {
      "universalIdentifier": "8abe646d-41d4-483d-8f22-09859a1bce26",
      "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "type": "RELATION",
      "name": "opportunity",
      "label": "Opportunity",
      "description": "The Opportunity this contract belongs to.",
      "icon": "IconTargetArrow",
      "relationTargetObjectMetadataUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
      "relationTargetFieldMetadataUniversalIdentifier": "0a9f9101-7093-4ef8-8087-671964f5b642",
      "universalSettings": {
        "relationType": "MANY_TO_ONE",
        "onDelete": "SET_NULL",
        "joinColumnName": "opportunityId"
      }
    },
    {
      "universalIdentifier": "9288433a-6972-4a3f-b7d2-5bbf655ad0c2",
      "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "type": "RELATION",
      "name": "submittedBy",
      "label": "Submitted by",
      "description": "The member who submitted this contract for review.",
      "icon": "IconUserUp",
      "relationTargetObjectMetadataUniversalIdentifier": "20202020-3319-4234-a34c-82d5c0e881a6",
      "relationTargetFieldMetadataUniversalIdentifier": "310b6c4e-22ab-4af5-bfc0-caf059128bf5",
      "universalSettings": {
        "relationType": "MANY_TO_ONE",
        "onDelete": "SET_NULL",
        "joinColumnName": "submittedById"
      }
    },
    {
      "universalIdentifier": "b949d9af-6b11-4ff3-8a76-7862bf90ceae",
      "objectUniversalIdentifier": "6e6de038-df90-4dc3-a1bd-fb9c15b98308",
      "type": "RELATION",
      "name": "contract",
      "label": "Contract",
      "description": "The contract this decision applies to.",
      "icon": "IconFileText",
      "relationTargetObjectMetadataUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "relationTargetFieldMetadataUniversalIdentifier": "f2754979-61fa-45eb-aefb-aa9ede9a73ef",
      "universalSettings": {
        "relationType": "MANY_TO_ONE",
        "onDelete": "CASCADE",
        "joinColumnName": "contractId"
      }
    },
    {
      "universalIdentifier": "cd548383-5560-4b90-a8ac-fb88f6483fe2",
      "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "type": "RELATION",
      "name": "approver",
      "label": "Approver",
      "description": "The Manager who decided on this contract.",
      "icon": "IconUserCheck",
      "relationTargetObjectMetadataUniversalIdentifier": "20202020-3319-4234-a34c-82d5c0e881a6",
      "relationTargetFieldMetadataUniversalIdentifier": "7dde2294-c596-4234-8aff-54e28af08a1c",
      "universalSettings": {
        "relationType": "MANY_TO_ONE",
        "onDelete": "SET_NULL",
        "joinColumnName": "approverId"
      }
    },
    {
      "universalIdentifier": "d554d5c1-48d4-4ee8-879d-bb7fe11fe925",
      "objectUniversalIdentifier": "6e6de038-df90-4dc3-a1bd-fb9c15b98308",
      "type": "RELATION",
      "name": "decidedBy",
      "label": "Decided by",
      "description": "The Manager who recorded this decision.",
      "icon": "IconUserCheck",
      "relationTargetObjectMetadataUniversalIdentifier": "20202020-3319-4234-a34c-82d5c0e881a6",
      "relationTargetFieldMetadataUniversalIdentifier": "67718dfb-37a1-46a6-961a-ec5d48a36c5f",
      "universalSettings": {
        "relationType": "MANY_TO_ONE",
        "onDelete": "SET_NULL",
        "joinColumnName": "decidedById"
      }
    },
    {
      "universalIdentifier": "f2754979-61fa-45eb-aefb-aa9ede9a73ef",
      "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "type": "RELATION",
      "name": "approvalDecisions",
      "label": "Approval decisions",
      "description": "History of approve/reject decisions for this contract.",
      "icon": "IconGavel",
      "relationTargetObjectMetadataUniversalIdentifier": "6e6de038-df90-4dc3-a1bd-fb9c15b98308",
      "relationTargetFieldMetadataUniversalIdentifier": "b949d9af-6b11-4ff3-8a76-7862bf90ceae",
      "universalSettings": {
        "relationType": "ONE_TO_MANY"
      }
    }
  ],
  "indexes": [],
  "permissionFlags": [],
  "roles": [
    {
      "universalIdentifier": "4f7a44cc-05c0-40c9-b833-ea335199959c",
      "label": "Manager",
      "description": "Managers who may approve or reject contracts. Assign members after install.",
      "canReadAllObjectRecords": false,
      "canUpdateAllObjectRecords": false,
      "canSoftDeleteAllObjectRecords": false,
      "canDestroyAllObjectRecords": false,
      "canUpdateAllSettings": false,
      "canBeAssignedToAgents": false,
      "canBeAssignedToUsers": true,
      "canBeAssignedToApiKeys": false,
      "objectPermissions": [
        {
          "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": true,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "7fa84e9d-5c1f-5bcb-b218-bc2457f0ae06"
        },
        {
          "objectUniversalIdentifier": "6e6de038-df90-4dc3-a1bd-fb9c15b98308",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": true,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "f454fe7f-a4ef-57ac-9b37-ddf3fb6f28a9"
        }
      ],
      "fieldPermissions": [
        {
          "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
          "fieldUniversalIdentifier": "128da9a5-ffba-4310-9792-5be2805de9f5",
          "canReadFieldValue": true,
          "canUpdateFieldValue": true,
          "universalIdentifier": "7a0d0e00-e84d-56cd-b3e4-47f5c5d954e6"
        }
      ],
      "permissionFlagUniversalIdentifiers": [],
      "rowLevelPermissionPredicateGroups": [],
      "rowLevelPermissionPredicates": []
    },
    {
      "universalIdentifier": "77fda223-b7a9-4f4a-844e-4a475f2adf0c",
      "label": "Contract Approval function role",
      "description": "Reads Opportunities/Attachments/Members and writes Contracts and Approval Decisions for the Contract Approval app.",
      "canReadAllObjectRecords": false,
      "canUpdateAllObjectRecords": false,
      "canSoftDeleteAllObjectRecords": false,
      "canDestroyAllObjectRecords": false,
      "canUpdateAllSettings": false,
      "canBeAssignedToAgents": false,
      "canBeAssignedToUsers": false,
      "canBeAssignedToApiKeys": false,
      "objectPermissions": [
        {
          "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": true,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "803672e7-d9f9-5972-b1c5-9495a0be5f72"
        },
        {
          "objectUniversalIdentifier": "6e6de038-df90-4dc3-a1bd-fb9c15b98308",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": true,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "89d7f6cd-e4a8-5c0f-b9fb-bebca0e99cb6"
        },
        {
          "objectUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": false,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "c241939e-f6ee-52fe-aeaa-d102c62a0871"
        },
        {
          "objectUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": false,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "cb5bd57f-a031-53d9-9e44-ef8dec6398c4"
        },
        {
          "objectUniversalIdentifier": "20202020-3319-4234-a34c-82d5c0e881a6",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": false,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "f5e43b75-5f64-5990-b6d3-e70c17739f91"
        },
        {
          "objectUniversalIdentifier": "20202020-0b00-45cd-b6f6-6cd806fc6804",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": true,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "4341bdd6-f70d-5b75-a671-d4465d53e1d1"
        },
        {
          "objectUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": true,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "6a4eb4bc-d254-542a-8699-058c7fc4f01f"
        },
        {
          "objectUniversalIdentifier": "20202020-1ba1-48ba-bc83-ef7e5990ed10",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": true,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "3b9b7e42-0b1b-50c2-a1c3-5ff1b84a66d2"
        },
        {
          "objectUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": true,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "d3ebf59c-6804-50b4-98d6-a5ac9b4d8fad"
        }
      ],
      "fieldPermissions": [],
      "permissionFlagUniversalIdentifiers": [
        "6189e7bd-4051-5752-b6b1-5f31358fbaf1"
      ],
      "rowLevelPermissionPredicateGroups": [],
      "rowLevelPermissionPredicates": []
    }
  ],
  "skills": [],
  "agents": [],
  "connectionProviders": [],
  "logicFunctions": [
    {
      "universalIdentifier": "65b60977-844a-4230-aacc-0dff6e2eb69d",
      "name": "post-install",
      "description": "Post-install hook for the Contract Approval app (no workflow seeding; the app runs via front-component actions).",
      "timeoutSeconds": 30,
      "shouldRunSynchronously": true,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/post-install.function.ts",
      "builtHandlerPath": "src/logic-functions/post-install.function.mjs",
      "builtHandlerChecksum": "02ac4c925155b697fe922f5d3f3e6047"
    },
    {
      "universalIdentifier": "6cf39b95-7065-4158-93b2-d4f954ed69ab",
      "name": "record-decision",
      "description": "Records a manager approve/reject decision on a Contract. Verifies the invoking user holds the Manager role before writing the audit record and updating status.",
      "timeoutSeconds": 60,
      "httpRouteTriggerSettings": {
        "path": "/contract-approval/record-decision",
        "httpMethod": "POST",
        "isAuthRequired": true
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/record-decision.function.ts",
      "builtHandlerPath": "src/logic-functions/record-decision.function.mjs",
      "builtHandlerChecksum": "b815ed624155d7d9edfa56e2e45a2514"
    },
    {
      "universalIdentifier": "7e4b41b4-a567-40d5-ac4f-af230e1fb198",
      "name": "ai-review",
      "description": "Reviews a Contract's attached PDF with an LLM, writes structured findings and a payment-mismatch flag, and advances status to pending manager approval.",
      "timeoutSeconds": 120,
      "databaseEventTriggerSettings": {
        "eventName": "contract.updated",
        "updatedFields": [
          "status"
        ]
      },
      "httpRouteTriggerSettings": {
        "path": "/contract-approval/ai-review",
        "httpMethod": "POST",
        "isAuthRequired": true
      },
      "workflowActionTriggerSettings": {
        "label": "AI review contract",
        "icon": "IconSparkles",
        "inputSchema": [
          {
            "type": "object",
            "properties": {
              "recordId": {
                "type": "record",
                "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
                "label": "Contract"
              }
            }
          }
        ],
        "outputSchema": [
          {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean",
                "label": "Success"
              },
              "recordId": {
                "type": "string",
                "label": "Record Id"
              },
              "status": {
                "type": "string",
                "label": "Status"
              },
              "paymentMismatch": {
                "type": "string",
                "label": "Payment Mismatch"
              },
              "llmConfigured": {
                "type": "boolean",
                "label": "LLM Configured"
              },
              "message": {
                "type": "string",
                "label": "Message"
              }
            }
          }
        ]
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/ai-review.function.ts",
      "builtHandlerPath": "src/logic-functions/ai-review.function.mjs",
      "builtHandlerChecksum": "25242c5594006b4c55666d3866ca4b0a"
    },
    {
      "universalIdentifier": "db02d2d0-85e9-4d97-a5cc-282d9ee0ca5f",
      "name": "submit-for-review",
      "description": "Submits a Contract for review: marks it pending AI review, records the submitter, and runs the AI review directly.",
      "timeoutSeconds": 120,
      "httpRouteTriggerSettings": {
        "path": "/contract-approval/submit-for-review",
        "httpMethod": "POST",
        "isAuthRequired": true
      },
      "workflowActionTriggerSettings": {
        "label": "Submit contract for review",
        "icon": "IconFileUpload",
        "inputSchema": [
          {
            "type": "object",
            "properties": {
              "contractId": {
                "type": "record",
                "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
                "label": "Contract"
              }
            }
          }
        ],
        "outputSchema": [
          {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean",
                "label": "Success"
              },
              "recordId": {
                "type": "string",
                "label": "Record Id"
              }
            }
          }
        ]
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/submit-for-review.function.ts",
      "builtHandlerPath": "src/logic-functions/submit-for-review.function.mjs",
      "builtHandlerChecksum": "926889aa20a0c8a1a6b7fe66a5c8b66e"
    }
  ],
  "frontComponents": [
    {
      "universalIdentifier": "0c35bc5e-7a5c-475d-a2c2-9eff530dc0a0",
      "name": "submit-for-review-effect",
      "description": "Submit contract for AI review effect",
      "isHeadless": true,
      "componentName": "SubmitForReview",
      "sourceComponentPath": "src/front-components/submit-for-review-component-effect.tsx",
      "builtComponentPath": "src/front-components/submit-for-review-component-effect.mjs",
      "builtComponentChecksum": "6b0d19f5444ed51ff033385cd244009260d44ae903b0eaaae8c84f01f0a2169d",
      "usesSdkClient": false
    },
    {
      "universalIdentifier": "146f44e2-164f-4173-8e42-11095c71455a",
      "name": "approve-effect",
      "description": "Approve contract effect",
      "isHeadless": true,
      "componentName": "Approve",
      "sourceComponentPath": "src/front-components/approve-component-effect.tsx",
      "builtComponentPath": "src/front-components/approve-component-effect.mjs",
      "builtComponentChecksum": "58853c26afbdb2f3b617f2eda7fae8a72bbc54ae00143b031b34fe2e27b97ac0",
      "usesSdkClient": false
    },
    {
      "universalIdentifier": "cd900b9d-6fed-40e8-b725-bfddd9fc350a",
      "name": "reject-effect",
      "description": "Reject contract effect",
      "isHeadless": true,
      "componentName": "Reject",
      "sourceComponentPath": "src/front-components/reject-component-effect.tsx",
      "builtComponentPath": "src/front-components/reject-component-effect.mjs",
      "builtComponentChecksum": "4f36e7cf3a439334052d8943bece5245874d9ed806bbcfd420bacb1f4e076f6d",
      "usesSdkClient": false
    }
  ],
  "publicAssets": [
    {
      "filePath": "public/contract-approval-icon.png",
      "fileName": "contract-approval-icon.png",
      "fileType": "png",
      "checksum": "1bd5a40fbe0248e3bb0b0bbace5b619e"
    },
    {
      "filePath": "public/cover.generated.png",
      "fileName": "cover.generated.png",
      "fileType": "png",
      "checksum": "56a06208f93970e451ae25e4a250b17a"
    }
  ],
  "views": [
    {
      "universalIdentifier": "a89136c8-e306-4416-b23f-ae8d499ef25e",
      "name": "Pending approvals",
      "icon": "IconGavel",
      "objectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "type": "TABLE",
      "fields": [
        {
          "universalIdentifier": "91bff7b8-d653-4e55-b46a-e1897dd1cc7b",
          "fieldMetadataUniversalIdentifier": "08a4f050-7c85-463e-9aa4-009d0213ba05",
          "position": 0,
          "isVisible": true
        },
        {
          "universalIdentifier": "deba2d27-b7e2-43c2-9ae6-f373f963b79f",
          "fieldMetadataUniversalIdentifier": "128da9a5-ffba-4310-9792-5be2805de9f5",
          "position": 1,
          "isVisible": true
        },
        {
          "universalIdentifier": "77b79f8d-d365-4068-a88d-96f1a0a6941e",
          "fieldMetadataUniversalIdentifier": "40bb9271-9f33-4e52-b4b6-91cc58a412dc",
          "position": 2,
          "isVisible": true
        },
        {
          "universalIdentifier": "cafed141-82dc-4bed-a039-4f54786f5f97",
          "fieldMetadataUniversalIdentifier": "2223a01a-25a1-4c2d-a13e-6044bfc3df62",
          "position": 3,
          "isVisible": true
        },
        {
          "universalIdentifier": "f1ab3513-d8e6-4fe3-80af-dec9dcf67447",
          "fieldMetadataUniversalIdentifier": "8abe646d-41d4-483d-8f22-09859a1bce26",
          "position": 4,
          "isVisible": true
        },
        {
          "universalIdentifier": "e342b6e0-9699-43d0-a4ae-b4cd139a6e6c",
          "fieldMetadataUniversalIdentifier": "9288433a-6972-4a3f-b7d2-5bbf655ad0c2",
          "position": 5,
          "isVisible": true
        },
        {
          "universalIdentifier": "f91c99ea-0a38-4eed-a5ec-dbe7c3440ca3",
          "fieldMetadataUniversalIdentifier": "324d19ae-98c7-4c1c-ae81-258619222ec7",
          "position": 6,
          "isVisible": true
        },
        {
          "universalIdentifier": "ed138b74-a407-4c29-86bd-d8acd69340ef",
          "fieldMetadataUniversalIdentifier": "cd548383-5560-4b90-a8ac-fb88f6483fe2",
          "position": 7,
          "isVisible": true
        }
      ],
      "filters": [
        {
          "universalIdentifier": "762f5269-1d9b-4ce3-a417-5b26c257f3ad",
          "fieldMetadataUniversalIdentifier": "128da9a5-ffba-4310-9792-5be2805de9f5",
          "operand": "IS",
          "value": [
            "PENDING_MANAGER_APPROVAL"
          ]
        }
      ]
    }
  ],
  "viewFields": [
    {
      "universalIdentifier": "6f2d9f81-2bf7-4030-af1e-0fffe3468793",
      "viewUniversalIdentifier": "20202020-a003-4a03-8a03-0aa0b1ca3001",
      "fieldMetadataUniversalIdentifier": "0a9f9101-7093-4ef8-8087-671964f5b642",
      "viewFieldGroupUniversalIdentifier": "20202020-a003-4a03-8a03-0aa0b1ca3102",
      "position": 10,
      "isVisible": true
    }
  ],
  "navigationMenuItems": [
    {
      "universalIdentifier": "979e246d-fac3-4098-bcad-24f77d065f57",
      "position": 6,
      "type": "OBJECT",
      "targetObjectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591"
    }
  ],
  "pageLayouts": [],
  "pageLayoutTabs": [],
  "commandMenuItems": [
    {
      "universalIdentifier": "4daf5c53-8a72-44ea-8e1a-0ca5b64ca844",
      "availabilityObjectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "frontComponentUniversalIdentifier": "146f44e2-164f-4173-8e42-11095c71455a",
      "label": "Approve contract",
      "availabilityType": "RECORD_SELECTION"
    },
    {
      "universalIdentifier": "af85127b-54be-4812-946f-bca6ce3c9fbd",
      "availabilityObjectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "frontComponentUniversalIdentifier": "0c35bc5e-7a5c-475d-a2c2-9eff530dc0a0",
      "label": "Submit for review",
      "availabilityType": "RECORD_SELECTION"
    },
    {
      "universalIdentifier": "c341b153-c8c2-4226-96b5-4b8c1b2fc84e",
      "availabilityObjectUniversalIdentifier": "45a32a16-21a5-4142-ad6b-7949cacad591",
      "frontComponentUniversalIdentifier": "cd900b9d-6fed-40e8-b725-bfddd9fc350a",
      "label": "Reject contract",
      "availabilityType": "RECORD_SELECTION"
    }
  ]
}
