technical field notes · v1
the engine room.
the operator doesn't fix the engines by hand any more. the operator still knows what a healthy one sounds like.
the manual says what the operator does. this page says what the machinery is: the languages, runtimes, package managers, frameworks, build tools and pipelines the fleet actually runs on. every concept is taught off a real incident from a shippers codebase, because the fleet's own wreckage is the best syllabus there is.
00 · why this exists
dispatch heavily, read fluently.
v2 doctrine moved the hands from the keyboard to the gates. that only works if the operator can read what comes back through them. you don't need to write every line. you do need to read every stack trace, every diff at a gate, every standup an agent files. an operator who can't read the telemetry isn't operating, they're hoping.
three reading skills carry almost all of it:
- the error
- the last line of a stack trace is the symptom. the first line you recognise is usually the cause. warnings above the error are frequently the actual answer, as the pnpm case below proves.
- the diff
- at a pre-deploy gate you're not reviewing style, you're asking two questions. what state does this touch, and what happens when it runs twice.
- the config
- most "worked yesterday" failures are version-gated behaviour changes in a tool's defaults, not bugs in your code. reading changelogs is a production skill.
01 · the languages
one language, three dialects.
the fleet is effectively a typescript shop end to end. everything else is a supporting dialect you read more than you write.
typescript
javascript with a type system compiled away before the code runs. the browser and node never see typescript; a compiler (tsc, or faster tools like esbuild and swc) strips the types and emits plain javascript. the types exist purely at build time, which is why they're called a static type system: they catch the mismatch before the code ships, not after a patient record fails to save at 2am.
- the payoff
- a function that expects
clinicId: stringrefuses to compile when handedundefined. in a multi-tenant system like strydeos, where every write must be scoped to the authed clinic, that refusal is a security control, not a nicety. - tsconfig.json
- the compiler's contract.
strict: trueis non-negotiable on new work. thepathsfield is where@/componentsaliases live, and it needs matching config in the bundler or imports resolve in the editor and die in the build. - where it runs
- strydeos (next.js), trademind (expo + supabase edge functions), driiva (react/vite + expo), every agent and mcp server in the software bucket.
sql
trademind sits on supabase, which is managed postgres. every query the app makes is sql underneath, even when a client library hides it. the two ideas worth owning: row level security, where the database itself enforces who can read which rows, and rpc functions, stored procedures the client can call directly. both featured in a real trademind finding, covered in section 03.
bash + the config dialects
bash is the glue of every pipeline: deploy scripts, ci steps, the init and bundle scripts in skills. read it well enough to spot set -e (exit on first error) and to follow a pipe. the config dialects are json (package.json, tsconfig), yaml (github actions workflows, pnpm-workspace.yaml) and toml (some rust-era tools). yaml's significant whitespace breaks more ci runs than logic errors do.
02 · runtimes + package managers
where the code runs, and who fetches its parts.
a runtime executes your javascript outside the browser. a package manager assembles the thousands of third-party parts your code imports. the second one just cost a working session, so it gets the case file.
runtimes
- node
- the default. v8 engine lifted out of chrome. everything on the fleet runs on it today. current lts lines are even-numbered (20, 22).
- bun
- the new-project bias. runtime, package manager, bundler and test runner in one binary, dramatically faster installs. use it where nothing forces node.
- edge runtimes
- cloudflare workers and vercel edge run a stripped v8 with no filesystem and tight limits. supabase edge functions run deno. code written for node does not automatically run there.
what a package manager actually does
- resolve
- reads package.json, walks the full dependency graph. your 30 direct dependencies become 400+ transitive ones.
- lock
- writes the exact resolved version of every package to a lockfile (
pnpm-lock.yaml,package-lock.json). the lockfile is why a build is reproducible. commit it, always. - install
- downloads into node_modules. pnpm hard-links from a global store instead of copying, which is why it's fast and disk-cheap.
- semver
^4.2.1means any 4.x.x. majors are allowed to break you. a major bump in a tool's defaults is exactly what the case below is.- lifecycle scripts
- packages can declare
postinstallscripts that run arbitrary code on your machine at install time. native modules (c/c++ compiled against node, like parcel's file watcher or swc's compiler core) genuinely need them. malware also loves them. hold that thought.
the bundle that refused to build
context: bundling a react + vite app into a single-file html artifact. the bundle script (written against pnpm 10) shells out to parcel. parcel runs a dependency check. the check runs pnpm install. and everything dies with a stack trace that points at pnpm internals, not at the actual problem.
root cause, two layers deep. after the 2024/25 npm supply-chain attacks, pnpm 10 stopped running lifecycle scripts by default: a security feature, deliberately inconvenient. under pnpm 10 that produced a warning and a broken-but-quiet install. pnpm 11 hardened it into a fatal exit 1. so a script that worked last quarter dies this quarter with zero code changes. version-gated default, the classic.
the trap inside the trap. the documented fix used to be an onlyBuiltDependencies allowlist under the pnpm field in package.json. pnpm 11 retired that field entirely and moved config to pnpm-workspace.yaml. applying the old fix produces this, and it's a warning, so it scrolls straight past:
the fix, then the proof. approve the native builds deliberately (know what you're approving: watcher, compiler core, database bindings, all legitimately need to compile), then verify with an exit code, not vibes.
the warning above the error was the answer. errors tell you something broke; warnings tell you why. read up the trace, not just the last line.
security defaults are features. the right move is a deliberate allowlist, not disabling the guard. a postinstall script runs with your full user permissions.
"worked yesterday" almost always means a tool's defaults moved under you. pin versions in scripts you expect to survive quarters, or write the version guard in.
lesson becomes skill. this trace is now a patch on the bundle script, not a memory. nothing gets learned twice.
03 · the fleet's stacks
three engines, three architectures.
a framework is the opinionated shell around your code: routing, rendering, data flow. the fleet runs three deliberately different ones, and each has already produced at least one incident worth teaching from.
strydeos
locked- stack
- next.js on vercel, firebase for auth, firestore and cloud functions. locked means locked: no migration proposals, the leverage is in shipping on it.
- next.js
- react with a server. pages can render on the server (fast first paint, seo) or the client (interactivity), and api routes live in the same repo. vercel is the deployment platform built by the same company, which is why the pairing is frictionless.
- firestore
- a nosql document database. no tables, no joins: collections of json-like documents, queried by index. reads are shallow and fast; the trade-off is that query power is bounded by what you indexed in advance.
the gdpr hard-delete job stopped running. root cause: the query filtered the patients collection on more than one field, and firestore requires an explicit composite index for that. no index, and the query doesn't degrade, it throws. the cron caught nothing, logged nowhere useful, and died on schedule for weeks. a compliance job, dead, silently.
nosql indexes are explicit contracts, declared in firestore.indexes.json and deployed like code. a query that works in the emulator can still throw in prod.
scheduled jobs obey the fleet rule: nothing runs without a heartbeat. a cron that can fail silently is a cron that already has.
trademind
builds · active contract- stack
- expo sdk 54 (react native for ios/android from one typescript codebase), supabase eu west (postgres + auth + storage + edge functions), claude api, elevenlabs convai + twilio for voice, stripe connect for payouts.
- expo
- react native with the toolchain solved: build service, over-the-air updates, testflight distribution. the code is react, but it compiles against native ios/android modules, which is where the next case file comes from.
- supabase
- managed postgres wearing a firebase costume. relational data, real sql, and row level security policies enforced by the database itself.
the app crashed on launch in a testflight build. the pattern: a native module loaded at import time, at the top of a file, before the app had initialised. in javascript, imports execute the moment the file is parsed. the fix was a lazy require: defer loading the module until the code path that needs it actually runs.
mobile shipped means verified on-device. this crash never appears in tests or the simulator config that hid it. tests green is not shipped.
a codebase review flagged rpc functions callable by the anon role, the unauthenticated public key shipped inside the app, that could mint records. postgres functions marked security definer run with the function owner's privileges and bypass row level security unless you re-check auth inside them. p1 finding: anyone with the public url could have called them.
the anon key is public by design. every rpc it can reach is attack surface. default to revoke execute from anon and grant back deliberately, per function.
driiva
deprioritised · post-raise- stack
- react + vite on the web, expo sdk 52 on mobile, firebase underneath. two clients, one backend.
- the incident class
- the memorable one was auth taking 27 seconds on load, since fixed. it belongs to the family production-bug-hunter exists for: auth races, stale listeners, initialisation order. firebase auth is asynchronous and event-driven; render before the auth state event lands and you get flicker, hangs, or double work.
anything event-driven needs a timeout and a fallback path. "it usually arrives quickly" is not an architecture.
shippers-tt · this site
static · netlify- stack
- static html deployed to netlify by manual drag-and-drop, no linked git repo. deliberately boring hosting.
- the one live part
/api/tasksis a netlify serverless function: a single javascript function netlify runs on demand, holding the notion token server-side and pulling the timetable tasks database live. notion edits go live without a redeploy because the data was never baked into the html. that split, static shell + live function, is the cheapest useful architecture on the internet.
04 · build tools
from source to artefact.
nothing you write ships as written. a toolchain transpiles it, resolves it, bundles it, minifies it. knowing the stages is what lets you read a build failure without guessing.
the stages
- transpile
- typescript and modern syntax down to javascript engines understand. tsc is the reference; esbuild and swc do it 10-100x faster and power everything current.
- bundle
- walk every import from an entry file, produce a handful of output files. resolves the module graph, deduplicates, splits code by route.
- tree-shake
- drop exports nothing imports. why importing one function from a big library doesn't ship the whole library.
- minify
- shrink names, strip whitespace and dead branches. the reason prod stack traces are unreadable without source maps.
the tools on the fleet
- vite
- the dev server + build tool for driiva web and most new react work. dev mode serves modules natively for instant hot reload; prod builds bundle through rollup.
- next's compiler
- strydeos builds through next.js, swc underneath, orchestrated by vercel's ci.
- metro
- expo's bundler for trademind and driiva mobile. same job, different constraints: it bundles for a phone, not a browser.
- parcel
- zero-config bundler, used in the artifact pipeline for its party trick: inlining an entire react app, css, fonts and all, into one self-contained 358kb html file. one file, no server, opens anywhere.
05 · shipping + devops
the pipeline is the product.
devops is everything between "it works here" and a real user completing the flow. the fleet's definitions apply: deployed means nothing, shipped means end-to-end without intervention, paid is the only done for builds.
git, with branch safety
the standing rule: no ai-assisted change lands on strydeos or driiva main directly. work happens on a branch (fix/ava-dead-line is a live example), ships through a pull request, and the diff gets read at the gate. branches are the undo button for a fleet: a bad agent run on a branch is a deleted branch, a bad agent run on main is an incident.
ci/cd + preview deploys
continuous integration runs your checks on every push: github actions workflows, defined in yaml, running tests, lint and builds on a clean machine. continuous deployment takes green builds live. vercel and netlify add preview deploys: every pull request gets its own throwaway url, so a gate review looks at a running thing, not a diff and a promise.
the classic failure class is environment mismatch: works locally, dies in ci. real fleet example: a vercel build that gated on the commit author's github org association. the code was fine; the pipeline's rules were the bug. ci config is code, read it like code.
secrets + telemetry
env vars keep secrets out of the repo: platform-side (vercel, netlify, github secrets), never committed, split per environment. two hard rules from a live strydeos audit. one, never log request payloads that can carry keys: an api key surfaced in error logs, and the only correct response to an exposed key is immediate rotation, revoke and reissue, not deletion of the log. two, error monitoring is an instrument: sentry catching exceptions in prod is the fleet dial for code health, and eu region residency matters when the data is clinical.
a secret that touched a log is burned. rotate first, investigate second.
06 · the claude code side
the operator codes through a harness.
this is the side that actually changed. the keyboard work moved to agents in claude code; what the operator writes is the harness around them. a harness is a prompt with a structure that survives contact: role, context, bugs in order, fix, verify. no verify command, no claim.
the pnpm case, as it would be dispatched
the same incident from case file 01, written as the harness you'd paste into claude code. notice the shape: the agent gets the trace, the constraint, and the definition of done as an exit code. it does not get room to guess.
- gates
- the operator steps in at three rings: scope (the harness above), pre-deploy (the diff and the preview url), sign-off. an agent that guesses past a gate gets pulled.
- fan-out
- independent legs never queue. the pattern for multi-screen builds: lock the shared contracts first (types, api layer, tokens, navigation), then one subagent per screen with tests as the merge gate.
- tdd + characterisation
- new behaviour gets a failing test first. big rewrites gate behind a characterisation suite that passes against the current code before a line changes: pin what is, then move it.
- model tiering
- frontier models for decision-heavy legs (ambiguous specs, architecture, security-sensitive, non-reproducible bugs). cheaper tiers for mechanical execution against known patterns. spend follows ambiguity, not volume.
- breadcrumbs
- long-running work leaves commits, a progress file, a standup. quiet equals not finished. before a context clear, the learning gets lifted into a skill or claude.md, which is exactly how case file 01 became a bundle-script patch.
07 · glossary