โš™๏ธ Deep Dive

Evidence & the Sandbox โ€” Under the Hood

Escape-order correctness, the compile pipeline's real costs, and a production bug that's still open in TODOS.md.

What tailoring and compiling cost

OperationCostWhy
Evidence resolutionO(BยทR)B bullets ร— R refs, each a Map lookup โ€” microseconds at โ‰ค10 bullets; the Map is built once, O(E)
escapeLatex per stringO(len), single passOne regex replacement over 10 special chars, then a control-char strip โ€” no re-scanning
Warm compile (Tectonic)~1โ€“3 s CPU-boundXeTeX typesetting; dominated by font loading and paragraph breaking
Cold cache warm-up~40 MB, minutesDownloads format + packages; allowed only off the request path (image build, test setup, first local compile)
R2 uploadO(PDF size)~30โ€“100 KB typical; one PUT
End-to-end "generate resume"โ‰ˆ AI (2โ€“10 s) + compile (1โ€“3 s)Which is why compile runs as a background task and the UI shows "compilation has started"

The design consequence: the 15-second compile timeout is only meaningful because cache warm-up is guaranteed not to happen inside it. Look at latex.ts:103 โ€” local dev self-warms in a separate, generously-timed step precisely so the tight timeout stays a true statement about compilation, not about network luck. Separating "expensive setup" from "bounded steady-state" is the same move as connection pooling or JIT warm-up: measure and budget the two phases separately.

Escape-order subtlety worth study (escape.ts): backslash must be handled in the same single pass as the other specials. A naive two-pass version โ€” escape backslashes, then escape braces โ€” would re-escape the braces its own first pass introduced (\{ โ†’ \textbackslash{}\{โ€ฆ). The single regex with a lookup table makes the order problem structurally impossible. Then control characters are stripped so nothing can smuggle line-based directives. Small function, textbook lesson: escaping is a compiler problem, not a string problem.

The decisions: structural truth, and Tectonic over everything else

Chosen
Closed evidence set + mechanical resolution

Fabrication is detectable with certainty โ€” a ref resolves or it doesn't.

Explainable to the user: every bullet shows its receipt.

Coarse: a bullet citing real evidence can still inflate it ("led" vs "helped"). The eval baseline (truthfulness scoring) exists for exactly this residual.

Alternative
Semantic similarity scoring

Catches inflation, not just fabrication.

Thresholds are arbitrary, drift with models, and can't be explained to a user ("rejected: cosine 0.71"). Wrong tool for a hard guarantee.

Alternative
Prompt-only guardrails

Free.

A request, not a law. The failure is silent, occasional, and lands in front of a recruiter.

Chosen
Tectonic, pinned, cache baked into the image

Single static binary; --untrusted and --only-cached flags built for exactly this use.

Pinning + deploy-time proof = the toolchain changes only when you change it.

Bundle covers most but not all of CTAN; exotic packages need image rebuilds.

Alternative
Full TeX Live container

Every package ever.

Multi-GB images, slow cold starts, and a vastly larger attack surface to sandbox.

Alternative
HTML โ†’ PDF (Playwright print)

No TeX at all; trivially sandboxable.

Template fidelity is the product's point โ€” the spec keeps this as a loud, user-chosen fallback, never a silent substitution.

The long-term security posture is worth quoting from escape.ts's header: "users submit structured resume data, never raw .tex." The endgame isn't sandboxing user templates harder โ€” it's making templates first-party code and reducing user input to escaped strings. The sandbox then becomes defense-in-depth around trusted code, which is where you want your sandbox to be.

Where it actually broke โ€” and where it still can

โš ๏ธ
Open production bug: template_not_found: /app/resume_template.tex

Template-mode compile payloads reference a repo-relative path โ€” but the deployed Trigger.dev bundle doesn't ship repo files, so the path doesn't exist in the runtime image (the warm template lives at /opt/wera/warm-template.tex instead). The product path is unaffected because it compiles LaTeX source loaded from the database, not a file path. Documented in TODOS.md, verified 2026-07-13. The class: works-locally path assumptions โ€” any file your code reads must be provably present in the artifact that actually runs, not just in git.

  • Every compile outcome is a value: CompileResult enumerates template_not_found, shell_escape_blocked, timeout, cache_miss, tectonic_failed, pdf_missing. Note pdf_missing: exit code 0 with no PDF on disk is a real Tectonic edge โ€” the code checks the artifact, not just the exit status. Trust outputs, not statuses.
  • Stuck-state prevention: if queuing the background compile fails (TRIGGER_SECRET_KEY missing, network down), the action marks the version compile_start_failed rather than leaving it "queued" forever. Every async workflow needs an answer to "what if the handoff itself fails?"
  • Cleanup discipline: each run gets a UUID-named temp dir, removed in finally blocks; the compile task also cleans after upload. SIGKILL on timeout means no graceful cleanup by the child โ€” the parent owns deletion. Orphaned dirs under OS tmp are the accepted residue of the crash-during-cleanup window.
  • Concurrent compiles of the same version: two triggers for one resumeVersionId would both compile and both upload โ€” to the same deterministic R2 key? No: buildResumePdfKey includes a random UUID, so the loser's PDF becomes an unreferenced orphan object; the winner's key lands in the row. Harmless waste, no corruption โ€” idempotency by last-write-wins on the pointer, not the blob.
  • The blocklist is not the sandbox: the regex catches \write18-family primitives, but obfuscated TeX (catcode tricks) could theoretically evade a static pattern. That's fine because the regex is layer two of four โ€” the design assumes it can be beaten and makes the prize worthless (no secrets, no network).

Make it yours

๐Ÿ›  Exercise 1
Break the escape function (try to)

Write property-based tests for escapeLatex: for any input string, the output must (a) contain no unescaped specials, (b) round-trip visually through a real compile, (c) never grow more than 20ร— the input. Then try adversarial inputs: "\\%", "{" repeated 1,000 times, a string of control characters.

Hint

The single-pass property means escapeLatex(escapeLatex(x)) โ‰  escapeLatex(x) โ€” it's not idempotent, by design. What discipline does that impose on call sites?

๐Ÿ›  Exercise 2
Fix the open bug

Design the fix for template_not_found: either ship resume_template.tex into the bundle via the build extension, or resolve repo-relative template paths to the baked /opt/wera/warm-template.tex. Compare blast radius, and write the deploy verification step that proves your fix (the codebase's own style would demand one).

Hint

The build extension already base64-embeds templates for warming โ€” the smallest fix reuses that path. The verification is a template-mode task run against the deployed version, asserting a PDF lands in R2.

๐Ÿ›  Exercise 3
Design the inflation detector

The evidence gate stops fabrication but not exaggeration. Sketch the eval from SPEC ยง17.5: fixed (profile, job) pairs, scored on truthfulness โ€” zero unresolved refs AND "no scope/impact inflation versus the cited evidence." What can be checked mechanically, what needs an LLM judge, and how do you keep the judge honest?

Hint

Refs are mechanical. Inflation needs a judge with a rubric anchored to the evidence text โ€” and a small human-labeled set to measure the judge against. The spec's warning applies: optimizing keyword coverage alone reproduces the slop you're fighting.

๐Ÿ“–
The Tectonic book โ€” official docs

The bundle model, --untrusted, and caching โ€” everything the hermetic pipeline is built on.

๐Ÿ“–
Injection Prevention Cheat Sheet โ€” OWASP

LaTeX injection is injection: the same escape-at-the-boundary discipline as SQL and HTML, generalized.

๐Ÿ“–
R2 S3-compatible API โ€” Cloudflare docs

The storage half of the pipeline; note how per-user key prefixes make tenancy a property of the key space.