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.
In this deep dive
What tailoring and compiling cost
| Operation | Cost | Why |
|---|---|---|
| Evidence resolution | O(BยทR) | B bullets ร R refs, each a Map lookup โ microseconds at โค10 bullets; the Map is built once, O(E) |
escapeLatex per string | O(len), single pass | One regex replacement over 10 special chars, then a control-char strip โ no re-scanning |
| Warm compile (Tectonic) | ~1โ3 s CPU-bound | XeTeX typesetting; dominated by font loading and paragraph breaking |
| Cold cache warm-up | ~40 MB, minutes | Downloads format + packages; allowed only off the request path (image build, test setup, first local compile) |
| R2 upload | O(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
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.
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.
Prompt-only guardrails
Free.
A request, not a law. The failure is silent, occasional, and lands in front of a recruiter.
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.
Full TeX Live container
Every package ever.
Multi-GB images, slow cold starts, and a vastly larger attack surface to sandbox.
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
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:
CompileResultenumeratestemplate_not_found,shell_escape_blocked,timeout,cache_miss,tectonic_failed,pdf_missing. Notepdf_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_KEYmissing, network down), the action marks the versioncompile_start_failedrather 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
finallyblocks; 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
resumeVersionIdwould both compile and both upload โ to the same deterministic R2 key? No:buildResumePdfKeyincludes 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
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?
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.
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 bundle model, --untrusted, and caching โ everything the hermetic pipeline is built on.
LaTeX injection is injection: the same escape-at-the-boundary discipline as SQL and HTML, generalized.
The storage half of the pipeline; note how per-user key prefixes make tenancy a property of the key space.