> ## Documentation Index
> Fetch the complete documentation index at: https://voyant.travel/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Legal

> The legal module owns contracts and policies: templated documents with variable substitution, a signing workflow, and versioned, structured policies with acceptance tracking.

The legal module owns the operator's legal and compliance surface in a single package: contracts and policies. Contracts are signed document instances rendered from reusable templates with variable substitution, versioning, number series, and a signing workflow. Policies are scoped, versioned rule sets (cancellation, payment, terms and conditions, guarantee, commission) with structured rule evaluation, scope-based assignment, and acceptance tracking.

It ships as `@voyant-travel/legal`, a headless module with Drizzle schema, validation, services, and Hono routes for both admin and public surfaces.

```bash theme={null}
pnpm add @voyant-travel/legal
```

## Key concepts

These terms come from the [glossary](/docs/concepts/glossary). Legal owns the documents and rule sets behind them.

* **Contract.** A signed legal document instance bound to a Booking, Quote Version, Program, Product, supplier or channel relationship, or explicit provider reference.
* **Contract Template.** A reusable contract form with variable placeholders, rendered per instance.
* **Signature.** A record of a Contract being signed (signer, method, IP, timestamp).
* **Policy.** A scoped rule set (cancellation, payment, terms and conditions, guarantee, commission); versioned.
* **Policy Version.** An immutable snapshot of a Policy's rules, either `published` or `retired`.
* **Policy Acceptance.** A recorded confirmation that a Person or Booking accepted a specific Policy Version.
* **PII.** Personally Identifiable Information. Reads and writes to PII fields are audit-logged.

<Note>
  Accept, Issue, and Sign are distinct. A customer **accepts** a Policy Version or a Quote Version; you **issue** a Contract to produce the artifact; a signer adds a **Signature**. Accepting required terms is not the same as a fully executed contract.
</Note>

## What it owns

Legal owns two barrels, contracts and policies, each with its own schema, service, validation, and routes.

### Contracts

The contract entities (with TypeID prefixes) are contracts (`cont`), contract templates (`ctpl`) with variable schemas and optional channel scope, contract template versions (`ctpv`, immutable snapshots), contract signatures (`ctsi`), contract number series (`ctns`) with auto-increment, and contract attachments (`ctat`) for rendered PDFs and appendices.

The contract lifecycle is enforced by the contract service:

```mermaid theme={null}
stateDiagram-v2
    [*] --> draft
    draft --> issued
    issued --> sent
    sent --> signed
    signed --> executed
    executed --> [*]
    issued --> expired
    sent --> expired
    draft --> void
    issued --> void
    sent --> void
    signed --> void
    void --> [*]
    expired --> [*]
```

A contract may be voided from any non-void stage. Each transition appends to `stageHistory` and, when an event bus is configured, emits a deliberately minimal domain event (`contract.issued`, `contract.sent`, `contract.signed`, `contract.executed`, or `contract.voided`). Event payloads carry contract ids, relationship ids, stage names, and timestamps only. Rendered bodies, variables, metadata, and signature details stay out of the event payload.

#### Default storefront templates

A contract template can be marked `isDefault: true`. At most one default may exist for a given `(scope, channelId, language)` selector. A default with `channelId: null` is the global fallback for that scope and language; a channel-specific default wins when callers pass a `channelId`. Storefronts resolve the active customer-safe template through the default-template routes, which check requested and fallback languages in order, prefer channel-specific defaults, ignore inactive templates, and only fall back to the newest active matching template when no explicit default exists.

#### Document operations

The contract routes expose stable operations for storefront previews and stored document handling: render-preview routes (by id or by slug) that accept `{ variables }` and return only the rendered text, an `attach-document` route that uploads a multipart file through the configured `documentStorage` and persists a contract attachment, and a `regenerate-pdf` route that replaces the canonical generated document artifact through the configured generator. Public preview routes require the template to be active.

### Policies

The policy entities are policies (`pol`) by kind, policy versions (`plvr`, immutable snapshots with a publish and retire lifecycle), policy rules (`plrl`, structured rules per version such as cancellation windows and percentages), policy assignments (`plas`, scope-based assignment to products, channels, or markets), and policy acceptances (`plac`, acceptance records per booking, order, or person).

A policy is assigned by scope and evaluated structurally, so a **Cancellation Policy** is an ordered rule set defining refund percentages by cutoff window, not free text. A **Policy Acceptance** binds a specific, immutable Policy Version to a Person or Booking, which is what makes consent auditable later.

## Working with it

Standard modules are not registered by hand. `@voyant-travel/legal` is part of the standard product graph, so its routes, services, subscribers, and jobs are resolved into the application at build time. See [Configuration](/docs/platform/fundamentals/configuration).

Render a template preview without persisting anything:

```ts theme={null}
import { contractsService } from "@voyant-travel/legal/contracts/service"

const preview = await contractsService.renderTemplate(db, {
  templateId: "ctpl_…",
  variables: { customerName: "Henderson Family", tripTitle: "Egypt, 14 days" },
})
// preview.text is the rendered body only.
```

Issue a contract and advance it through the lifecycle, emitting minimal events:

```ts theme={null}
const contract = await contractsService.issue(db, {
  templateId: "ctpl_…",
  bookingId: "book_…",
  variables: { totalDue: "€12,400" },
}, { eventBus })

await contractsService.markSent(db, { contractId: contract.id }, { eventBus })
// stageHistory records draft -> issued -> sent; contract.sent is emitted.
```

Publish a policy version and record acceptance against a booking:

```ts theme={null}
import { policiesCoreService } from "@voyant-travel/legal/policies/service"

const version = await policiesCoreService.publishVersion(db, {
  policyId: "pol_…",
  rules: [
    { cutoffDaysBefore: 30, refundPercent: 100 },
    { cutoffDaysBefore: 14, refundPercent: 50 },
    { cutoffDaysBefore: 0, refundPercent: 0 },
  ],
})

await policiesCoreService.recordAcceptance(db, {
  policyVersionId: version.id,
  bookingId: "book_…",
  personId: "pers_…",
})
```

## Links to other modules

* **Bookings and Proposals.** A Contract binds to a Booking, Proposal Version, or Program, and a Policy Acceptance binds to a Booking or Person. Legal stays decoupled and references these through links and ids.
* **Distribution.** Templates and policies can be scoped to a channel, so storefront default-template resolution and policy assignment respect channel context.
* **Finance.** A cancellation Policy's refund windows inform what finance refunds or credits; legal owns the rule set, finance owns the money. See [Finance](/docs/platform/modules/finance).
* **Notifications.** The booking document bundle sends the latest customer-facing contract attachment alongside invoice and proforma renditions. See [Notifications](/docs/platform/modules/notifications).

## React package

`@voyant-travel/legal-react` provides hooks, a client, query keys, reusable UI, and admin surfaces for contracts and policies.

```tsx theme={null}
import { LegalProvider } from "@voyant-travel/legal-react/provider"
```

It exposes the standard family of subpaths (`./hooks`, `./client`, `./query-keys`, `./ui`, `./admin`, and `./components/*`) so an app can drop in the contract editor, signing surfaces, and the policy rule editor without reimplementing the data layer.

## Next steps

<CardGroup cols={2}>
  <Card title="Finance" icon="receipt" href="/docs/platform/modules/finance">
    Invoices, credit notes, and the refund math behind cancellation policies.
  </Card>

  <Card title="Notifications" icon="paper-plane" href="/docs/platform/modules/notifications">
    Deliver contracts and policy documents over multiple channels.
  </Card>

  <Card title="Modules" icon="cubes" href="/docs/platform/modules">
    The full catalog of domain modules and how they link.
  </Card>

  <Card title="Glossary" icon="book-open" href="/docs/concepts/glossary">
    The legal and compliance vocabulary, including PII handling.
  </Card>
</CardGroup>
