Approval Workflow
The problem
A document needs review and sign-off from multiple people — legal, finance, management — before it becomes official.
The solution
IntentText has three keywords for the approval chain: approve: → sign: → freeze:.
title: Service Agreement — Acme Corp & GlobalTech Industries
summary: Annual IT support contract
meta: | type: contract | domain: legal | track: true
section: Terms
Provider shall deliver monthly IT support services...
Payment within 30 days of invoice.
section: Approval Chain
// Step 1: Approvals (review, no crypto)
approve: Legal review complete | by: Sarah Chen | role: General Counsel | at: 2026-03-20
approve: Finance approved | by: James Miller | role: CFO | at: 2026-03-21
approve: Management sign-off | by: Lisa Park | role: CTO | at: 2026-03-21
// Step 2: Integrity hash seals (tamper-evident)
sign: Ahmed Al-Rashid | role: CEO | at: 2026-03-22T10:00:00Z | hash: sha256:a1b2c3d4 | spec: 4
sign: Maria Santos | role: COO, GlobalTech | at: 2026-03-22T14:30:00Z | hash: sha256:e5f6a7b8 | spec: 4
// Step 3: Freeze (seal the document)
freeze: | at: 2026-03-22T15:00:00Z | hash: sha256:f9a0b1c2 | spec: 4 | status: locked
track: | by: legal@acme.co
Step by step
1. Enable tracking
meta: | type: contract | track: true
track: true in metadata enables the trust chain. Without it, approve:, sign:, and freeze: still parse, but the document isn't formally tracked.
2. Record approvals
approve: Legal review complete | by: Sarah Chen | role: General Counsel | at: 2026-03-20
Each approve: block records:
- What was approved (the content)
- Who approved it (
by:) - Their role (
role:) - When (
at:)
Add as many approve: blocks as your workflow requires. Common chains:
- Legal → Finance → Management
- Technical → Security → Architecture
- Author → Editor → Publisher
3. Add digital signatures
sign: Ahmed Al-Rashid | role: CEO | at: 2026-03-22T10:00:00Z | hash: sha256:a1b2c3d4 | spec: 4
sign: records a SHA-256 hash of the document content at the time of signing (and binds the signer's identity). This is machine-verifiable: if anyone changes the content, the hash won't match.
4. Freeze the document
freeze: | at: 2026-03-22T15:00:00Z | hash: sha256:f9a0b1c2 | spec: 4 | status: locked
freeze: seals the document. The hash covers all content above the history: boundary — including the signatures and the seal's own metadata — but excludes the sign:/freeze:/amendment: payload, styling (presentation lines/properties), and comments (approve: lines ARE included). After freezing, any edit to the content invalidates the seal; restyling does not.
In-file approval routing: route: and require:
The approve: lines above record what already happened. To declare what must still
happen — the policy — a document carries its own approval route. The route travels inside
the file, and its live state is derived, never stored in a database:
title: Purchase Order PO-9001
meta: | type: purchase-order
summary: Capital equipment purchase — routed for sequential approval.
route: sequential
require: manager
require: finance | when: amount > 100000
require: legal
section: Order
text: Supplier | end: Medad Industrial Co.
metric: Amount | value: 250000 | unit: USD
section: Approvals
approve: Within budget; vendor vetted | by: Sarah Chen | role: manager | at: 2026-03-20
approve: Funds confirmed for Q2 | by: James Miller | role: finance | at: 2026-03-21
// legal approval is still pending
Three canonical contract-tier keywords work together:
route:declares the order —sequential(each approver in turn) orparallel(any order).route:andrequire:are stable canonical keywords (andcertify:records an authority certification).require:declares who must approve, by role. Addwhen:to make a requirement conditional — and like every form/route condition,when:holds exactly one comparison (==,!=,>,<,>=,<=). Herefinanceis only requiredwhen: amount > 100000.approve:fulfils a requirement. An approval matches a requirement by itsrole:.
Derived workflow state
workflowState(source) computes where the route stands right now — you never persist this,
you re-derive it from the document:
import { workflowState } from "@dotit/core";
const state = workflowState(source);
// {
// hasRoute: true,
// order: "sequential",
// required: [ { match: "manager" }, { match: "finance", when: "amount > 100000" }, { match: "legal" } ],
// active: [ … requirements whose `when:` currently holds … ],
// fulfilled: ["manager", "finance"],
// pending: ["legal"],
// next: "legal", // who's up now (null when complete)
// complete: false, // true once every active requirement is fulfilled
// }
Because amount is 250000, the when: amount > 100000 condition holds, so finance is
active and must approve; manager and finance have signed off, leaving next: "legal" and
complete: false. Drop the amount below 100000 and finance falls out of active — the
route would be complete after manager and legal alone. (deriveWorkflowState(doc) is the
same computation if you already have a parsed document; extractRoute(doc) returns just the
declared route.)
This is what drives the approval-route panel in the editor: it reads workflowState to
show the ordered approvers, ticks off the fulfilled ones, and highlights next — no external
workflow engine, the file is the workflow.
Using the CLI
The seal command automates steps 2–4:
dotit seal contract.it --signer "Ahmed Al-Rashid" --role "CEO"
This:
- Computes a content hash
- Adds a
sign:block - Adds a
freeze:block - Writes the updated document
Querying approvals
# Find all approved documents
dotit query ./contracts --type approve --format table
# Who approved what?
dotit query ./contracts --type approve --by "Sarah Chen" --format json
# Find unsigned documents (have approve but no freeze)
dotit ask ./contracts "Which contracts have approvals but no freeze?" --format text
Next steps
- Sealing Contracts — deep dive on the seal command and verification
- Amending Frozen Documents — changing a frozen document formally
- Audit Trail — tracking changes over time
- Fillable Forms — route a completed form for sign-off
- Conformance — gate a document before it enters the approval route