惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
罗磊的独立博客
H
Help Net Security
I
Intezer
G
Google Developers Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
Troy Hunt's Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
U
Unit 42
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News and Events Feed by Topic
J
Java Code Geeks
S
Security Affairs
T
The Blog of Author Tim Ferriss
Recent Commits to openclaw:main
Recent Commits to openclaw:main
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
D
Docker
The GitHub Blog
The GitHub Blog
F
Full Disclosure
N
News and Events Feed by Topic
Webroot Blog
Webroot Blog
S
Security @ Cisco Blogs
腾讯CDC
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
Blog — PlanetScale
Blog — PlanetScale
T
Threatpost
D
DataBreaches.Net
Recent Announcements
Recent Announcements
博客园 - 三生石上(FineUI控件)
MongoDB | Blog
MongoDB | Blog
博客园 - 【当耐特】
L
LINUX DO - 最新话题
Google Online Security Blog
Google Online Security Blog
S
Schneier on Security
S
Securelist
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Help Net Security
Help Net Security
P
Proofpoint News Feed
Project Zero
Project Zero
S
SegmentFault 最新的问题
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MyScale Blog
MyScale Blog
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 叶小钗

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
When Step Five Fails, Undo Steps One Through Four
Mat Weiss · 2026-06-16 · via DEV Community

The previous articles built up a picture of what a compiler can verify about individual operations: their outcomes, what data they can see, what state they require. The operations article introduced N-track pipelines where every outcome gets handled and errors can accumulate along the way. If every step is a pure transformation, that model is sufficient — errors collect, the caller handles them, nothing external has changed.

But real procedures aren't pure transformations. An away mission involves assembling the team, briefing them, beaming them down, establishing contact, collecting samples, and beaming them back. Each step commits real effects — crew assignments change, transporter logs update, communication channels open. When step four fails, steps one through three have already happened. Accumulated errors tell you what went wrong. They don't undo what already happened.

I explored error recovery for effectful pipelines — steps that commit external state, not just transform data. The answer was compensation: each forward step paired, when possible, with the action that reverses or settles it. Ruuk's saga keyword makes that pairing a first-class part of the declaration.

The Problem: Distributed Commitment

The challenge isn't failure itself. The challenge is partial failure after partial commitment.

A pipeline handles failure cleanly when no step has committed external state. If step three returns a failure outcome, steps one and two produced data that's still local — nothing external has changed. The failure is just an outcome, not a cleanup problem.

But starship operations commit to external systems. Beaming a team down changes their location in the ship's records. Opening a communication channel allocates subspace bandwidth. Logging samples in the science database creates records other systems depend on. These aren't local transformations — they're effects that persist whether the procedure succeeds or not.

When establishContact fails after beamDownTeam succeeded, the team is on the planet surface with no confirmed communication link. The correct response is to beam them back up — but that compensation logic lives in a catch block somewhere, and whether it matches the current beam-down procedure depends on whether someone updated both when the procedure changed.

The standard approach: execute each step, check the result, compensate on failure.

async function conductAwayMission(mission, team, planet) {
  const assigned = await assembleTeam(team, mission);
  if (!assigned.ok) return missionFailed(assigned.error);

  const briefed = await briefTeam(assigned.team, mission);
  if (!briefed.ok) {
    await releaseTeam(assigned.team);
    return missionFailed(briefed.error);
  }

  const landed = await beamDown(briefed.team, planet);
  if (!landed.ok) {
    await debriefTeam(briefed.team);
    await releaseTeam(assigned.team);
    return missionFailed(landed.error);
  }

  const contact = await establishContact(landed.team, planet);
  if (!contact.ok) {
    await beamUp(landed.team);
    await debriefTeam(briefed.team);
    await releaseTeam(assigned.team);
    return missionFailed(contact.error);
  }

  // ... remaining steps
}

This is competent, readable code. It handles each failure and compensates correctly. But the compensation list grows with every step — each failure handler must repeat every previous compensation in reverse order. Add a step between briefTeam and beamDown and every subsequent handler needs updating. The relationship between a forward step and its compensation is implicit: releaseTeam undoes assembleTeam, but you learn that by reading the error handler, not the step declaration. And nothing verifies that every effectful step has a corresponding undo.

Sagas as Declarations

A saga declares steps in order; each step that commits external state declares its compensating operation:

pub saga ConductAwayMission =
    subject mission: Mission<Approved>
    payload team: List<CrewMember>
    payload planet: Planet

    step assembleTeam
        compensate releaseTeam

    step briefTeam
        compensate debriefTeam

    step launchMission
        compensate abortMission
        performs Mission.Approved -> Mission.InProgress

    step beamDown
        compensate beamUp

    step establishContact

    step collectSamples

    step beamUp

    step completeMission
        performs Mission.InProgress -> Mission.Completed

    outcomes =
        | MissionComplete of Mission<Completed>
        | ContactFailed of reason: String
        | TransporterFailure of TransporterError
        | TeamUnavailable of missing: List<String>

Read it as a mission briefing: assemble the team (if it fails later, release them), brief them (debrief if needed), launch the mission, beam them down (beam them back up if something goes wrong), establish contact, collect samples, beam up, and complete the mission with a state transition from InProgress to Completed.

establishContact and collectSamples have no compensate clause. establishContact is a read/verify step — if it fails, there's nothing to undo beyond the earlier compensations. collectSamples is the forward payload of the mission — if sample collection fails, the earlier steps still need unwinding, but "uncollecting" samples isn't a meaningful operation.

The final beamUp step (the planned return, not the compensation) and completeMission also lack compensation — by the time you reach them, the mission has substantively succeeded. completeMission performs the typestate transition from article 5, moving the mission from InProgress to Completed.

The saga's outcomes block declares the saga-level results — the outcomes a caller must handle. When a step fails, the saga unwinds completed steps that declared compensation and surfaces the failure as the appropriate saga outcome.

Automatic Compensation on Failure

If establishContact fails after beamDown, launchMission, briefTeam, and assembleTeam have all succeeded, the saga unwinds automatically in reverse order:

  1. beamUp — compensates beamDown (team is returned to the ship)
  2. abortMission — compensates launchMission (mission is settled as aborted)
  3. debriefTeam — compensates briefTeam (team status is reset)
  4. releaseTeam — compensates assembleTeam (crew assignments are freed)

Last completed, first compensated. The compensation order is the reverse of the execution order — the same principle as unwinding a call stack, but applied to domain operations with real-world effects.

The developer doesn't write this unwind logic. The saga declaration defines it. The compensation for beamDown is beamUp, declared on the same line. The relationship between forward step and undo is visible, explicit, and maintained in one place.

What the Compiler Verifies

Compensation completeness. Every step that calls a mutating operation should declare a compensating action. The compiler warns on steps that modify external state without a compensate clause — not an error, because some mutations genuinely can't be undone (you can't un-send a transmission), but a signal that the developer should make that judgment explicitly.

Operation existence. Each operation named in step and compensate must exist in scope. You can't reference a compensation operation that hasn't been defined.

Performs consistency. If a saga step uses performs, the same validation rules from the typestate article apply: the subject parameter must match the source state, the success outcome must match the target state, no mismatched transitions.

Order guarantees. Compensation executes in reverse execution order by definition. The saga declaration makes this explicit — reading the steps top to bottom tells you the compensation order bottom to top. That leaves less runtime coordination logic to get wrong.

An agent generating a saga gets the same guardrails. The compiler warns on uncompensated mutations whether a human or an agent wrote the declaration — the structural check doesn't depend on who authored the code.

A Richer Example: Ship Repair Workflow

Away missions are dramatic but linear. Ship repairs show how sagas handle more complex workflows with multiple external system interactions:

pub saga RepairCriticalSystem =
    payload system: ShipSystem
    payload damage: DamageReport
    by chief: CrewMember

    step assessDamage

    step allocateRepairTeam
        compensate releaseRepairTeam

    step requisitionParts from supplyStore
        compensate returnParts to supplyStore

    step takeSectionOffline
        compensate bringBackOnline

    step performRepair

    step runDiagnostic

    step certifyRepair
        performs RepairOrder.InProgress -> RepairOrder.Completed

    outcomes =
        | Repaired of RepairOrder<Completed>
        | PartsUnavailable of needed: List<PartId>
        | DiagnosticFailed of failures: List<String>
        | SectionCritical of reason: String

Each forward step reads naturally with its compensation: allocate a team / release the team, requisition parts / return the parts, take the section offline / bring it back online. The parameter roles from article 3 appear in the steps — requisitionParts from supplyStore — making the saga declaration read like a procedure manual.

If runDiagnostic fails after the repair is done, the saga unwinds: bring the section back online, return the parts, release the team. The repair itself may need manual intervention — there's no compensate on performRepair because "un-repairing" isn't a meaningful action. That's a deliberate design choice, visible in the declaration.

Sagas vs. Pipelines

Both sagas and pipelines compose sequential operations. The distinction matters:

A pipeline passes data forward. Each step transforms the previous step's result. N-track pipelines can accumulate multiple errors along the way, but the model assumes no step has committed external state — failures are outcomes to report, not effects to undo.

A saga coordinates effects. Each step may commit external state — a transport, a database write, a resource allocation. If a later step fails, committed state must be undone. The saga manages the compensation stack.

Use a pipeline when steps are pure transformations or when a single system handles rollback automatically (a database transaction). Use a saga when you're coordinating across multiple systems that don't share a transaction boundary — a transporter system, a personnel database, a supply chain, a communication array.

What Sagas Complete

The previous article ended with "What Compounds" — each feature was designed for practical relief and arrived at structural correctness. Sagas extend the pattern one more time. I was exploring error recovery in effectful pipelines, and what started as a way to declare compensation alongside forward steps turned into compiler-verified workflow integrity.

With scattered compensation logic, the answer to "what happens if the transporter fails mid-mission?" is: "our error handlers call the right cleanup functions." That answer depends on every handler being correct, complete, and synchronized with the forward path. With a saga declaration, the answer is the declaration itself: beamDown has compensate beamUp. When someone adds a step, the syntax prompts the question "does this step need compensation?" — and the compiler warns if a mutating step omits one. The forward path and the compensation path live together, so they evolve together.

Series Conclusion

This series has built up a picture of what a compiler could verify about the code agents write and humans review.

Operations and outcomes (article 3) give the compiler visibility into what an operation means — not just its return type, but its domain-specific results. The compiler holds every caller to every outcome.

Projections (article 4) give the compiler control over what each operation can see. Data access boundaries are structural properties of the type, not runtime filters maintained separately.

Typestate (article 5) gives the compiler control over when operations can run. State preconditions are in the type system, not in runtime guards. Invalid transitions are compile errors.

Sagas (this article) give the compiler visibility into multi-step workflows and their compensation. The forward path and the undo path are declared together, maintained together, and verified together.

Each feature stands on its own, but the compounding is the point. An operation with typed outcomes, scoped to a projected data view, guarded by typestate, coordinated within a saga — that's a level of structural verification that testing and code review alone don't reliably achieve. The compiler holds invariants that humans struggle to keep in working memory. In an era where agents write code at volume and humans review it under time pressure, that starts to look less like a nice-to-have and more like part of the architecture of trust.

Ruuk is in alpha. The syntax will continue to evolve and the implementation has a long road ahead. But the design criteria — compiler-visible domain semantics, structural enforcement over behavioral convention, progressive development that doesn't sacrifice rigor — are the properties I think the agentic era makes newly important. If these ideas resonate, give ruuk a spin; follow along on GitHub and weigh in on the discussions. The best languages get shaped by the people who care about the problems they solve.

This article was created with the help of AI