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

推荐订阅源

大猫的无限游戏
大猫的无限游戏
博客园 - 【当耐特】
Cloudbric
Cloudbric
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Attack and Defense Labs
Attack and Defense Labs
爱范儿
爱范儿
The Cloudflare Blog
腾讯CDC
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
云风的 BLOG
云风的 BLOG
Recent Announcements
Recent Announcements
C
Check Point Blog
Schneier on Security
Schneier on Security
S
Schneier on Security
J
Java Code Geeks
B
Blog RSS Feed
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
Stack Overflow Blog
Stack Overflow Blog
博客园_首页
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
A
About on SuperTechFans
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Google DeepMind News
Google DeepMind News
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
A
Arctic Wolf
S
Secure Thoughts
P
Palo Alto Networks Blog
The Last Watchdog
The Last Watchdog
SecWiki News
SecWiki News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 三生石上(FineUI控件)
D
Darknet – Hacking Tools, Hacker News & Cyber Security
量子位
U
Unit 42
I
InfoQ
D
DataBreaches.Net
P
Privacy International News Feed
T
Troy Hunt's Blog
博客园 - 叶小钗
T
Threatpost
博客园 - Franky
K
Kaspersky official blog
Hugging Face - Blog
Hugging Face - Blog
IT之家
IT之家
www.infosecurity-magazine.com
www.infosecurity-magazine.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cisco Blogs

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
Three majors, two mistakes: designing a pause API for a Turing-machine interpreter
Ruslan Gilmu · 2026-05-19 · via DEV Community

Screenshot of demo.machines.mellonis.ru running a Turing machine

I spent the last two weeks shipping four breaking major versions of @turing-machine-js/machine — v3, v4, v5, v6 — and the most interesting part wasn’t any single feature. It was watching the same API surface (a pause/breakpoint hook on the run loop) get redesigned twice in three versions, each time because the previous shape exposed something it shouldn’t have.

This post is the post-mortem. If you’re designing pause/step/breakpoint APIs for a generator-loop interpreter, scheduler, or DSL runtime, the mistakes I made are easy to make and worth seeing in someone else’s code first.

The setup

The engine runs a Turing machine. Internally it's a generator: each yield corresponds to one step (one transition firing on one tape symbol). The driver loop looks roughly like this:

async function run({ initialState, onStep }) {
  for (const m of runStepByStep({ initialState })) {
    await onStep?.(m);
    if (m.state.isHalt) return;
  }
}

Enter fullscreen mode Exit fullscreen mode

MachineState (m) is a snapshot per iteration: the state about to execute, the current and next symbols, the moves the heads will make. A consumer (a logger, a UI, a test) can hang off onStep to observe.

What I wanted to add in v4 was a way to pause execution at chosen points — like a breakpoint in a debugger. Any State should be able to carry a debug config:

myState.debug = { before: [symA], after: [symA] };

Enter fullscreen mode Exit fullscreen mode

...and the run loop should give the consumer a chance to inspect the machine and decide when to resume.

v4: ship it

The v4 shape was straightforward. I made run() async, added an onDebugBreak hook, and routed it like this:

await machine.run({
  initialState,
  onStep: (m) => { /* every step */ },
  onDebugBreak: async (m) => {
    if (m.debugBreak?.before) console.log('before:', m.state.name);
    if (m.debugBreak?.after)  console.log('after:',  m.state.name);
    // hold here until promise resolves
  },
});

Enter fullscreen mode Exit fullscreen mode

The per-state debug field is mutable. state.debug = { before: true } sets a pre-step break; before: [symA] filters by what the head is reading. The hook is awaited, so any consumer can implement “freeze until the human clicks Resume” by simply not resolving the promise.

I also wanted breakpoints on halt:

haltState.debug = { before: true };  // pause before exit

Enter fullscreen mode Exit fullscreen mode

That worked. Symbol-list filters on haltState were silent no-ops, because haltState has no head symbol to filter by. Fine, I thought — be permissive in input, the wildcard true is the one that matters.

Two things in this design were wrong. I didn’t notice either until I started writing docs and a UI on top.

Mistake 1: the hook describes the engine, not the consumer

onDebugBreak reads, on paper, like a perfectly reasonable name. The engine fires a “debug break”; you hook into it.

But what does the consumer do in that hook? They pause. They inspect. They wait for input. They resume. The hook isn’t really notifying you that a thing happened — it’s offering you a cooperation point.

The name onDebugBreak carries one specific framing: “debugging”. But the same hook is exactly what you want for a step-through visualization, a slow-motion playback control, an animation tween, a “press space to advance” UI. None of those are debugging. They’re all pausing.

This sounds like a small thing. It’s not, because once consumers see the name they design around it: their code path is called handleDebugBreak, their state machine has a debugging boolean, their UI button says “Stop debugging”. The name leaks into every consumer’s vocabulary.

I wrote an RFC (turing-machine-js#109) and renamed it in v5. Hard rename, no deprecation alias:

await machine.run({
    initialState,
-   onDebugBreak: (m) => { ... },
+   onPause: (m) => { ... },
  });

Enter fullscreen mode Exit fullscreen mode

The payload field m.debugBreak stayed — it’s metadata describing why this yield fired ({ before: true } or { after: true }), and “break” works fine inside the payload. But the hook name is the consumer’s contract: onPause describes what they do, not what the engine does.

Rule for next time: when naming a hook, name the consumer’s verb, not the engine’s event. Hooks are cooperation points, not event listeners.

Restriction: haltState.debug.after is nonsense

The second v4 mistake hid under an instinct to be permissive in input. haltState.debug.after accepted writes silently. It just didn’t fire.

Why? Because the after semantics in this engine mean “fire on the iteration after the one where the filter matched”. That makes perfect sense for normal states — you transition out of state K, the next iter (K+1) runs, and at that yield the consumer is told “by the way, K’s after-filter fired last step”. But halt is terminal. There is no K+1 after halt. The after event has nothing to anchor on.

I’d silently swallowed haltState.debug.after = true in v4. Worse, I’d let { before: true, after: true } through — half the assignment was meaningful, half was a no-op, and nothing told the consumer.

In v5 I made both throw at write time (turing-machine-js#108 part 2):

- haltState.debug = { before: true, after: true }; // v5: throws — 'after' on halt has nothing to anchor on
+ haltState.debug = { before: true };

Enter fullscreen mode Exit fullscreen mode

Rule for next time: if a configuration has no semantics, throw early. Silently no-op input looks user-friendly until a consumer spends an afternoon wondering why their UI doesn’t fire on halt. “Be permissive” is a false economy when permissiveness silences a real mistake.

While I was in there, I also fixed a related bug: the halting iter’s own after filter wasn’t firing either (turing-machine-js#108 part 1). The driver loop exited as soon as state.isHalt became true — and the after-event from the previous iter, which would normally fire on this final yield, got dropped on the floor. v5 added a post-loop drain to fire it.

I’ll come back to this.

And one v5 bonus: a master switch on run() for the whole pause system.

await machine.run({ initialState, debug: false, onPause: ... });

Enter fullscreen mode Exit fullscreen mode

When false, all pause-fires are suppressed regardless of what state.debug says across the graph (turing-machine-js#106). You can A/B “debug mode” without rewriting the graph or clearing every state.debug field. The flag dispatches onPause; the underlying m.debugBreak payload field still populates on the generator’s yields (it’s a property of the iteration, not of how run() chose to surface it).

Mistake 2: the substitution dance

This is the one I’m proudest to have caught, because the code worked, the tests passed, and the docs were correct. The shape was just wrong.

In v4 and v5, after K (the after-fire for iteration K) actually fired on iteration K+1’s yield. The driver loop carried a pendingAfterFromPrev flag across yields, and on the next iter’s yield it dispatched the after hook first, then the before hook, then onStep. The hook for K’s after-fire had to see K’s state — not K+1’s. So I substituted the previous yield’s snapshot into the m.state field of K+1’s yield, just for the duration of the after dispatch.

This worked. But it had three knock-on effects:

  1. The substitution leaked. Consumers wanted access to the un-substituted, “real” iteration state (the one the step hook saw) — see turing-machine-js#107. Some users were reading raw MachineState.debugBreak from runStepByStep directly and were surprised that m.state referred to the iter on which the after-fired-from, not the iter that produced it.

  2. The halt case needed a special path. As mentioned: the halting iter’s own after fires after the loop exits. v5 added a post-loop drain to handle it. That drain is its own code path, with its own substitution, separate from the in-loop dispatch.

  3. The generator’s return type widened. Because the post-loop drain needed to return a final yielded value out of the iterator, runStepByStep’s return type became Generator<MachineState, MachineState | null> — a yield type and a return type. The canonical for..of consumer doesn’t see the return value, but anyone reading the type signature now had to understand why there were two slots.

All of this came from one design choice: dispatching after K on iter K+1’s yield instead of on iter K’s own yield.

In v6 I collapsed it (turing-machine-js#119). The per-iter lifecycle is now plainly before → step → after. The after fire rides on the same yield as the iter that produced it. No substitution. No pendingAfterFromPrev. No post-loop drain. The generator return type narrows back to Generator<MachineState>.

// v6
await machine.run({
  initialState,
  onStep: (m) => { /* unchanged */ },
  onPause: (m) => {
    if (m.debugBreak?.before) console.log('before:', m.state.name);
    if (m.debugBreak?.after)  console.log('after:',  m.state.name);
  },
});

Enter fullscreen mode Exit fullscreen mode

The dispatch order across hooks changed (any test asserting pause(after K-1) → pause(before K) → step(K) had to flip to pause(before K) → step(K) → pause(after K)), but the set of dispatched calls and per-iter semantics are unchanged. Consumers treating the hooks as independent observers see no change at all (turing-machine-js#107 — “expose un-substituted state” — disappeared as a problem: there’s no substitution to expose around).

Rule for next time: if your hook payload requires substituting state from a different iteration than the one that’s yielding, the dispatch sequence is wrong. Lifecycle phases (before, step, after) belong on the iter they describe. The substitution wasn’t a clever trick — it was a leak telling me the events were on the wrong tick.

What I’d carry to the next pause/breakpoint API

  1. Name hooks for the consumer's verb, not the engine's event. onPause, not onDebugBreak. onProgress, not onChunkReceived. The name leaks into every consumer's mental model — pick the framing you want them to inherit.

  2. Throw on impossible configurations early. “Permissive in input” is fine for shapes that might be meaningful; it's a trap for shapes that can't be. haltState.debug.after = true had no possible semantics. Silently swallowing it cost an afternoon to one user before I noticed.

  3. Per-iter phases belong on the iter they describe. If you're tempted to dispatch after K on K+1's yield, ask what payload you have to substitute to make it look right. The substitution is the signal.

  4. A master switch is cheap and worth it. A boolean on the entry point to flip the whole feature off (without rewriting graph-level flags) makes the feature safe to ship to production and toggle in tests. run({ debug: false }) was one of the smaller v5 adds and one of the most-used by downstream consumers.

  5. Breaking changes are worth the major bump. Three majors in two weeks sounds expensive. It isn't, if the lockstep downstream is one repo (in my case @post-machine-js/machine, which bumped its peer dep ^4.0.0^6.0.0 and renamed its own internal __onDebugBreak__onPause in step). API ergonomics compound across every consumer for the life of the library — pay the migration cost while the API is young.

The v6 shape is the one I should have shipped in v4. I didn't have the vocabulary then — I was thinking “expose the engine's events” instead of “design the consumer's contract”. Writing the docs is what surfaced both mistakes. There's a meta-lesson in there about how docs are the cheapest design review you can run, but that's another post.


Code: turing-machine-js (engine) and post-machine-js (a Post machine built on top, kept in lockstep). The interactive demo at demo.machines.mellonis.ru consumes the v6 onPause hook for its Step / Run / Stop controls.