InFeeo
Global
technology-news
New
Language
Profile channel

@Simon

No bio yet.

Since 31.05.2026

Tripping on Acid(scotmurphy.com)
Tripping on ACID Notes on cross-boundary transaction hijacking What I noticed about commit ownership drifting across packages, triggers, scripts, and clients — in one horribly complex system I help maintain Let’s start with the good news: your database is (probably) not on drugs. The bad news? Its transaction boundaries might be absolutely tripping. Mine were. I could not answer a simple operational question about systems I work on every week: who decides when this operation becomes durable? Not which service owns the API. Not which team owns the package. Who issues the COMMIT that makes the state real? I am not writing this as the authority on those systems. I am the person who got tired of not knowing. After enough half-done data and residual side effects, that question became a private obsession — a hobby I did not consent to. What I kept finding was a small set of ways our stack could hallucinate who owns the transaction: partial-persistence loops, rollback-then-commit exception flows, autonomous triggers that survive rollbacks like cockroaches in a fallout shelter. Still in production. Still ruining Mondays. What this is: notes from grepping and incident-chasing one multi-tier estate — costs I saw, shapes in the code, questions that helped me stop guessing. What this is not: a universal diagnosis, “delete every COMMIT,” a claim every shape is always wrong, or a finished fix story. Some patterns are intentional policy. Most of what bit us was undocumented ownership, not the keyword itself. Your estate may rhyme; it will not be identical. The jokes are free. The residual rows are not. Patterns below are from real code paths I touched. Comedy is a coping mechanism, not a severity rating — and not a claim that I have this solved. Shapes come from a large Oracle PL/SQL + fat-client desktop + ASP.NET mess. If you have more than one layer that can finalize work, you may recognize the shape even if the dialect differs. When durability becomes a suggestion I can usually answer who owns a service endpoint. I often could not answer who owns COMMIT on a multi-layer write path. When ownership is ambiguous, independent control points fire on one logical operation: a desktop client with COMMIT USING, an app repository that never opens a TransactionScope, a package body that commits mid-workflow, an autonomous trigger with its own durability channel, a release script that decides rollback policy on the way out. None of that was exotic in our tree. Each piece was locally rational. Together, durability stopped feeling like a designed boundary and started feeling like an emergent outcome of whichever layers ran — often undocumented, occasionally contradictory, invisible until something broke. COMMIT felt less like an authoritative command and more… like a suggestion. Working label I ended up using — transaction boundary hijacking: any layer that changes durability outcome outside a clear ownership contract — commits it did not own as the boundary, side effects that outlive upstream rollback, atomic units sliced by hidden intermediate commits, or durability coupled to object mechanics (ON COMMIT refresh) instead of policy. The cases that confused me were not “someone used COMMIT.” They were “someone used COMMIT where it rewrote the contract I thought the caller had.” The framing that helped: a transaction boundary is control-plane machinery. When multiple layers can override it unilaterally, atomicity and recoverability stop behaving like guarantees and start behaving like path accidents. ACID properties stop reading like properties. They start reading like vibes. How the objects lined up in our stack — call sites and in-database things, each able to finalize something: flowchart TB subgraph outside [Outside the database] direction LR Desktop([Desktop clientwindow + connection handle]) App[Web MVC apprepository class] Scripts[/Release or backoutSQL runner scripts/] end subgraph inside [Inside the database] direction TB Pkg[[Package bodyworkflow_pkg]] Trig{{Triggerautonomous transaction}} Biz[(Business tablesorders, balances)] Side[(Side-effect tablesaudit, error_log)] Chunk[(Work or staging tableschunk N of M durable)] end Desktop -->|COMMIT USING / ROLLBACK USING| Pkg App -->|ExecuteProc, often no TransactionScope| Pkg Scripts -->|script-level COMMIT / ROLLBACK| Pkg Pkg -->|COMMIT primary path| Biz Pkg -->|fires| Trig Trig -->|own COMMIT, survives caller ROLLBACK| Side Pkg -->|COMMIT inside LOOP or FORALL| Chunk classDef major fill:#0b1220,stroke:#64748b,color:#e2e8f0 classDef majorBiz fill:#052e16,stroke:#4ade80,color:#ecfdf5 classDef minor fill:#e2e8f0,stroke:#64748b,color:#0f172a classDef minorWarn fill:#ffedd5,stroke:#ea580c,color:#7c2d12 classDef minorLeak fill:#fee2e2,stroke:#dc2626,color:#7f1d1d class Pkg major class Biz majorBiz class Desktop,App,Scripts minor class Trig minorWarn class Side,Chunk minorLeak style outside fill:#020617,stroke:#334155,color:#94a3b8 style inside fill:#020617,stroke:#334155,color:#94a3b8 Where it actually hurt Grep counts only mattered once I connected them to pain I had already felt: Atomicity — Intermediate commits left neither-committed-nor-rolled-back state. Schrödinger’s transaction; no clean recovery point. Recoverability — Residual rows outlived the “failed” work. We could not roll back reality; we reconciled it. Debuggability — Cross-layer graphs by hand. Murder weapon: a SQL keyword; suspects: every tier. Blast radius — Shared packages made one batch-era choice ambient law for paths nobody on the call had reviewed — 2005 Makefile energy. Change cost — Touching a COMMIT sometimes meant touching an undocumented checkpoint. Without idempotency first, “cleanup” looked like its own outage risk. Organizational cost — Hard to put on a slide: incident time, reconciliation past budget, migrations that could not match accidental durability semantics. For us this was not a style argument. It was a reliability and cost problem dressed as “how we’ve always done batches.” How the trail started The suite is multi-decade: several database schemas, fat-client desktop, MVC web (core OLTP/batch, interface ingest, service-bus inbox, web admin, desktop writers, ASP.NET calling stored procedures). Our team owns the app, release, and desktop surface; DBAs partner on the database estate. The last of the original SMEs — the people who could still tell you why a given path committed where it did — retired in 2024. After an incident left durable state nobody on the call could attribute, I asked who committed this? Nobody knew — not the DBA on the bridge, not the people on our team who ship those layers, and there was no longer an original designer to ping. That is not a flex. That is the whole point. The question stuck because I could not answer it either, and the oral history had walked out the door. No funded study. No research mandate. A person with grep and dread, poking at files I already had open for other reasons. I searched for boundary primitives — COMMIT, ROLLBACK, SAVEPOINT, PRAGMA AUTONOMOUS_TRANSACTION, DDL via EXECUTE IMMEDIATE, ON COMMIT, client COMMIT USING / ROLLBACK USING, app TransactionScope / BeginTransaction — and noted what tended to show up together. Comment noise got in the way; uncommon forms got a second pass (ROLLBACK TO, SET TRANSACTION, DBMS_TRANSACTION). Match counts were triage for me, not bug tallies; active vs commented-out autonomous pragmas still need a human eye. What fell out of that mess: a hundred “unique” package problems started looking like the same few behaviors under different names. The C# layer almost never owned transactions in the code I opened; it delegated into the database. Mid-incident I did not want a catalog — I wanted shapes I could recognize while grepping. For this estate, they settled into six. I am not claiming exhaustiveness for every estate on earth. For this corpus, under static search, new files usually re-hit one of these six rather than inventing a seventh. Runtime-built SQL can still surprise me. How it showed up for me The batch that cannot start over. COMMIT inside loops or after each chunk. Failure at chunk k left 1..k−1 durable. Retry only made sense when it was idempotent and position-aware. Multi-stage “milestone” commits felt like the same idea with better names. The failure path that still leaves a scar. ROLLBACK, write error/audit, COMMIT. Fine when independent side-effect durability is written down as policy. In the packages I opened, it usually was not. Road to data hell: good intentions and EXCEPTION WHEN OTHERS. The callee that owns durability for the caller. “Composable” procedures that commit themselves — and then you cannot compose them. Variants I hit: COMMIT WORK, DBMS_TRANSACTION, savepoint slicing invisible to outer callers. The side effect that survives the undo. Autonomous triggers and package routines. Caller rolls back; autonomous work may not. Sometimes that is exactly what audit wants. Undocumented, it became the residual row in the incident notes. The commit that does more than you wrote. Runtime DDL via EXECUTE IMMEDIATE (implicit commits); ON COMMIT refresh expanding a commit beyond the DML on the screen. The layer that thought it was a guest. Desktop COMMIT USING (Save as final boss of disk); release scripts with their own policy; web repos that only call procedures — innocent C#, opinionated package body. When a new file made me wince, it was almost always another instance of one of these six — not a brand-new kind of magic. In this tree, anyway. What it looked like in code Anonymized shapes from patterns I kept seeing. Names are generic stand-ins, not production identifiers. -- Chunk commit: throughput in, atomic restart out FOR i IN 1 .. CEIL(l_total / l_chunk_size) LOOP FORALL j IN l_from .. l_to INSERT INTO work_table VALUES l_rows(j); COMMIT; -- progress checkpoint, not a business boundary END LOOP; Efficient until mid-batch failure. Recovery only worked for us when it knew chunk identity. -- Failure that still mutates durable state BEGIN perform_main_work(); COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; INSERT INTO error_log(error_details, error_time) VALUES (SQLERRM, SYSTIMESTAMP); COMMIT; RAISE; END; Two durability outcomes. Fine if specified. Bad if discovered from leftover rows at 2 a.m. -- Autonomous trigger: audit may outlive the business update CREATE OR REPLACE TRIGGER trg_audit_row_changes BEFORE UPDATE ON domain_rows FOR EACH ROW DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO row_change_audit(row_id, old_status, new_status, changed_by) VALUES (:OLD.id, :OLD.status, :NEW.status, USER); COMMIT; END; If that independence is intentional, I wish it had been written down. Otherwise the audit outliving the update that “never happened” is a nasty surprise. -- Client-owned boundary (desktop / 4GL-style) IF update_succeeded THEN COMMIT USING connection_handle; ELSE ROLLBACK USING connection_handle; END IF On this path the UI owns durability. Any backend assumption that “we control composition” is wrong here. // App looks neutral; package may not be db.ExecuteProcNoReturn("workflow_pkg.apply_changes_prc", payload); Black box at the call site; opinions about disk downstairs. Why I missed it for so long In our world, local patches optimized one symptom (“always keep the error log”) without re-deriving the durability contract. Historical constraints ossified into cargo cult after the constraint died. Org-chart ownership is clearer than the runtime story: our team already owns app, release, and desktop, and we work with DBAs on packages and schemas — yet durability still had no single named owner inside that mandate. Layers could each finalize work; the contract between layers was oral — and oral history does not survive a retirement wave. When the last original SMEs left in 2024, a lot of “ask so-and-so” answers left with them. We wrote endpoint contracts; we almost never wrote boundary contracts. Incident reviews fixed wrong data more often than they asked which layer was allowed to make it durable. Camouflage until I specifically grepped. I know what you’re thinking… “In-loop commit is required for long batches.” Often true in our batch windows. Then the operation is not one atomic transaction; it is chunked durable processing. What helped was stopping the slide-deck fiction of all-or-nothing and talking about idempotency, replay, and chunk identity instead. “Autonomous triggers are required for audit.” Sometimes. Where policy really wants independent durability, fine — I just wish the policy lived somewhere greppable. Undocumented autonomy is what burned us. Residual-row discovery is a lousy design review. “The database owns transactions; the app should not care.” Coherent if ownership is explicit and visible in app contracts (outcomes, retries, partial failure). In the paths I opened, it was not. “App should not care” then meant “app cannot reason about failure.” Bold. Expensive. “This is just bad code. Good teams don’t have this.” I do not think our problem was one incompetent package — or four teams refusing to talk. Our team already owns app, release, and desktop; we still lost the path in the seams between layers (and between our stack and DBA-owned package territory), where no single review walks client → app → package → trigger → script. Competent people filled a vacuum locally, under deadline, for years. That includes me, when I shipped past a smell I did not have time to name. Lint I wish we had Not enforced where I work. Wish list, not law: No COMMIT / ROLLBACK in reusable packages unless something is actually designated the boundary owner No new autonomous triggers without a written independent-durability note No exception ROLLBACK + side-effect COMMIT without a declared side channel No release scripts that silently redefine production transaction semantics App call sites that say whether ownership is app-managed or DB-managed If a dangerous form can be written silently, it will be — under deadline. I say that as someone who has been the deadline. What I started counting Rough dashboard for “how deep is this trip,” not a maturity model: Leading: internal commits outside modules I already knew were boundary owners; active autonomous declarations; rollback-then-commit shapes Lagging: consistency incidents; how long it took to attribute a durable anomaly; partial-state backouts Structural: critical workflows where someone can name the durability owner; chunked workflows with any idempotency story at all Three questions I use now On the next package I already distrust: Who can commit this business operation today? Which side effects can survive rollback of the primary work? Can a failed run be replayed without manual data surgery? Unclear answers are useful — they mean I finally named the confusion. If you fund the migrations rather than live in the packages, the parallel questions that helped conversations with leadership: how long is the committer list on a critical path, who owns side-effect durability policy, and could someone find those owners without tribal knowledge that may no longer work here? Long silence is data. It was for us — especially after 2024. Monday morning: the greps I actually ran Not a product. Not complete. A starter kit for the curious. Hits are triage, not guilt. Adjust paths and dialects. rg -n -S '\b(COMMIT|ROLLBACK|SAVEPOINT)\b' rg -n -S 'PRAGMA\s+AUTONOMOUS_TRANSACTION' rg -n -S 'EXECUTE\s+IMMEDIATE|ON\s+COMMIT' rg -n -S 'COMMIT\s+USING|ROLLBACK\s+USING' rg -n -S 'DBMS_TRANSACTION\.|COMMIT\s+WORK|ROLLBACK\s+TO' rg -n -S 'TransactionScope|BeginTransaction|OracleTransaction' -g '*.cs' I take a second look when I see: COMMIT inside LOOP/FORALL; ROLLBACK then INSERT then COMMIT in one exception block; autonomous pragmas in triggers. Closing I am not selling architecture purity. I am saying that in our multi-tier mess, transaction boundaries behaved like architecture even when we treated them like syntax — and every layer that could type COMMIT eventually did. Durability drifted. ACID stayed true inside fragments. The global property I thought we had was path-dependent. The system is not malicious. It is not even confused. It is just… tripping. I have not fixed this estate. I have patterns I wish I had noticed years earlier, a vocabulary that made a few incident calls less chaotic, and proof — to myself — that the confusion is nameable. Naming was the first thing that helped. Everything after that is still work. If any of this sounds familiar, pick a path you already distrust. Ask who owns the commit. Write the answer down somewhere more durable than a meeting. You might not like what you find. Unlike the residual audit row, that answer can still roll back. Disclaimer: This post was crafted as a joint venture between myself, GPT-5.3-Codex, Deepseek V4 Flash, and Grok 4.5.
On vibe forks(linkedin.com)
Blog PostsOn Vibe Forks2026-06-29People Management in the Age of AI2026-05-14Coding on a Phone: What I Learned Building Software on Mobile in the Last Three Weeks2026-01-14Autopilot, Copilot, and Software Developers2025-10-12Degrees are merely proof of the money you've spent on a course...2025-06-25The AI Remote Has Arrived: Let Go of the Knob2025-05-27Developer Tooling in the age of LLM2024-07-03Developer Agents & The Future of Software Development2024-03-09Team Values2021-08-16J.A.R.V.I.S. for Developers2018-03-09Screens, Windows, Distractions, Focus, and Work"2017-04-15'Mini-Servers' for 'Micro-Services'2017-03-31A few Billion Lines of code Later: Using Static Analysis to Find Bugs in the Real World2016-04-18API Mapping using Text Mining2015-10-10Tricorder: Building a Program Analysis Ecosystem2015-09-30When, How, and Why Developers (Do Not) Test in Their IDEs2015-09-23Hello World!2015-09-21 On Vibe Forks 2026-06-29 LLM code generation enthusiasts are pushing the boundaries of what’s feasible with vibe coding. What started as ghost-text line completion moved to function generation, then to file completion, and now we’re all pushing for whole-project generation. These advances have made a new phenomenon possible: vibe forking. I define a vibe fork as a reimplementation or forked extension of an existing project using vibe coding. It’s a deliberately broad definition. At one end it covers the startups and incumbents forking VS Code to compete for the coveted AI-IDE crown. At the other, it covers solo developers prompting their way to glory, recreating existing software from scratch in a clean-room environment. However, for the purpose of this post, I will focus on the latter. As an example of the latter, Nicholas Carlini at Anthropic set sixteen Claude agents loose on a shared repo to build a C compiler from scratch: over nearly 2,000 Claude Code sessions and $20,000 in API costs, the agent team produced a 100,000-line compiler that can build Linux 6.9 on x86, ARM, and RISC-V. It is worth taking a moment to consider how impressive this is before we add it to the pile of bazillion other amazing things that AI can do. A hundred thousand lines, three architectures, a booting kernel, a 99% pass rate on the GCC torture suite, and it even compiles DOOM. This is not a toy. Neither is it a drop-in replacement of the C compilers for production-ready use cases. This is somewhere in the uncanny valley. The dizzying pace of the advancement of LLM code generation capabilities and the whiplash among developers in terms of their changing role in this new reality forms a fertile ground for wildly varying and imaginative extrapolation of the future: some utopian, some dystopian. When anyone with a decent token budget can vibe code a clean-room implementation of the software that folks put years of their life into, it is only natural for builders and maintainers to worry.1 The strongest version of the worry is real, and I’ll grant it up front: if the cost of reimplementation collapses toward zero, then any project whose only moat was its source code is genuinely exposed. But I feel, while the source code is a necessary condition for a project’s success, it is not a sufficient one. The sucess of software is a function of many factors. Here is my thinking on why I am not yet concerned about vibe forks. In scarcity, features matter; in abundance, curation matters. Today developers are empowered to prompt their way to glory. And when anyone can prompt a C compiler or a PTY implementation or a toy OS to existence, the artifact stops being the scarce thing. Judgment about the artifact becomes scarce: which of the thousand generated compilers is correct, is maintained, is safe to depend on? Answering that is itself labor, and it doesn’t fall out of a model. Rather it accrues as reputation, over time. The app stores are a great lens to look at this. Cloning cost there is already near zero and the barrier to listing is low, so the stores are flooded with clones. A hundred habit trackers, a thousand flashlight apps. The clones exist. They just don’t seem to go the distance. What drives the winner in a category from the mountain of clones is never the feature list. It’s the discovery (the editorial featuring, the reviews, the ranking, the brand, the trust) that separates genuinely useful apps from the pile of clones. Trust and real deployment are the moat, and feature parity is not enough to get past. A drop-in replacement carries a real, hard-to-quantify switching cost. If it ain’t broke, you don’t fix it. You certainly don’t swap a battle-tested dependency for a freshly minted clone that does the same thing. People do switch, but only when the new thing is not equivalent but better by a wide margin. Look at the AI-IDE forks: nobody abandoned VS Code because someone shipped a pixel-identical copy; they moved for Cursor and Windsurf because the AI integration was a genuine step-change. Parity doesn’t move anyone. Slope does. And the very property that makes vibe forking possible works against it here. A widely deployed software has been pulled into the training data and into context windows many times over; the model is good at it precisely because the world has already hit its corner cases and written them down. Zero history of a freshly minted vibe-fork may result in varying mileage of LLM-based coding depending on how far the APIs have deviated from the original project. The LLM is, in a real sense, better at maintaining the incumbent than at sustaining its replacement. The same way Carlini’s compiler leaned on GCC to get over the line. Support is a long-tailed, deeply human task. Agentic greenfield projects can go swimmingly, but brownfield work is where real perseverance is tested. Every project carries essential and accidental complexity, and much of software engineering is the slow work of reducing the accidental part over time. If your program uses only a narrow slice of C, a custom compiler clone may look attractive. The harder question is whether you are willing to maintain that personalized artifact for the full life of the system. Anyone can prompt a prototype in an afternoon. Sustaining it through years of issue triage is still unproven and very debatable. Finally, if everyone is going to set tokens ablaze, a little organization about where we burn them goes a long way. There is little glory in n people pushing their models to the same solution and comparing who got the answer fastest. There is real progress in n people solving n distinct problems in service of one larger goal. These are two extremes and reality sits somewhere in between, but the incentives mostly push away from the wasteful end: economic pressure and plain human pride rarely permit a thousand identical forks to all survive. Ask how many vibe-forks of C compilers are currently out there and how many of them you have actually used. Footnotes Some of these are reminiscent of the “recent” licensing wars: the fights that went mainstream when hyperscalers operationalized OSS as cloud services and developers fought back with more restrictive licenses. The outcome of that bloody, tooth-and-nail battle is still debatable. For instance, RedMonk’s analysis found no meaningful change in financial outcomes from the relicensing. And here is the thing worth remembering before we panic about vibe forks: copyleft was, at bottom, a copyright defense; it governed linking and distribution. The clean-room vibe fork reproduces behavior without copying expression, and walks straight around that defense. That is the genuinely new and unsettling property. But the licensing wars already taught us something deflating: even the defenders who had the copyright weapon and used it saw the revenue needle barely move. The wall everyone is now afraid of losing turns out, on inspection, to have been fairly short all along. ↩