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

# Guardrails and limitations

> Run budget, salary cap, the encrypted flagged counter, the solvency invariant, withdrawals that survive a pause, and every known limitation of Dagon Payroll stated plainly.

> **CONFIDENTIAL & PROPRIETARY © 2026 Inkwell Finance, Inc. All Rights Reserved.**
>
> This document is for informational purposes only and does not constitute legal, tax, or investment advice, nor an offer to sell or a solicitation to buy any security or other financial instrument. Any examples, structures, or flows described here are design intent only and may change.

> [!NOTE]
>
> **Pre-alpha.** Not deployed, not externally audited. The guardrails below
> are enforced by the program and covered by its integration tests. The
> confidentiality they rely on holds only from Encrypt Alpha 1.

Approvers never see a salary. The guardrails let them bound exposure
anyway, with public numbers they set themselves.

## Run budget

`open_pay_run(period, budget)` sets a public total ceiling for the run.
`prepare_pay_run` turns it into an encrypted `remaining` accumulator. Every
payment and bonus in the run is checked against it inside the pay graph
and subtracted from it on success. A payment that would exceed the
remaining budget moves nothing.

`budget` set to `u64::MAX` (the SDK's `UNLIMITED`) means no ceiling.

## Salary cap

`update_policy(auditor, salary_cap, paused)` sets a public per-payment cap.
Each run snapshots the cap when it opens. Inside the pay graph, any single
payment above the cap moves nothing. This bounds the damage from a bad
salary ciphertext.

The cap starts at `u64::MAX` (no cap) when the company is created.

## The flagged counter

Each run carries an encrypted `flagged` counter, starting at 0. The pay
graph increments it whenever a payment fails any check:

```text theme={null}
ok   = treasury >= amount  &  remaining >= amount  &  amount <= cap
paid = ok ? amount : 0
→ treasury - paid,  balance + paid,  remaining - paid,  flagged + !ok
```

A flagged payment moves nothing. The count stays encrypted. The authority
or the auditor reads it through `grant_view` (target: run flagged) and
fixes shortfalls with a make-up run or a bonus.

## Solvency invariant

Every graph conserves value:

```text theme={null}
vault tokens = treasury + Σ employee balances + in-flight withdrawals
```

The full lifecycle test checks this after a complete cycle: fund, hire,
run, withdraw, treasury withdrawal. The FHE graphs are also parity-tested
bit for bit against a plaintext shadow model, on an edge grid of roughly
14k cases plus 5k random cases, for conservation, all-or-nothing, no
overdraft, cap and flag properties.

## Withdrawals survive a pause

`employee_withdraw` deliberately has no `paused` or `active` check. Earned
wages belong to the employee and stay withdrawable when the company is
paused or the employee is deactivated. Pausing stops new runs and new
payments; it does not stop exits.

## Other guards

| Guard | Defends against |
| - | - |
| `last_paid_run < run_id` and `eligible_from_run ≤ run_id` in `process_payment` | Double pay. Hires made mid-run inflating a run. |
| Vault balance must equal the recorded balance before `process_payment` / `process_bonus` | A crank front-running `sync_treasury` so payments flag against a treasury that only looks empty. |
| Optional `operator` (`set_operator`) | Third parties choosing payment order or timing. |
| No treasury withdrawal while a run is open (`begin_treasury_withdrawal`) | Draining the treasury mid-run to make payments flag. |
| Initiator-first decryption, then anyone after 150 slots (`request_withdrawal_decryption`) | Third parties timing decryptions. A silent initiator freezing funds. |
| FHE re-credit on the no-payout path (`complete_withdrawal`) | A stale decryption answer stranding a debit. |
| `retarget_withdrawal` (employee or authority) | A frozen or closed payout account locking funds. |
| Decryption result must come from an Encrypt-owned account and match the stored digest | Forged decryption results. |
| Ciphertext ingress: Encrypt-owned, authorized to the program, `EUint64`, not the treasury or a balance (`add_employee`, `update_salary`, `approve_bonus`) | Foreign or mistyped ciphertexts. |
| Authority in the Company PDA seeds; policy is authority-only (`create_company`) | Squatting a company id with hostile settings. |

## Known limitations

* **Failed payments consume the slot.** A payment the graph flags (treasury
  short, budget exhausted, over cap) still marks the employee paid for that
  run, because success is encrypted. The authority sees the encrypted
  `flagged` count via `grant_view` and fixes it with a make-up run or a
  bonus. The sync guard, the no-withdrawal-mid-run rule and the optional
  operator remove the ways an outsider could trigger this.
* **Without an operator, the crank picks the order.** If the treasury is
  short, whoever cranks decides who is paid first. Set an `operator` if
  that matters.
* **Salary and bonus inputs are trusted from the authority.** The ingress
  check can't tell a fresh `CreateInput` ciphertext from another
  program-authorized handle, such as a run accumulator. This is
  authority-only, so it is not exploitable by outsiders, but the SDK should
  create fresh inputs.
* **Scratch-ciphertext rent.** `sync_treasury`'s amount ciphertext and the
  no-payout `paid` ciphertext are inputs to graphs still pending when the
  instruction ends, so they aren't closed. The loss is roughly 0.001 SOL per
  occurrence, paid by the caller.
* **Ordering.** Consecutive graphs on the same ciphertext rely on Encrypt's
  executor applying them in order, which is its documented behavior.

Two consequences of the code worth knowing when operating a run:

* **Deactivating an employee mid-run.** The run's headcount is a snapshot
  taken at `open_pay_run`, and `process_payment` rejects inactive
  employees. If an employee is deactivated before being paid in an open
  run, the paid count never reaches the headcount, so only the authority
  can close that run.
* **`grant_view` is not multisig-shaped.** It creates a ciphertext, so it
  needs a fresh keypair and an Encrypt CPI, unlike the twelve authority
  instructions. See [Multisig integration](https://docs.inkwell.finance/dagon/payroll/multisig.md).

And one that is not a code limitation but governs everything else:
**Encrypt is pre-alpha** and its devnet does no real encryption. See
[Who sees what](https://docs.inkwell.finance/dagon/payroll/privacy.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.