{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Lindsay Edwards",
  "home_page_url": "https://lindsayedwardsit.com/",
  "feed_url": "https://lindsayedwardsit.com/feed.json",
  "description": "Full stack development. AI engineering. Lessons learned.",
  "language": "en-AU",
  "authors": [
    {
      "name": "Lindsay Edwards",
      "url": "https://lindsayedwardsit.com/about/"
    }
  ],
  "items": [
    {
      "id": "https://lindsayedwardsit.com/blog/the-spare-key-everyone-already-has/",
      "url": "https://lindsayedwardsit.com/blog/the-spare-key-everyone-already-has/",
      "title": "The Spare Key Everyone Already Has",
      "date_published": "2026-08-24T00:00:00.000Z",
      "content_text": "An API that cannot find its signing key should not invent one. This week I found one that did, and it fell back to a key already sitting in the repo.",
      "summary": "An API that cannot find its signing key should not invent one. This week I found one that did, and it fell back to a key already sitting in the repo.",
      "tags": [
        "Cybersecurity",
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-report-is-not-the-work/",
      "url": "https://lindsayedwardsit.com/blog/the-report-is-not-the-work/",
      "title": "The Report Is Not The Work",
      "date_published": "2026-08-21T00:00:00.000Z",
      "content_text": "A crew of AI agents fixed about eighty findings in one of my apps this week. The rule that made it work: an agent's report is testimony, not evidence.",
      "summary": "A crew of AI agents fixed about eighty findings in one of my apps this week. The rule that made it work: an agent's report is testimony, not evidence.",
      "tags": [
        "AI",
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-error-handler-that-ate-the-errors/",
      "url": "https://lindsayedwardsit.com/blog/the-error-handler-that-ate-the-errors/",
      "title": "The Error Handler That Ate The Errors",
      "date_published": "2026-08-19T00:00:00.000Z",
      "content_text": "I added a safety net to make failed data loads impossible to miss. Then I watched a page fail to load and say nothing at all.",
      "summary": "I added a safety net to make failed data loads impossible to miss. Then I watched a page fail to load and say nothing at all.",
      "tags": [
        "Engineering",
        "Projects"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-day-that-had-not-happened-yet/",
      "url": "https://lindsayedwardsit.com/blog/the-day-that-had-not-happened-yet/",
      "title": "The Day That Had Not Happened Yet",
      "date_published": "2026-08-17T00:00:00.000Z",
      "content_text": "I wrote a journal entry at breakfast and my app insisted the day had not happened yet. The code asked what day it is, but never asked whose day.",
      "summary": "I wrote a journal entry at breakfast and my app insisted the day had not happened yet. The code asked what day it is, but never asked whose day.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/test-from-the-customers-seat/",
      "url": "https://lindsayedwardsit.com/blog/test-from-the-customers-seat/",
      "title": "Test From The Customer's Seat",
      "date_published": "2026-08-14T00:00:00.000Z",
      "content_text": "I know my own apps too well to test them. This week I planned ContentForge's test pass from the seat that matters, a paying subscriber who starts with nothing.",
      "summary": "I know my own apps too well to test them. This week I planned ContentForge's test pass from the seat that matters, a paying subscriber who starts with nothing.",
      "tags": [
        "Learning",
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/overnight-agent-relay-not-marathon/",
      "url": "https://lindsayedwardsit.com/blog/overnight-agent-relay-not-marathon/",
      "title": "The Overnight Agent Is A Relay, Not A Marathon",
      "date_published": "2026-08-12T00:00:00.000Z",
      "content_text": "I wanted an audit and fix loop that runs unattended all night. The answer was not a smarter agent. It was a dumber one that runs a single phase and exits.",
      "summary": "I wanted an audit and fix loop that runs unattended all night. The answer was not a smarter agent. It was a dumber one that runs a single phase and exits.",
      "tags": [
        "AI",
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/my-own-pipeline-served-me-slop/",
      "url": "https://lindsayedwardsit.com/blog/my-own-pipeline-served-me-slop/",
      "title": "My Own Pipeline Served Me Slop",
      "date_published": "2026-08-10T00:00:00.000Z",
      "content_text": "My tool generated a preview website for a real business and it read as generic slop. The fix was not a better prompt. It was encoding my taste somewhere the machine can reach it.",
      "summary": "My tool generated a preview website for a real business and it read as generic slop. The fix was not a better prompt. It was encoding my taste somewhere the machine can reach it.",
      "tags": [
        "AI",
        "Projects"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/green-tests-red-reality/",
      "url": "https://lindsayedwardsit.com/blog/green-tests-red-reality/",
      "title": "Green Tests, Red Reality",
      "date_published": "2026-08-07T00:00:00.000Z",
      "content_text": "A backend with 748 passing tests still could not boot on the database it was built to ship on. The tests were never wrong. They just never ran the part that broke.",
      "summary": "A backend with 748 passing tests still could not boot on the database it was built to ship on. The tests were never wrong. They just never ran the part that broke.",
      "tags": [
        "AI",
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/give-the-robot-a-spending-cap/",
      "url": "https://lindsayedwardsit.com/blog/give-the-robot-a-spending-cap/",
      "title": "Give The Robot A Spending Cap",
      "date_published": "2026-08-04T00:00:00.000Z",
      "content_text": "I let AI agents write code while I sleep. This week I made sure they cannot quietly run up a bill while they do it.",
      "summary": "I let AI agents write code while I sleep. This week I made sure they cannot quietly run up a bill while they do it.",
      "tags": [
        "AI",
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/get-the-protocol-right-backend-later/",
      "url": "https://lindsayedwardsit.com/blog/get-the-protocol-right-backend-later/",
      "title": "Get the protocol right, the backend can come later",
      "date_published": "2026-07-28T00:00:00.000Z",
      "content_text": "A local-first sync client was designed properly. The server behind it stored everything in memory and lost it all on restart. That was the right order.",
      "summary": "A local-first sync client was designed properly. The server behind it stored everything in memory and lost it all on restart. That was the right order.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/own-the-raf-loop-and-honour-reduced-motion/",
      "url": "https://lindsayedwardsit.com/blog/own-the-raf-loop-and-honour-reduced-motion/",
      "title": "Own the RAF loop and honour reduced motion",
      "date_published": "2026-07-21T00:00:00.000Z",
      "content_text": "A smooth-scroll library ran its own animation loop. Sloppy teardown left a second one running, and scrolling went doubled and janky. Discipline fixed it.",
      "summary": "A smooth-scroll library ran its own animation loop. Sloppy teardown left a second one running, and scrolling went doubled and janky. Discipline fixed it.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-constraint-is-the-aesthetic/",
      "url": "https://lindsayedwardsit.com/blog/the-constraint-is-the-aesthetic/",
      "title": "The constraint is the aesthetic",
      "date_published": "2026-07-14T00:00:00.000Z",
      "content_text": "Targeting cheap phones forced a low-poly, dithered look on my game. I stopped treating that as a compromise once I noticed the style and the budget were the same thing.",
      "summary": "Targeting cheap phones forced a low-poly, dithered look on my game. I stopped treating that as a compromise once I noticed the style and the budget were the same thing.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/deep-links-are-best-effort/",
      "url": "https://lindsayedwardsit.com/blog/deep-links-are-best-effort/",
      "title": "Deep links are best-effort, so plan for the miss",
      "date_published": "2026-07-07T00:00:00.000Z",
      "content_text": "A deep link into another app assumes that app is installed. It might not be. Pair every custom-scheme link with an https fallback and use the check as a probe.",
      "summary": "A deep link into another app assumes that app is installed. It might not be. Pair every custom-scheme link with an https fallback and use the check as a probe.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/defeat-every-bundler-in-the-chain/",
      "url": "https://lindsayedwardsit.com/blog/defeat-every-bundler-in-the-chain/",
      "title": "Defeat every bundler in the chain",
      "date_published": "2026-06-30T00:00:00.000Z",
      "content_text": "An API route needed a module that only exists at runtime on the deploy target. Two separate bundlers tried to resolve it at build and failed. I had to beat both.",
      "summary": "An API route needed a module that only exists at runtime on the deploy target. Two separate bundlers tried to resolve it at build and failed. I had to beat both.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/your-language-is-the-worst-devices-subset/",
      "url": "https://lindsayedwardsit.com/blog/your-language-is-the-worst-devices-subset/",
      "title": "Your language is your worst device's subset",
      "date_published": "2026-06-25T00:00:00.000Z",
      "content_text": "A dither shader ran fine on my desktop and broke on export to phones. The fix was ugly on purpose. The real lesson was which language I was actually writing.",
      "summary": "A dither shader ran fine on my desktop and broke on export to phones. The fix was ugly on purpose. The real lesson was which language I was actually writing.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-auth-tag-is-not-optional/",
      "url": "https://lindsayedwardsit.com/blog/the-auth-tag-is-not-optional/",
      "title": "The auth tag is not optional",
      "date_published": "2026-06-21T00:00:00.000Z",
      "content_text": "Drop the 16-byte authentication tag and you have not built weaker encryption. You have built unauthenticated encryption, and tampering sails straight through.",
      "summary": "Drop the 16-byte authentication tag and you have not built weaker encryption. You have built unauthenticated encryption, and tampering sails straight through.",
      "tags": [
        "Engineering",
        "Cybersecurity"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/convenience-wrappers-assume-the-heavy-case/",
      "url": "https://lindsayedwardsit.com/blog/convenience-wrappers-assume-the-heavy-case/",
      "title": "Convenience wrappers assume the heavy case",
      "date_published": "2026-06-17T00:00:00.000Z",
      "content_text": "The tidy helper for a 3D backdrop was quietly building an expensive lighting pass I did not need, and it was killing my canvas. The primitive API fixed it.",
      "summary": "The tidy helper for a 3D backdrop was quietly building an expensive lighting pass I did not need, and it was killing my canvas. The primitive API fixed it.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/tune-the-timestep-to-the-fastest-thing/",
      "url": "https://lindsayedwardsit.com/blog/tune-the-timestep-to-the-fastest-thing/",
      "title": "Tune the timestep to the fastest thing",
      "date_published": "2026-06-13T00:00:00.000Z",
      "content_text": "My retro racer clipped through walls and misjudged landings. The fix was not smarter collision code. It was running physics twice as often, on purpose.",
      "summary": "My retro racer clipped through walls and misjudged landings. The fix was not smarter collision code. It was running physics twice as often, on purpose.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/one-orm-any-database-is-a-lie/",
      "url": "https://lindsayedwardsit.com/blog/one-orm-any-database-is-a-lie/",
      "title": "One ORM, any database is a lie",
      "date_published": "2026-06-10T00:00:00.000Z",
      "content_text": "IntoAction runs SQLite in dev and Postgres in prod, with Drizzle promising the two are identical. They are not. The leak showed up in a JSON column that came back parsed on one and raw on the other.",
      "summary": "IntoAction runs SQLite in dev and Postgres in prod, with Drizzle promising the two are identical. They are not. The leak showed up in a JSON column that came back parsed on one and raw on the other.",
      "tags": [
        "Engineering",
        "Projects"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/on-device-migrations-are-different/",
      "url": "https://lindsayedwardsit.com/blog/on-device-migrations-are-different/",
      "title": "On-device migrations are a different problem",
      "date_published": "2026-06-09T00:00:00.000Z",
      "content_text": "A server database you migrate once at deploy. On-device SQLite you migrate on a million phones you will never see. That changes what a migration runner should be.",
      "summary": "A server database you migrate once at deploy. On-device SQLite you migrate on a million phones you will never see. That changes what a migration runner should be.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-slider-that-did-nothing/",
      "url": "https://lindsayedwardsit.com/blog/the-slider-that-did-nothing/",
      "title": "The slider that did nothing",
      "date_published": "2026-06-05T00:00:00.000Z",
      "content_text": "A retro shader had two signature effects. I dragged its intensity slider from 0 to 100 and nothing on screen moved. Both effects were secretly dead.",
      "summary": "A retro shader had two signature effects. I dragged its intensity slider from 0 to 100 and nothing on screen moved. Both effects were secretly dead.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/scaffolding-that-compiles-isnt-a-product/",
      "url": "https://lindsayedwardsit.com/blog/scaffolding-that-compiles-isnt-a-product/",
      "title": "Scaffolding that compiles is not a product",
      "date_published": "2026-06-01T00:00:00.000Z",
      "content_text": "A codebase with real crypto, real schema, and real auth still was not shippable. The gap was the last-mile wiring, and it documented itself.",
      "summary": "A codebase with real crypto, real schema, and real auth still was not shippable. The gap was the last-mile wiring, and it documented itself.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/report-the-whole-cycle/",
      "url": "https://lindsayedwardsit.com/blog/report-the-whole-cycle/",
      "title": "Report the whole cycle, not just that there is one",
      "date_published": "2026-05-30T00:00:00.000Z",
      "content_text": "Building workman, my agent orchestration tool, meant detecting cycles in a dependency graph. A good error names the loop; a useless one just says one exists.",
      "summary": "Building workman, my agent orchestration tool, meant detecting cycles in a dependency graph. A good error names the loop; a useless one just says one exists.",
      "tags": [
        "Engineering",
        "Projects"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/webgl-contexts-are-scarce/",
      "url": "https://lindsayedwardsit.com/blog/webgl-contexts-are-scarce/",
      "title": "WebGL contexts are scarce",
      "date_published": "2026-05-28T00:00:00.000Z",
      "content_text": "Two 3D scenes on one page, and the second went blank with Context Lost. I blamed the material. The real limit was the browser itself.",
      "summary": "Two 3D scenes on one page, and the second went blank with Context Lost. I blamed the material. The real limit was the browser itself.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/monorepo-tooling-will-fight-you/",
      "url": "https://lindsayedwardsit.com/blog/monorepo-tooling-will-fight-you/",
      "title": "Monorepo tooling will fight you",
      "date_published": "2026-05-24T00:00:00.000Z",
      "content_text": "Three footguns from a multi-package TypeScript monorepo: a rootDir that rejected sibling imports, a shared library that broke everyone at once, and a bundler tracing dead code.",
      "summary": "Three footguns from a multi-package TypeScript monorepo: a rootDir that rejected sibling imports, a shared library that broke everyone at once, and a bundler tracing dead code.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/optional-features-should-fail-open/",
      "url": "https://lindsayedwardsit.com/blog/optional-features-should-fail-open/",
      "title": "Optional features should fail open",
      "date_published": "2026-05-20T00:00:00.000Z",
      "content_text": "A new AI classifier had no business taking the main pipeline down when the model call failed. So I built it opt-in, fail-open, cached, and gated behind a cheap check.",
      "summary": "A new AI classifier had no business taking the main pipeline down when the model call failed. So I built it opt-in, fail-open, cached, and gated behind a cheap check.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/a-living-gotchas-file/",
      "url": "https://lindsayedwardsit.com/blog/a-living-gotchas-file/",
      "title": "Keep a living gotchas file",
      "date_published": "2026-05-16T00:00:00.000Z",
      "content_text": "The sharpest bugs compile cleanly and only bite at runtime or in review. A short gotchas file in the repo pays for itself the second time one shows up.",
      "summary": "The sharpest bugs compile cleanly and only bite at runtime or in review. A short gotchas file in the repo pays for itself the second time one shows up.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/you-cant-mock-what-you-new-up/",
      "url": "https://lindsayedwardsit.com/blog/you-cant-mock-what-you-new-up/",
      "title": "You can't mock what you new up",
      "date_published": "2026-05-12T00:00:00.000Z",
      "content_text": "A collaborator was built with new inside the service, so no test could reach it. The fix was to move the seam to the edge of my own logic.",
      "summary": "A collaborator was built with new inside the service, so no test could reach it. The fix was to move the seam to the edge of my own logic.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/mobile-is-two-questions/",
      "url": "https://lindsayedwardsit.com/blog/mobile-is-two-questions/",
      "title": "Mobile is two questions",
      "date_published": "2026-05-08T00:00:00.000Z",
      "content_text": "A pixel-positioned desktop UI was unusable on phones. The fix was not one breakpoint. It was realising mobile is two separate questions.",
      "summary": "A pixel-positioned desktop UI was unusable on phones. The fix was not one breakpoint. It was realising mobile is two separate questions.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/12-vector-ai-security-hardening-plan/",
      "url": "https://lindsayedwardsit.com/blog/12-vector-ai-security-hardening-plan/",
      "title": "A 12-vector AI security hardening plan",
      "date_published": "2026-05-05T00:00:00.000Z",
      "content_text": "Building ContentForge's AI pipeline meant threat-modelling it properly first. Here is the practical, plain-English checklist of twelve places things go wrong, and how to harden each one.",
      "summary": "Building ContentForge's AI pipeline meant threat-modelling it properly first. Here is the practical, plain-English checklist of twelve places things go wrong, and how to harden each one.",
      "tags": [
        "Cybersecurity",
        "AI",
        "Engineering",
        "Projects"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/state-a-render-loop-reads-belongs-in-a-ref/",
      "url": "https://lindsayedwardsit.com/blog/state-a-render-loop-reads-belongs-in-a-ref/",
      "title": "State a render loop reads belongs in a ref",
      "date_published": "2026-05-04T00:00:00.000Z",
      "content_text": "A canvas game where you could never lose. The death screen showed for one frame, then the game quietly restarted. The bug was in the effect deps.",
      "summary": "A canvas game where you could never lose. The death screen showed for one frame, then the game quietly restarted. The bug was in the effect deps.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/a-real-concurrency-cap/",
      "url": "https://lindsayedwardsit.com/blog/a-real-concurrency-cap/",
      "title": "A real concurrency cap: a worker pool and a circuit breaker",
      "date_published": "2026-04-30T00:00:00.000Z",
      "content_text": "Promise.all is not a concurrency limit, it is a promise to run everything at once. Two lessons on capping work and cutting off a source that keeps failing.",
      "summary": "Promise.all is not a concurrency limit, it is a promise to run everything at once. Two lessons on capping work and cutting off a source that keeps failing.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/normalize-at-the-boundary/",
      "url": "https://lindsayedwardsit.com/blog/normalize-at-the-boundary/",
      "title": "Normalise at the boundary, not everywhere else",
      "date_published": "2026-04-26T00:00:00.000Z",
      "content_text": "My similarity thresholds kept drifting for no reason I could see. The cause was mixing normalised and raw vectors, so cosine was measuring two different things at once.",
      "summary": "My similarity thresholds kept drifting for no reason I could see. The cause was mixing normalised and raw vectors, so cosine was measuring two different things at once.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/isolate-the-runtime-codegen-the-contract/",
      "url": "https://lindsayedwardsit.com/blog/isolate-the-runtime-codegen-the-contract/",
      "title": "Isolate the runtime, codegen the contract",
      "date_published": "2026-04-22T00:00:00.000Z",
      "content_text": "Keeping a heavy Python ML service out of the JS install was the right call. Hand-mirroring the request shapes on both sides was not.",
      "summary": "Keeping a heavy Python ML service out of the JS install was the right call. Hand-mirroring the request shapes on both sides was not.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/why-i-left-wordpress/",
      "url": "https://lindsayedwardsit.com/blog/why-i-left-wordpress/",
      "title": "Why I left WordPress (and you might too)",
      "date_published": "2026-04-22T00:00:00.000Z",
      "content_text": "WordPress is a fine tool that quietly became the wrong one for me. A fair, slightly spicy account of why I left, and how to tell if you should too.",
      "summary": "WordPress is a fine tool that quietly became the wrong one for me. A fair, slightly spicy account of why I left, and how to tell if you should too.",
      "tags": [
        "Engineering",
        "Learning"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-write-that-must-succeed/",
      "url": "https://lindsayedwardsit.com/blog/the-write-that-must-succeed/",
      "title": "The write that must succeed",
      "date_published": "2026-04-18T00:00:00.000Z",
      "content_text": "A mailing-list signup taught me to separate the write that has to land from the nice-to-have side effect, and that binding names lie on shared accounts.",
      "summary": "A mailing-list signup taught me to separate the write that has to land from the nice-to-have side effect, and that binding names lie on shared accounts.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/idempotent-ingestion-is-a-composite-key/",
      "url": "https://lindsayedwardsit.com/blog/idempotent-ingestion-is-a-composite-key/",
      "title": "Idempotent ingestion is a three-part key",
      "date_published": "2026-04-14T00:00:00.000Z",
      "content_text": "The same external id can collide across providers and must never merge across users. Why idempotent third-party ingestion is a composite key, enforced in two places.",
      "summary": "The same external id can collide across providers and must never merge across users. Why idempotent third-party ingestion is a composite key, enforced in two places.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/graceful-drain-on-shutdown/",
      "url": "https://lindsayedwardsit.com/blog/graceful-drain-on-shutdown/",
      "title": "Graceful drain on shutdown: the deploy that killed a three-minute job",
      "date_published": "2026-04-10T00:00:00.000Z",
      "content_text": "A routine deploy landed mid-render. The process died, the job stuck 'active', the output half written. The fix was letting SIGTERM finish the work first.",
      "summary": "A routine deploy landed mid-render. The process died, the job stuck 'active', the output half written. The fix was letting SIGTERM finish the work first.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/rebuilding-my-site-wordpress-to-astro-cloudflare/",
      "url": "https://lindsayedwardsit.com/blog/rebuilding-my-site-wordpress-to-astro-cloudflare/",
      "title": "Rebuilding my site: from WordPress to Astro on Cloudflare",
      "date_published": "2026-04-08T00:00:00.000Z",
      "content_text": "I tore the old WordPress site down to the studs and rebuilt it as a fast, repo-native Astro project on Cloudflare. Here is the why, the how, and what I would do again.",
      "summary": "I tore the old WordPress site down to the studs and rebuilt it as a fast, repo-native Astro project on Cloudflare. Here is the why, the how, and what I would do again.",
      "tags": [
        "Engineering",
        "Projects"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/dedup-on-meaning-not-on-an-id/",
      "url": "https://lindsayedwardsit.com/blog/dedup-on-meaning-not-on-an-id/",
      "title": "Deduplicate on meaning, not on an id",
      "date_published": "2026-04-06T00:00:00.000Z",
      "content_text": "My deduplication code ran on every batch and never removed a single duplicate. It was keying on a random id that is unique by construction, so it could only ever match a row against itself.",
      "summary": "My deduplication code ran on every batch and never removed a single duplicate. It was keying on a random id that is unique by construction, so it could only ever match a row against itself.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/one-vocabulary-behind-many-vendors/",
      "url": "https://lindsayedwardsit.com/blog/one-vocabulary-behind-many-vendors/",
      "title": "One vocabulary behind many vendors",
      "date_published": "2026-04-02T00:00:00.000Z",
      "content_text": "Every provider named the same thing differently. The fix was not to remember all their names, but to stop letting any of them into the rest of the code.",
      "summary": "Every provider named the same thing differently. The fix was not to remember all their names, but to stop letting any of them into the rest of the code.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/a-zero-dependency-edge-backend/",
      "url": "https://lindsayedwardsit.com/blog/a-zero-dependency-edge-backend/",
      "title": "A zero-dependency edge backend",
      "date_published": "2026-03-29T00:00:00.000Z",
      "content_text": "I skipped the official SDKs and called the payment and email APIs with raw fetch. The saving was real. So was the new pile of things I now owned.",
      "summary": "I skipped the official SDKs and called the payment and email APIs with raw fetch. The saving was real. So was the new pile of things I now owned.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/sync-cursors-expire/",
      "url": "https://lindsayedwardsit.com/blog/sync-cursors-expire/",
      "title": "Sync cursors expire, and so does the empty window",
      "date_published": "2026-03-25T00:00:00.000Z",
      "content_text": "An incremental sync that ran clean for weeks, then reprocessed the same empty window forever. Two non-obvious failures in one cursor, and how each one gets designed for.",
      "summary": "An incremental sync that ran clean for weeks, then reprocessed the same empty window forever. Two non-obvious failures in one cursor, and how each one gets designed for.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/one-process-many-workers/",
      "url": "https://lindsayedwardsit.com/blog/one-process-many-workers/",
      "title": "One process, eleven workers: how a heavy render starved the cheap jobs",
      "date_published": "2026-03-21T00:00:00.000Z",
      "content_text": "Eleven queue workers shared one event loop. A GPU render on defaults (concurrency 1, no retries) starved the cheap jobs, and Redis filled up forever.",
      "summary": "Eleven queue workers shared one event loop. A GPU render on defaults (concurrency 1, no retries) starved the cheap jobs, and Redis filled up forever.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-fake-db-taxes-your-real-types/",
      "url": "https://lindsayedwardsit.com/blog/the-fake-db-taxes-your-real-types/",
      "title": "The fake test database taxes your real types",
      "date_published": "2026-03-17T00:00:00.000Z",
      "content_text": "Tests ran on in-memory SQLite, prod ran on Postgres with a vector column that SQLite has never heard of. Keeping the tests green quietly threw away the index I was paying for.",
      "summary": "Tests ran on in-memory SQLite, prod ran on Postgres with a vector column that SQLite has never heard of. Keeping the tests green quietly threw away the index I was paying for.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/middleware-order-is-correctness/",
      "url": "https://lindsayedwardsit.com/blog/middleware-order-is-correctness/",
      "title": "Middleware order is correctness",
      "date_published": "2026-03-13T00:00:00.000Z",
      "content_text": "Auth ran before the rate limiter, and that order was load-bearing. Swap the two lines and every user behind one office router starts colliding.",
      "summary": "Auth ran before the rate limiter, and that order was load-bearing. Swap the two lines and every user behind one office router starts colliding.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/envelope-encryption-without-a-big-bang/",
      "url": "https://lindsayedwardsit.com/blog/envelope-encryption-without-a-big-bang/",
      "title": "Envelope encryption without a big-bang migration",
      "date_published": "2026-03-09T00:00:00.000Z",
      "content_text": "How a nullable key-id column let me roll out per-user envelope encryption alongside old rows, and why every secret leaves the server as a boolean.",
      "summary": "How a nullable key-id column let me roll out per-user envelope encryption alongside old rows, and why every secret leaves the server as a boolean.",
      "tags": [
        "Engineering",
        "Cybersecurity"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/a-long-job-behind-a-sync-request/",
      "url": "https://lindsayedwardsit.com/blog/a-long-job-behind-a-sync-request/",
      "title": "A long job behind a sync request: why the socket died at 60 seconds",
      "date_published": "2026-03-05T00:00:00.000Z",
      "content_text": "A media pipeline did three heavy stages inside one HTTP POST. The caller held the socket open for minutes, then got ECONNRESET instead of a result.",
      "summary": "A media pipeline did three heavy stages inside one HTTP POST. The caller held the socket open for minutes, then got ECONNRESET instead of a result.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/never-trust-the-clients-price/",
      "url": "https://lindsayedwardsit.com/blog/never-trust-the-clients-price/",
      "title": "Never trust the client's price",
      "date_published": "2026-02-28T00:00:00.000Z",
      "content_text": "The browser cart is a request for a price, not a source of truth. Here is how I stopped treating it like one on a Stripe checkout.",
      "summary": "The browser cart is a request for a price, not a source of truth. Here is how I stopped treating it like one on a Stripe checkout.",
      "tags": [
        "Engineering",
        "Cybersecurity"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/an-error-taxonomy-across-the-wire/",
      "url": "https://lindsayedwardsit.com/blog/an-error-taxonomy-across-the-wire/",
      "title": "An error taxonomy across the wire",
      "date_published": "2026-02-24T00:00:00.000Z",
      "content_text": "Every endpoint turned every failure into the same 500 with a stringified message, and the retry layer had no way to tell a blip from a dead end.",
      "summary": "Every endpoint turned every failure into the same 500 with a stringified message, and the retry layer had no way to tell a blip from a dead end.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-oauth-refresh-that-bricks-the-connection/",
      "url": "https://lindsayedwardsit.com/blog/the-oauth-refresh-that-bricks-the-connection/",
      "title": "The OAuth refresh that quietly bricks the connection",
      "date_published": "2026-02-20T00:00:00.000Z",
      "content_text": "Assuming a token refresh returns a new refresh token will permanently kill an integration the first time the provider decides not to. The fix is a single fallback, and a lazier refresh.",
      "summary": "Assuming a token refresh returns a new refresh token will permanently kill an integration the first time the provider decides not to. The fix is a single fallback, and a lazier refresh.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/expensive-handles-are-singletons/",
      "url": "https://lindsayedwardsit.com/blog/expensive-handles-are-singletons/",
      "title": "Expensive handles want to be singletons",
      "date_published": "2026-02-16T00:00:00.000Z",
      "content_text": "A machine-learning model loaded per request will quietly cost you CPU and leak native memory if disposal ever slips. The fix is old: a long-lived handle behind a stateless facade.",
      "summary": "A machine-learning model loaded per request will quietly cost you CPU and leak native memory if disposal ever slips. The fix is old: a long-lived handle behind a stateless facade.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/keyset-pagination-needs-the-sort-key/",
      "url": "https://lindsayedwardsit.com/blog/keyset-pagination-needs-the-sort-key/",
      "title": "Keyset pagination needs the sort key in the cursor",
      "date_published": "2026-02-12T00:00:00.000Z",
      "content_text": "Cursor pagination is lovely until you let people sort by an arbitrary column. If the cursor doesn't carry the sort value, it quietly degrades to skipping and duplicating rows at page boundaries.",
      "summary": "Cursor pagination is lovely until you let people sort by an arbitrary column. If the cursor doesn't carry the sort value, it quietly degrades to skipping and duplicating rows at page boundaries.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/not-configured-is-a-supported-state/",
      "url": "https://lindsayedwardsit.com/blog/not-configured-is-a-supported-state/",
      "title": "Not configured is a supported state",
      "date_published": "2026-02-08T00:00:00.000Z",
      "content_text": "Half a dozen optional integrations, each able to be missing. Instead of branching everywhere, they degrade to a no-op that still runs the surrounding path. Plus the one no-op that's dangerous.",
      "summary": "Half a dozen optional integrations, each able to be missing. Instead of branching everywhere, they degrade to a no-op that still runs the surrounding path. Plus the one no-op that's dangerous.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/synchronous-is-a-feature/",
      "url": "https://lindsayedwardsit.com/blog/synchronous-is-a-feature/",
      "title": "Synchronous is a feature: picking an embedded database",
      "date_published": "2026-02-04T00:00:00.000Z",
      "content_text": "I started a local-first tool on async in-memory SQLite and kept losing writes. A synchronous native database deleted the whole category of bug. For a single-process app, sync is the safer choice.",
      "summary": "I started a local-first tool on async in-memory SQLite and kept losing writes. A synchronous native database deleted the whole category of bug. For a single-process app, sync is the safer choice.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-config-key-that-ran-prod-on-sqlite/",
      "url": "https://lindsayedwardsit.com/blog/the-config-key-that-ran-prod-on-sqlite/",
      "title": "The config key that ran production on a disposable database",
      "date_published": "2026-01-31T00:00:00.000Z",
      "content_text": "The app read its connection string from one key; the deployment supplied a different one. So production quietly ran on an ephemeral SQLite file that vanished on restart, with no error at boot.",
      "summary": "The app read its connection string from one key; the deployment supplied a different one. So production quietly ran on an ephemeral SQLite file that vanished on restart, with no error at boot.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/a-connected-socket-is-not-server-push/",
      "url": "https://lindsayedwardsit.com/blog/a-connected-socket-is-not-server-push/",
      "title": "A connected socket is not server push",
      "date_published": "2026-01-27T00:00:00.000Z",
      "content_text": "The real-time feature looked done: a hub class on the server, a client connected with auto-reconnect. It was quietly updating by polling, because nothing on the server ever actually pushed.",
      "summary": "The real-time feature looked done: a hub class on the server, a client connected with auto-reconnect. It was quietly updating by polling, because nothing on the server ever actually pushed.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/model-time-like-you-mean-it/",
      "url": "https://lindsayedwardsit.com/blog/model-time-like-you-mean-it/",
      "title": "Model time like you mean it (and commit migrations as one unit)",
      "date_published": "2026-01-23T00:00:00.000Z",
      "content_text": "Two quiet Postgres and EF Core decisions that only bite you later: storing a bare local-or-UTC timestamp, and committing a migration without the snapshot it belongs to.",
      "summary": "Two quiet Postgres and EF Core decisions that only bite you later: storing a bare local-or-UTC timestamp, and committing a migration without the snapshot it belongs to.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/a-migration-ships-everywhere/",
      "url": "https://lindsayedwardsit.com/blog/a-migration-ships-everywhere/",
      "title": "A migration ships everywhere: the admin account I baked into the build",
      "date_published": "2026-01-19T00:00:00.000Z",
      "content_text": "Seed data in a migration felt convenient until I realised it meant a known admin login running on every deployment. On what belongs in a migration and what absolutely does not.",
      "summary": "Seed data in a migration felt convenient until I realised it meant a known admin login running on every deployment. On what belongs in a migration and what absolutely does not.",
      "tags": [
        "Engineering",
        "Cybersecurity"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/what-row-level-security-wont-protect/",
      "url": "https://lindsayedwardsit.com/blog/what-row-level-security-wont-protect/",
      "title": "What row-level security won't protect you from",
      "date_published": "2026-01-15T00:00:00.000Z",
      "content_text": "Postgres RLS is a lovely way to enforce tenant isolation in the database, and it has a few quiet ways of being completely bypassed. The ones that caught me.",
      "summary": "Postgres RLS is a lovely way to enforce tenant isolation in the database, and it has a few quiet ways of being completely bypassed. The ones that caught me.",
      "tags": [
        "Engineering",
        "Cybersecurity"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/cross-cutting-only-cuts-what-flows-through-it/",
      "url": "https://lindsayedwardsit.com/blog/cross-cutting-only-cuts-what-flows-through-it/",
      "title": "A cross-cutting concern only cuts what flows through it",
      "date_published": "2026-01-11T00:00:00.000Z",
      "content_text": "Audit logging, validation, and mapping that were 'handled centrally' but only covered the endpoints that happened to route through the centre. On guarantees that are really conventions.",
      "summary": "Audit logging, validation, and mapping that were 'handled centrally' but only covered the endpoints that happened to route through the centre. On guarantees that are really conventions.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/looks-finished-isnt/",
      "url": "https://lindsayedwardsit.com/blog/looks-finished-isnt/",
      "title": "Looks finished, isn't: streams and processes that lie to you",
      "date_published": "2026-01-07T00:00:00.000Z",
      "content_text": "Three things that appear done and aren't: a log tailer that drops half a line, a timeout that doesn't cancel anything, and a process that ignores the polite request to die.",
      "summary": "Three things that appear done and aren't: a log tailer that drops half a line, a timeout that doesn't cancel anything, and a process that ignores the polite request to die.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/deterministic-by-name-random-at-runtime/",
      "url": "https://lindsayedwardsit.com/blog/deterministic-by-name-random-at-runtime/",
      "title": "Deterministic by name, random at runtime",
      "date_published": "2026-01-03T00:00:00.000Z",
      "content_text": "A test that only failed one run in five, on a component labelled deterministic, pointing at a hash that quietly reshuffles itself every time the process restarts.",
      "summary": "A test that only failed one run in five, on a component labelled deterministic, pointing at a hash that quietly reshuffles itself every time the process restarts.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/notes-from-writing-a-vulnerability-scanner/",
      "url": "https://lindsayedwardsit.com/blog/notes-from-writing-a-vulnerability-scanner/",
      "title": "Notes from writing a vulnerability scanner",
      "date_published": "2025-12-30T00:00:00.000Z",
      "content_text": "Building a scanner teaches offence and defence at once: pick markers that can't happen by accident, score by how forgeable the evidence is, and watch your own outbound calls.",
      "summary": "Building a scanner teaches offence and defence at once: pick markers that can't happen by accident, score by how forgeable the evidence is, and watch your own outbound calls.",
      "tags": [
        "Cybersecurity",
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/multi-tenant-isolation-is-a-minefield/",
      "url": "https://lindsayedwardsit.com/blog/multi-tenant-isolation-is-a-minefield/",
      "title": "Multi-tenant isolation is a series of footguns",
      "date_published": "2025-12-26T00:00:00.000Z",
      "content_text": "Every tenant-scoped system I've worked on had the same class of bug: isolation that looks centralised and airtight, with a handful of quiet ways for one tenant's data to reach another.",
      "summary": "Every tenant-scoped system I've worked on had the same class of bug: isolation that looks centralised and airtight, with a handful of quiet ways for one tenant's data to reach another.",
      "tags": [
        "Engineering",
        "Cybersecurity"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/deactivate-dont-destroy/",
      "url": "https://lindsayedwardsit.com/blog/deactivate-dont-destroy/",
      "title": "Deactivate, don't destroy: reversible state over destructive migrations",
      "date_published": "2025-12-22T00:00:00.000Z",
      "content_text": "In a system where modules turn on and off at runtime, disabling one should never drop its tables. On reversible state, and giving each module its own migration history.",
      "summary": "In a system where modules turn on and off at runtime, disabling one should never drop its tables. On reversible state, and giving each module its own migration history.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/startup-order-is-a-real-thing/",
      "url": "https://lindsayedwardsit.com/blog/startup-order-is-a-real-thing/",
      "title": "Startup order is a real thing",
      "date_published": "2025-12-18T00:00:00.000Z",
      "content_text": "Two bugs that live in the first few milliseconds of a process: a security check that ran too late to matter, and a test that froze the logger before the app could build.",
      "summary": "Two bugs that live in the first few milliseconds of a process: a security check that ran too late to matter, and a test that froze the logger before the app could build.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-bug-the-happy-path-hides/",
      "url": "https://lindsayedwardsit.com/blog/the-bug-the-happy-path-hides/",
      "title": "The bug the happy path can't show you",
      "date_published": "2025-12-14T00:00:00.000Z",
      "content_text": "Two correctness bugs that never appear when everything goes right: a tenant filter that only works on the request path, and an aggregate saved in two steps that can strand half of itself.",
      "summary": "Two correctness bugs that never appear when everything goes right: a tenant filter that only works on the request path, and an aggregate saved in two steps that can strand half of itself.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/make-the-rule-a-failing-test/",
      "url": "https://lindsayedwardsit.com/blog/make-the-rule-a-failing-test/",
      "title": "Make the rule a failing test: ratchets, tripwires, and a refactor that stayed boring",
      "date_published": "2025-12-10T00:00:00.000Z",
      "content_text": "You can't fix every latent problem at once. But you can freeze how bad it is and forbid it from getting worse. On turning architecture rules and scary refactors into tests that fail loudly.",
      "summary": "You can't fix every latent problem at once. But you can freeze how bad it is and forbid it from getting worse. On turning architecture rules and scary refactors into tests that fail loudly.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/green-tests-broken-software/",
      "url": "https://lindsayedwardsit.com/blog/green-tests-broken-software/",
      "title": "Green tests, broken software: when the suite is lying to you",
      "date_published": "2025-12-06T00:00:00.000Z",
      "content_text": "A full green test run is not the same as working software. Two cases where every test passed and the feature was dead in production, and what the tests were actually failing to check.",
      "summary": "A full green test run is not the same as working software. Two cases where every test passed and the feature was dead in production, and what the tests were actually failing to check.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/the-n-plus-one-you-cant-see/",
      "url": "https://lindsayedwardsit.com/blog/the-n-plus-one-you-cant-see/",
      "title": "The N+1 you can't see, and a cache that must never lie",
      "date_published": "2025-12-02T00:00:00.000Z",
      "content_text": "A twelve-second report caused by a database call hiding inside an innocent-looking helper, the test that stops it coming back, and a few rules for caches that fail safe.",
      "summary": "A twelve-second report caused by a database call hiding inside an innocent-looking helper, the test that stops it coming back, and a few rules for caches that fail safe.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/money-time-and-a-lying-database/",
      "url": "https://lindsayedwardsit.com/blog/money-time-and-a-lying-database/",
      "title": "Money, time, and a database that lied to me",
      "date_published": "2025-11-28T00:00:00.000Z",
      "content_text": "Three data bugs that are small, plausible, and expensive to notice: money stored as floats, month boundaries computed in the wrong timezone, and a test database quietly disagreeing with the real one.",
      "summary": "Three data bugs that are small, plausible, and expensive to notice: money stored as floats, month boundaries computed in the wrong timezone, and a test database quietly disagreeing with the real one.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/making-peace-with-at-least-once/",
      "url": "https://lindsayedwardsit.com/blog/making-peace-with-at-least-once/",
      "title": "Making peace with at-least-once: field notes from a message queue",
      "date_published": "2025-11-24T00:00:00.000Z",
      "content_text": "A durable queue will hand you the same message twice, and one bad message can wedge the whole worker. Four things I changed once I stopped fighting that and designed for it.",
      "summary": "A durable queue will hand you the same message twice, and one bad message can wedge the whole worker. Four things I changed once I stopped fighting that and designed for it.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/git-worktrees-parallel-without-collisions/",
      "url": "https://lindsayedwardsit.com/blog/git-worktrees-parallel-without-collisions/",
      "title": "Git worktrees, or how I stopped my parallel work colliding",
      "date_published": "2025-11-20T00:00:00.000Z",
      "content_text": "When you run several streams of work against one repo at once, sharing a working tree turns into a branch-checkout collision storm. Worktrees, and partitioning by the files you touch, fix it.",
      "summary": "When you run several streams of work against one repo at once, sharing a working tree turns into a branch-checkout collision storm. Worktrees, and partitioning by the files you touch, fix it.",
      "tags": [
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/factory-of-one-building-with-ai-agents/",
      "url": "https://lindsayedwardsit.com/blog/factory-of-one-building-with-ai-agents/",
      "title": "A factory of one: building a software studio solo with AI agents",
      "date_published": "2025-11-16T00:00:00.000Z",
      "content_text": "I have more projects on the go than one person has any business running. Here is the system that makes that ambitious instead of insane, and the honest catch in it.",
      "summary": "I have more projects on the go than one person has any business running. Here is the system that makes that ambitious instead of insane, and the honest catch in it.",
      "tags": [
        "Projects",
        "AI",
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/what-i-actually-use-ai-for/",
      "url": "https://lindsayedwardsit.com/blog/what-i-actually-use-ai-for/",
      "title": "What I actually use AI for as an engineer (and what I don't)",
      "date_published": "2025-11-12T00:00:00.000Z",
      "content_text": "Past the hype and the doom, the honest daily reality of building with AI: where it genuinely earns its place in my workflow, and where I keep it well away.",
      "summary": "Past the hype and the doom, the honest daily reality of building with AI: where it genuinely earns its place in my workflow, and where I keep it well away.",
      "tags": [
        "AI",
        "Engineering"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/defenders-pivot-security-advantage-ai/",
      "url": "https://lindsayedwardsit.com/blog/defenders-pivot-security-advantage-ai/",
      "title": "The defender's pivot: why security is an unfair advantage in AI engineering",
      "date_published": "2025-11-08T00:00:00.000Z",
      "content_text": "People treat moving from cybersecurity into AI engineering like a leap. It felt more like turning the same habits around to face the other way.",
      "summary": "People treat moving from cybersecurity into AI engineering like a leap. It felt more like turning the same habits around to face the other way.",
      "tags": [
        "AI",
        "Cybersecurity"
      ]
    },
    {
      "id": "https://lindsayedwardsit.com/blog/learning-in-public-before-youre-ready/",
      "url": "https://lindsayedwardsit.com/blog/learning-in-public-before-youre-ready/",
      "title": "Learning in public before you feel ready",
      "date_published": "2025-11-04T00:00:00.000Z",
      "content_text": "Waiting until you're an expert to write anything is a great way to write nothing. A case for posting the honest, half-finished version, from someone still doing it.",
      "summary": "Waiting until you're an expert to write anything is a great way to write nothing. A case for posting the honest, half-finished version, from someone still doing it.",
      "tags": [
        "Learning"
      ]
    }
  ]
}