Concepts · Templates
Render and sign — end-to-end
The tier_3 agent flow: compose overlays, queue a render, wrap in a signing envelope, dispatch, archive. Returns 202 immediately; terminal status arrives via the WebhookOutbox in strict per-entity sequence order.
Last updated
A tier_3 agent renders + signs a Matter document in three calls. Each
returns 202 Accepted immediately; terminal outcomes arrive via the
WebhookOutbox in strict per-entity sequence order.
POST /v1/templates/{template_id}/render
POST /v1/signing_envelopes
POST /v1/signing_envelopes/{id}/sendStep 1 — render
POST /templates/{template_id}/render does seven things atomically:
-
Authenticate + budget. Resolve token, check it's not revoked, check per-(orgId, mode) render budget (default 200 fresh fingerprints per hour; restricted-classification renders cost 5×). Per-token cap: 50/hour. Over-budget →
429 render_budget_exhaustedwithRetry-After. -
Resolve revision + jurisdiction. Defaults to latest
garevision and the entity'sformation_jurisdiction. The caller may pin either via query parameters. -
Load + compose overlays. Four-layer composition (platform → variant → org → entity) with the three-position grammar guard.
-
Validate parameters. Zod
.strict()againstfields.schema.json; rejected unknown keys with422 additional_properties_not_permitted. -
Classify. Compute
Document.classificationas the max of classifications across present parameters. Per-classification tier check:restrictedrequires tier_3 + PKCE consent nonce. The classification is immutable post-creation; subsequent reclassification of a field in a new revision doesn't retro-bump this Document. -
Compute render_fingerprint. SHA-256 over
(template_revision_id ∥ overlay_hash ∥ canonical_json(parameters) ∥ mode ∥ classification). Forconfidential/restricted, the orgId is folded in — cross-org cache collisions are impossible by construction. -
Queue the job. Inserts
TemplateRenderJob+BackgroundJob+WebhookOutboxrows in one transaction. Cache lookup: if aDocumentwith the samerender_fingerprintalready exists for this org, return its id; queue-collapse is org-scoped.
Returns 202 Accepted with { request_id, render_fingerprint }.
The render worker (driven by the existing
apps/api/app/cron/process-background-jobs/route.ts poller, NOT
Inngest) drains the queue. Concurrency caps via Upstash Redis token
bucket: 16 react-pdf + 4 Playwright per region. Backpressure
hysteresis: queue depth >= 500 → API returns 429 until depth drops
below 250.
When the render completes, the worker writes a Document row,
publishes the rendered PDF to Vercel Blob at
renders/<render_fingerprint>.pdf (restricted lands in
renders-restricted/ with 60-second signed-URL TTLs), and emits
document.rendered_from_template via the WebhookOutbox.
Step 2 — envelope
POST /signing_envelopes wraps the Document in a signing envelope:
POST /v1/signing_envelopes
{
"document_ids": ["doc_..."],
"signers": [
{
"role": "sole_director",
"stakeholder_id": "stk_..."
}
]
}The envelope captures at create time:
- ESRA disclosure packet hash (the version of the disclosure the signer is consenting to)
- Intent text
- Disclosure version pin (so a later disclosure-text rewrite doesn't retroactively change the consent the signer gave)
Per-signer consent records are minted at this step, NOT at signer-
portal time. This matters for round-2 finding F15: deferring consent
capture to portal time would weaken the
legal_basis: esra_consent signatures.
Step 3 — send
POST /signing_envelopes/{id}/send dispatches signing invitations
(email via Resend; SMS where supported). Signers complete via the
SigningSession portal at https://sign.mattermode.com/{session_id}.
The portal:
- Verifies the signer's identity (email OTP or Clerk OAuth).
- Renders the document with the test-mode watermark if applicable.
- Surfaces the Trust card for forks ( "This document is customized by ...").
- Captures the signature with
signing_key_fingerprint,signature_method, IP, user-agent, and timestamp. - Records the ESRA consent against the version captured at envelope create.
Archive on final signature
When the final signer signs, the archive worker:
- Snapshots the MCTF directory of
template_revision_idinto a tar with inlined font binaries (byte-deterministic re-render in 2033). - Fetches RFC 3161 timestamps from DigiCert + FreeTSA in parallel.
- Wraps restricted parameters in per-data-subject AES-256-GCM envelopes (key in KMS).
- Builds the audit-chain segment with KMS Ed25519 signatures plus the public Rekor anchor records that cover the segment.
- Writes the bundle to S3 Object Lock with 7-year Compliance retention.
- Sets
Document.archive_uri. Emitsdocument.archivedvia the WebhookOutbox.
Webhook ordering
Every webhook flows through WebhookOutbox, which guarantees strict
per-(entityId) sequence ordering:
template_render_job.queued
template_render_job.succeeded
document.rendered_from_template
signing_envelope.created
signing_envelope.sent
document.sent
signing_envelope.completed
document.executed
document.archivedThe dispatcher is single-instance and drains in
(entityId, sequenceNumber) order. A stuck retry on one entity does
not block events for unrelated entities; per-entity advisory locks
serialize allocations without cross-entity contention.
See also
- MCTF format — the seven-file directory shape.
- Customization — overlay grammar.
- Test-mode safety — three-layer defense.
- Evidence bundle — what the archived bundle contains and how a court verifies it.