Köy sulara gömüldü, minarenin ucu görünür kaldı

Kars’ın Sarıkamış ilçesinde Karakurt Barajı’nın su seviyesi yüzde 100 doluluğa ulaşınca eski köy tamamen sular altında kaldı. Geriye yalnızca caminin minaresinin ucu kaldı. Dronla çekim yapılırken yaşlı bir vatandaşın cihaza taş fırlatması dikkat çekti.

Telegram Just Opened the Door for Agent-to-Agent Communication. Here’s Why That’s Not Enough.

On May 7, 2026, Telegram became the first billion-user messaging platform to enable native bot-to-bot communication. One bot can now send a private message directly to another bot by referencing its @username. No intermediary server. No custom routing layer.

For developers building multi-agent systems, this sounds like the answer. It isn’t.

What Telegram Actually Shipped

The update is real and significant. Telegram’s Bot API now supports direct agent-to-agent messaging with a mutual opt-in requirement. Both bots must explicitly enable the mode before they can exchange messages.

The use cases Telegram outlined are practical: a code-review bot delegating to a specialist bot, enterprise booking agents coordinating sub-tasks, multi-step AI workflows executing end-to-end without human relay.

With over 10 million bots already on the platform, those bots can now form networks. A research agent can offload data retrieval to a specialist bot and receive results back, all within Telegram’s native infrastructure.

This is progress. But it comes with three structural limitations that matter for production multi-agent systems.

Limitation 1: Platform Lock-In

Telegram’s bot-to-bot communication works inside Telegram. Your Claude-powered agent on Telegram can talk to your GPT-powered agent on Telegram. But what about the agent running on Slack? The one deployed on AWS Bedrock? The custom Python bot on your own infrastructure?

The moment your agent network spans more than one platform, Telegram’s native communication becomes one channel among many, not the universal layer.

// With rosud-call: platform-independent agent messaging
import { RosudCall } from 'rosud-call';

const agent = new RosudCall({
  agentId: 'procurement-bot-v3',
  token: process.env.ROSUD_TOKEN
});

// This works regardless of where the other agent lives
// Telegram, Slack, AWS, your own server - same API
await agent.send('inventory-checker', {
  type: 'stock-query',
  sku: 'WIDGET-2000',
  urgency: 'high'
});

// Subscribe to responses from any agent, any platform
agent.on('message', (msg) => {
  if (msg.from === 'inventory-checker') {
    console.log(`Stock level: ${msg.payload.quantity}`);
  }
});

Limitation 2: Security Coverage Gap

The same week Telegram shipped bot-to-bot communication, a Georgia Tech study found that the best existing security framework for multi-agent systems covers only 65.3% of identified threat categories. Non-determinism and data leakage were the two most under-addressed risk domains.

A separate study from the Cooperative AI Foundation identified three structural failure modes specific to multi-agent architectures: miscoordination, conflict, and collusion. One compromised agent can cascade through trust relationships across an entire network.

Telegram’s mutual opt-in is a start. But opt-in doesn’t solve:

  • Message integrity verification between agents
  • Rate limiting per agent pair
  • Payload schema validation
  • Audit trails for compliance
  • Credential scoping per conversation
// rosud-call: built-in security primitives
const secureChannel = new RosudCall({
  agentId: 'finance-bot',
  token: process.env.ROSUD_TOKEN,
  security: {
    verifyPeer: true,           // Cryptographic identity check
    maxMessagesPerMinute: 100,  // Rate limiting per peer
    schemaValidation: true,     // Reject malformed payloads
    auditLog: true              // Every message logged
  }
});

// Only accept messages from verified agents
secureChannel.on('message', (msg) => {
  // msg.verified === true means cryptographic proof of sender
  // msg.auditId links to immutable log entry
  if (!msg.verified) return;
  processPaymentRequest(msg.payload);
});

Limitation 3: No Observability by Default

Telegram mentions that users “who choose to watch can observe bot-to-bot conversations.” That’s consumer-grade visibility. Enterprise multi-agent systems need:

  • Message latency metrics per agent pair
  • Failure rate tracking with automatic retry
  • Dead letter queues for undeliverable messages
  • Circuit breakers when downstream agents are unhealthy
  • Distributed tracing across multi-hop agent chains

These aren’t nice-to-haves. When your procurement agent delegates to an inventory agent, which delegates to a supplier agent, and the chain fails silently at hop three, you need infrastructure-level observability to diagnose it.

// rosud-call: observability built into the SDK
const channel = new RosudCall({
  agentId: 'orchestrator',
  token: process.env.ROSUD_TOKEN,
  observability: {
    tracing: true,        // Distributed trace IDs across hops
    metrics: true,        // Latency, throughput, error rates
    deadLetterQueue: true // Capture failed deliveries
  }
});

// Send with delivery guarantees
const result = await channel.send('supplier-agent', payload, {
  timeout: 5000,          // 5s deadline
  retries: 3,             // Automatic retry on failure
  circuitBreaker: {
    threshold: 5,         // Open after 5 consecutive failures
    resetAfter: 30000     // Try again after 30s
  }
});

if (!result.delivered) {
  // Message is in dead letter queue
  // Alert, fallback, or escalate
  await escalateToHuman(result.deadLetterId);
}

The Real Question

Telegram opening bot-to-bot communication validates the market. Over 10 million bots can now form networks on a billion-user platform. That’s real.

But production multi-agent systems don’t live inside a single messaging app. They span platforms, clouds, and custom infrastructure. They need security primitives that go beyond opt-in. They need observability that goes beyond “users can watch.”

The protocol layer (A2A, MCP) tells agents how to discover each other. The platform layer (Telegram, Slack) gives them a place to exist. The SDK layer makes sure messages actually arrive, securely, with proof.

That’s what rosud-call does. One npm install. Platform-independent. Security and observability built in. No lock-in to any single messaging platform.

npm install rosud-call

Telegram opened the door. Now build something that works everywhere the door leads.

Kavin Kim builds payment and communication infrastructure for AI agents at Rosud. Previously: 15 years designing global payment systems processing cross-border transactions across 172 countries.

Agentic Coding in 2026: Why AI Copilots Are Being Replaced by AI Orchestration

For the past two years, “AI-assisted development” meant one thing: a smart autocomplete that finished your lines and suggested function signatures. GitHub Copilot, Tabnine, Codeium — great tools. But they were fundamentally reactive. You still drove every step.

That model is being replaced — fast.

In 2026, the leading engineering teams aren’t using AI to write faster. They’re using AI agents to think at a different altitude entirely.

What’s Actually Changed

The numbers tell the story clearly:

  • 84% of developers are using or actively planning to use AI tools in their development workflow (2026 survey data)
  • 46% of all code on GitHub is now AI-generated, with Gartner projecting 60% by year-end
  • Multi-agent system inquiries surged 1,445% from Q1 2024 to Q2 2025 (Gartner), representing a fundamental shift in how teams think about automation

The key shift: from copilots (reactive, single-step assistants) to agents (autonomous, multi-step executors that research, write, test, and iterate with minimal human intervention per cycle).

An agentic coding workflow looks something like this:

# Traditional AI-assisted workflow
developer: "write me a function that validates email"
copilot: [suggests function body]
developer: reviews, accepts, moves on

# Agentic workflow
developer: "Implement the full user onboarding flow — validation, welcome email trigger, analytics events, and tests."
agent: [reads codebase → writes feature → runs tests → fixes failures → opens PR with description]
developer: reviews PR, merges or redirects

The human is still essential, but operating at the level of intent and review, not keystroke-by-keystroke implementation.

The New Developer Skill: Orchestration

This is where it gets interesting (and where many teams are struggling).

Writing code well is no longer the differentiator. The new leverage point is AI orchestration: the ability to decompose a complex outcome into well-defined agent tasks, validate outputs at the right checkpoints, and catch the specific failure modes that agentic systems introduce.

And there are real failure modes. Gartner has projected a 2,500% increase in generative AI software defects in 2026. The teams that win aren’t just shipping faster, they’re building governance layers: automated QA pipelines, output validators, and structured review protocols that catch AI-generated errors before they reach production.

This is the area where engineering maturity matters most right now.

What “Agentic QA” Actually Looks Like

One pattern we’ve seen work well in production — and that we’ve refined through 300+ shipped products at Ailoitte — is pairing agentic code generation with an agentic QA layer.

Instead of human testers running test cases after the fact, the QA pipeline runs in parallel with the build:

  1. Intent capture — the engineer specifies what “done” looks like (acceptance criteria, edge cases, security boundaries)
  2. Agent build — code is generated and iterated against the spec
  3. Agentic QA sweep — a separate agent family runs OWASP checks, regression tests, and functional validation
  4. Diff review — a senior engineer reviews the validated diff, not the raw code

The result is dramatically compressed review time and fewer production incidents. You can read more about how Ailoitte’s Agentic QA Pipeline works in practice.

The Governance Problem No One’s Talking About

Most of the 2026 agentic coding conversation focuses on speed. Less discussion happens around governance, and this is where serious engineering teams differentiate themselves.

Key governance questions for teams adopting agentic workflows:

  • Access scope: What systems can agents read/write to? (Incredibuld’s new Islo sandbox addresses exactly this problem)
  • Audit trails: Can you trace every agent action for compliance or debugging?
  • Model switching: If your primary coding model changes or regresses on a task type, can you swap it without rewriting workflows?
  • Cost attribution: Who on the team is spending what on model inference, and is it mapped to business outcomes?

The teams investing in these questions now will have a massive structural advantage in 12–18 months.

Where to Start

If your team is early in this transition, a few practical starting points:

  1. Run a bounded pilot: Pick one internal tool or non-critical feature and run a fully agentic sprint. Measure actual time vs estimate.
  2. Instrument the QA layer first: Before scaling agentic generation, build the validation layer. You need the safety net before the speed.
  3. Separate planning from implementation agents: Models that plan well often don’t implement well (and vice versa). Multi-model workflows outperform single-model all-in approaches.
  4. Define “done” more precisely than you ever have: Agentic systems are only as good as the acceptance criteria they’re given. Garbage spec in, garbage code out.

The shift from Copilot to orchestration isn’t a productivity upgrade. It’s a fundamental change in what it means to be a senior engineer. The teams that are building this muscle now, in real production contexts, not just demos, are compounding an advantage that will be very hard to close later.

What’s your team’s current state on this? Running fully agentic sprints, or still in the copilot-assisted phase? Would love to hear what’s working (and what isn’t) in the comments.

Ailoitte is an AI-native product engineering company that has shipped 300+ products across 21 countries using AI Velocity Pod methodology — small elite teams paired with governed agentic workflows. Learn more at ailoitte.com.

We Scored 14,800+ MCP Servers on Behavioral Trust. Here’s What We Found.

The MCP ecosystem has a trust problem — and scanning source code won’t fix it

The Model Context Protocol ecosystem is growing fast. Thousands of MCP servers now offer tools that AI agents call autonomously — executing code, querying databases, moving money, managing infrastructure. Agents are making decisions on behalf of humans, and those decisions depend on servers they’ve never met.

Recently, a well-circulated analysis scanned roughly 1,800 MCP servers and found security issues in a significant percentage of them. That work was valuable. Static analysis catches real bugs: injection vulnerabilities, missing input validation, insecure defaults.

But here’s the question nobody asked: what happens after deployment?

A server can pass every static check and still behave terribly in production — dropping requests, responding with garbage after midnight, degrading quietly over weeks until an agent makes a costly mistake. Static analysis is a snapshot. Production is a film.

We built Dominion Observatory to watch the film.

Behavioral trust scoring: what it is and why it matters

Dominion Observatory provides behavioral trust scores for 14,800+ MCP servers — nearly 8x the coverage of the largest published static analysis. But coverage isn’t the point. The methodology is.

Instead of reading source code, Dominion scores servers based on how they actually behave at runtime:

  • Success rates — Does the server reliably return valid responses, or does it fail silently?
  • Latency profiles — Is response time consistent, or does it spike unpredictably?
  • Uptime patterns — Is the server available when agents need it, or does it vanish during peak hours?
  • Interaction history — How does the server perform across thousands of real tool calls over time?
  • Anomaly detection — Has the server’s behavior suddenly changed in a way that suggests compromise, misconfiguration, or degradation?

A trust score isn’t a binary pass/fail. It’s a continuous signal that reflects a server’s operational reputation — built from observed behavior, not assumed intent.

What behavioral scoring actually reveals

When you shift from “does this code look safe?” to “does this server behave reliably?”, you start seeing patterns that static analysis simply cannot detect.

Degradation over time. A server that worked perfectly three months ago might now be timing out on 30% of requests. No code changed — maybe the underlying infrastructure shifted, maybe a dependency started throttling, maybe the maintainer moved on. Static analysis sees the same clean code. Behavioral scoring sees the decay.

Inconsistent reliability across tools. A single MCP server might expose five tools where four perform well and one is essentially broken. Behavioral scoring operates at the granularity of individual tool interactions, not just the server as a whole.

Anomalous behavior shifts. A server that suddenly starts returning responses 10x faster than its historical baseline might sound like good news — or it might mean it’s returning cached garbage instead of computing real results. Anomaly detection flags deviations in both directions.

Availability patterns. Some servers are rock-solid during US business hours and unreachable at other times. For a global agent economy, that’s a reliability concern that only shows up through continuous observation.

These aren’t theoretical scenarios. They’re the kinds of signals that emerge when you instrument trust at the behavioral layer.

From scoring to settlement: trust as infrastructure

Dominion Observatory isn’t a dashboard you check once. It’s infrastructure that agents query in real time, at the moment of decision.

The system is itself an MCP server (available via Streamable HTTP at https://dominion-observatory.sgdata.workers.dev/mcp), which means any MCP-capable agent can call it natively. The core tools:

  • get_trust_score — Retrieve the behavioral trust score for any MCP server before calling it
  • detect_anomalies — Check whether a server is currently exhibiting unusual behavior
  • get_leaderboard — See which servers rank highest for reliability in a given category
  • get_ecosystem_stats — Understand the overall health of the MCP ecosystem
  • report_tool_outcome — Contribute your own interaction data back to the scoring engine

The most consequential integration point is the beforeSettle hook. In agent-to-agent payment flows — where one agent pays another for a service rendered via MCP — the trust score can gate whether settlement proceeds. If a server’s behavioral trust has dropped below a threshold, the payment holds. This turns trust from a nice-to-have metric into an economic primitive.

Think of it as a credit score for MCP servers. Not based on who they say they are, but on what they’ve actually done.

Static and behavioral analysis are complements, not competitors

To be clear: static analysis is important. You should absolutely scan MCP servers for injection flaws, validate their input handling, and audit their permission models. Tools that do this well are doing necessary work.

But static analysis answers the question: “Could this server misbehave?”

Behavioral scoring answers the question: “Is this server misbehaving?”

The first is a security audit. The second is an operational reputation system. A mature MCP ecosystem needs both — just as the traditional web needs both code review and uptime monitoring.

The difference becomes critical as the agent economy scales. When thousands of agents are autonomously selecting which MCP servers to call, making payments, and chaining tool calls across multiple servers, you need trust signals that operate at runtime speed and reflect current reality. You can’t re-audit source code on every request. You can query a behavioral trust score in milliseconds.

The agent economy needs accountability infrastructure

We’re at an inflection point. MCP adoption is accelerating, and the servers agents depend on are increasingly operated by unknown third parties. The agent economy will either develop robust trust infrastructure, or it will learn expensive lessons about what happens when autonomous systems make decisions without accountability.

Dominion Observatory is our contribution to the first outcome. It’s open source, it’s composable, and it’s designed to be infrastructure that other systems build on — not a walled garden.

Try it now

Query it directly. Point any MCP client at https://dominion-observatory.sgdata.workers.dev/mcp using Streamable HTTP transport. Call get_ecosystem_stats to see the current state of the ecosystem, or get_trust_score for any server you’re curious about.

Contribute data. The scoring engine gets better with more interaction data. Use report_tool_outcome to feed back your own observations about MCP server behavior. More data means more accurate trust signals for everyone.

Star the repo. The engine is open source at github.com/vdineshk/daee-engine. Issues, PRs, and ideas are welcome.

Build on it. If you’re building agent infrastructure — orchestration frameworks, payment rails, marketplace platforms — behavioral trust scoring is a building block. Integrate it. Extend it. Make agents smarter about who they trust.

The MCP ecosystem is too important to fly blind. Let’s build the accountability layer together.

Why Passing Tests Are Sometimes the Most Dangerous Thing in Your Pipeline

There is a specific kind of confidence that comes from watching a CI pipeline run green. You pushed a change, the tests ran, everything passed, and now you are deploying to production feeling reasonably certain that nothing broke.

That confidence is earned most of the time. And sometimes it is the most expensive thing in your entire pipeline.

I am not talking about flaky tests or poorly written assertions. I am talking about something more subtle and more dangerous: tests that are working exactly as designed, passing exactly as expected, and validating something that stopped being true months ago.

This is the part of automated testing that most pipeline conversations skip over. Everyone talks about coverage percentages, execution speed, and CI integration. Nobody talks about what happens when your tests are structurally correct but fundamentally disconnected from how your system actually behaves.

The Confidence Problem

Software deployment has gotten fast. Most teams I talk to are shipping multiple times per day, sometimes dozens of times. The pipeline is the gatekeeper between a developer’s change and production, and a passing test suite is what opens that gate.

That relationship between passing tests and deployment confidence is supposed to be simple. Tests pass means the system works. Tests fail means something broke. Ship when green, hold when red.

Except the relationship only holds when tests are actually checking the right things under the right conditions. When they are not, a green pipeline is not evidence that your system works. It is evidence that your system matches what your tests expect, which is a very different claim.

What Black Box Testing Gets Right and Where It Goes Wrong

Black box testing is one of the most honest approaches to automated testing precisely because it validates behavior from the outside. You interact with the system through its external interfaces, provide inputs, observe outputs, and validate results without any knowledge of what is happening inside. No reaching into implementation details. No testing internal state. Just the system behaving the way real users and downstream services would experience it.

This is a significant strength. A well-designed black box testing suite tells you something genuinely useful: the system’s external behavior is what you expect. That is the thing that actually matters to users, and it is the thing that most other testing approaches approximate rather than test directly.

The problem is not with the approach. The problem is with what happens to the expectations those tests are validating over time.

The Drift Nobody Notices

In a microservices environment, services evolve independently. A downstream API gets a new field in its response schema. An authentication service changes its error handling behavior. A data processing service starts returning slightly different output shapes under certain conditions.

None of these changes necessarily cause failures in your black box test suite. Because the tests are not checking against what the downstream services currently do. They are checking against what the mocks say the downstream services do. And those mocks were written when the tests were written, by developers who were accurately representing the service behavior at that moment in time.

Six months later, the service has changed. The mock has not. The tests keep passing. The behavior they are validating no longer reflects production reality.

This is mock drift, and it is the specific failure mode that makes passing tests dangerous. The pipeline is green. The deployment goes through. The production incident happens not because something broke in the traditional sense but because the system changed in ways that the test suite had no mechanism to detect.

The Most Dangerous Tests Are the Ones You Trust Most

Here is the counterintuitive part: the tests most likely to cause this problem are not the ones you are worried about. They are the ones you trust implicitly.

A test that fails intermittently is annoying but visible. You investigate it, you fix it, you quarantine it. Its unreliability is known.

A test that has passed reliably for eighteen months is a different matter. You have stopped questioning it. It runs green in every environment, on every branch, in every pipeline stage. It is part of the bedrock of your automated testing confidence.

And if that test was written against a mock that drifted from reality a year ago, it has been telling you something false for a very long time. The longer it has been passing, the more confident you have been, and the more dangerous that confidence has become.

What Fixes This

The fix is not to distrust your tests. It is to change where your test inputs come from.

The most reliable source of truth for how a system behaves is not a specification written by a developer and not a mock based on developer assumptions. It is the actual traffic flowing through the system in production. When black box tests are generated from real API interactions rather than hand-written specifications, the tests stay grounded in current system behavior rather than historical assumptions.

When a downstream service changes its response schema, the next round of traffic capture reflects that change automatically. The tests update because the source of truth updated, not because a developer remembered to review and update a mock file.

Keploy takes this approach to automated testing: capturing real HTTP traffic and generating black box tests and dependency mocks from actual production interactions. The coverage stays current because it is derived from what the system actually does rather than what someone thought it would do when writing the test.

This is not a magic solution to all testing problems. Tests sourced from real traffic still need thoughtful review and maintenance. But they address the specific failure mode that makes passing tests dangerous: the drift between what tests expect and what systems actually do.

Before Your Next Software Deployment

The next time you watch a CI pipeline run green before a software deployment, it is worth asking a specific question: when were these tests last checked against real system behavior?

Not “when did they last pass” — that is the wrong question. Tests pass all the time while validating outdated behavior. The right question is when the expectations embedded in your test suite were last verified against how the system currently behaves in production.

If the answer is never, or a long time ago, the confidence that green pipeline is giving you may be less earned than it feels. That does not mean your system is broken. It means you do not actually know whether it is or not, which is a different and more uncomfortable problem.

A passing test suite built on current, accurate expectations is one of the most valuable things an engineering team can have. A passing test suite built on drifted assumptions is something else entirely: it is confidence you have not paid for, and like most things you have not paid for, there is usually a bill coming.

Drow.js: A Practical Look at the Tiny Web Components Library

Drow.js: A Practical Look at the Tiny Web Components Library

If you’ve built a Web Component before, you’ve probably written something like this:

class MyComp extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    // boilerplate setup
  }

  // lifecycle methods
}

customElements.define('my-comp', MyComp);

It works, but there’s a lot of ceremony for something relatively simple. Drow.js strips away that ceremony. Instead of class inheritance and lifecycle methods, you define your components as plain JavaScript objects. The library handles the rest.

And it’s tiny—under 1kb gzipped with zero dependencies.

What Is Drow.js?

Drow is a minimalist wrapper around the Web Components API. It takes the standard Custom Elements spec and makes it feel less like writing framework code and more like configuring objects.

The core idea: Components are objects, not classes.

The Object-Based Approach

Here’s how you define a Drow component:

var myComp = {
    "name": "my-comp",
    "props": ['title', 'link'],
    "template": `<div>
        <div>Title: {{title}}</div>
        <div>Link: {{link}}</div>
    </div>`
}

Drow.register(myComp);

Then use it in HTML like any Web Component:

<my-comp title="Great Name" link="https://something.com"></my-comp>

Notice a few things:

  • name: Must be dash-separated (Web Components requirement)
  • props: Array of attribute names that can be set on the element
  • template: HTML with handlebars-style {{variable}} placeholders

That’s the bare minimum. Let’s look at what else you can add.

Building a Real Component

Here’s an example component from the documentation with some practical features:

var config = {
    "name": "my-comp",
    "props": ['prop1', 'prop2'],
    "css": "b { color: blue; }",
    "template": `<div>
        <div>Every time you click the timestamp updates.</div>
        <b>Click for the timestamp</b>
        <div>{{prop1}}</div>
    </div>`,
    "init": function(config) {
        let prop1 = this.getAttribute('prop1') || "";
        const comp = this.getComp();

        comp.addEventListener('click', e => {
            comp.querySelector("b").innerHTML = new Date();
        });
    },
    "watch": function(attribute) {
        if (attribute.name == 'prop1') {
            attribute.comp.querySelector('b').innerHTML = `Hello, ${attribute.newValue}`;
        }
    }
}

Drow.register(config);

Now we’ve added three new features:

1. CSS Scoping

The css field contains component-scoped styles. They won’t leak out and external styles won’t leak in. Shadow DOM encapsulation handled automatically.

2. The init Hook

The init function runs when the component is created. It gets access to the component via this.getComp() and can:

  • Set up event listeners
  • Query the template
  • Read initial prop values with this.getAttribute()

This is where you add interactivity without the lifecycle method overhead.

3. The watch Hook

The watch function triggers whenever an attribute changes. It receives an object with:

  • name: The attribute that changed
  • newValue: The new value
  • comp: A reference to the component

This is how you react to updates from outside the component—essentially a property observer.

A Practical Example: Timestamp Component

Let’s build something concrete. A component that displays when it was last clicked:

var timestampComp = {
    "name": "last-click",
    "props": ['label'],
    "css": `
        .container {
            padding: 16px;
            border: 1px solid #ddd;
            border-radius: 4px;
            background: #f5f5f5;
        }
        .timestamp {
            font-weight: bold;
            color: #0066cc;
            cursor: pointer;
            padding: 8px;
            user-select: none;
        }
        .timestamp:hover {
            background: #e8e8e8;
            border-radius: 2px;
        }
    `,
    "template": `
        <div class="container">
            <p>{{label}}</p>
            <div class="timestamp">Click here for current time</div>
            <p id="output"></p>
        </div>
    `,
    "init": function(config) {
        const comp = this.getComp();
        const timestamp = comp.querySelector('.timestamp');
        const output = comp.querySelector('#output');

        timestamp.addEventListener('click', () => {
            output.textContent = new Date().toLocaleString();
        });
    }
}

Drow.register(timestampComp);

Use it:

<last-click label="Last clicked at:"></last-click>

That’s it. No build step, no complicated setup. Just define, register, and use.

How the Templating Works

Drow uses simple mustache/handlebars style variable replacement. Anything in {{}} gets replaced with the prop value:

"template": `
    <h1>{{title}}</h1>
    <p>{{description}}</p>
`

The variables are applied by prop name from your attributes:

<my-card 
    title="Hello World" 
    description="A simple component">
</my-card>

No special syntax, no directives, no magical binding. Just string interpolation based on props.

Reacting to Changes with watch

The watch hook is useful for when attributes get updated dynamically:

var greeting = {
    "name": "greeting",
    "props": ['name'],
    "template": `<h1>Hello, <span class="user"></span></h1>`,
    "watch": function(attribute) {
        if (attribute.name == 'name') {
            attribute.comp.querySelector('.user').textContent = attribute.newValue;
        }
    }
}

Drow.register(greeting);

Now if you change the name:

const card = document.querySelector('greeting');
card.setAttribute('name', 'Alice'); // The component updates automatically

Communication Between Components

For parent-to-child, use props and attributes. For child-to-parent, use custom events (standard Web Component pattern):

var buttonComp = {
    "name": "my-button",
    "props": ['text'],
    "template": `<button>{{text}}</button>`,
    "init": function(config) {
        const comp = this.getComp();
        const btn = comp.querySelector('button');

        btn.addEventListener('click', () => {
            comp.dispatchEvent(new CustomEvent('button-clicked', {
                detail: { timestamp: Date.now() }
            }));
        });
    }
}

Drow.register(buttonComp);

Listen for it in your HTML:

<my-button text="Click me"></my-button>

<script>
    document.querySelector('my-button').addEventListener('button-clicked', (e) => {
        console.log('Button clicked at:', e.detail.timestamp);
    });
</script>

Setup and Installation

Via Script Tag

Include the file directly in your HTML:

<script src="drow.js"></script>

<my-comp prop="value"></my-comp>

<script>
    var myComp = {
        "name": "my-comp",
        "props": ['prop'],
        "template": `<div>{{prop}}</div>`
    };

    Drow.register(myComp);
</script>

Via NPM

npm install drow

Then import it:

import Drow from 'drow';

const myComp = {
    "name": "my-comp",
    "props": ['prop'],
    "template": `<div>{{prop}}</div>`
};

Drow.register(myComp);

When Should You Use Drow?

Good fit:

  • You want Web Components without the boilerplate
  • Building UI components for your site
  • Small to medium projects
  • You want a library that’s easy to understand and modify
  • Bundle size matters
  • Prototyping and learning

Consider alternatives:

  • You need complex state management (React/Vue/Svelte are better here)
  • Very large enterprise applications with extensive component ecosystems
  • You need advanced templating or reactivity features

Comparison with Lit

Both are lightweight Web Component libraries, but they approach it differently:

Feature Drow Lit
Bundle Size <1kb ~5kb
Learning Curve Minimal Low-Medium
Dependencies None None
Syntax Objects Classes + Decorators
Templating Handlebars Tagged template literals
Reactivity Manual Built-in

Drow keeps things simple. Lit adds more power at the cost of a bit more complexity. Pick based on your project needs.

Recent Updates

The library recently converted its internal naming from DrowJS to Drow, making the API cleaner and more consistent. The core functionality remains the same—object-based component definitions with minimal overhead.

Getting the Code

Everything is on GitHub: https://github.com/johnfacey/drowjs

There’s a /docs folder with more detailed documentation, and you can see examples in index.html and samples.js.

To run locally:

npm install
npm run server

The Bottom Line

Drow.js won’t replace React or Vue for complex applications. But if you want to use the browser’s native component system without fighting the API, it’s a really pleasant experience.

The value of Drow is in its simplicity. You get Web Component encapsulation, scoped CSS, prop-based templating, and lifecycle hooks—all in a tiny package with almost no learning curve.

If that sounds useful for your project, give it a try. The barrier to entry is basically zero.

Learn more: Check out @johnfacey on Twitter, @johnfacey on BlueSky or visit the GitHub repository.

What Happens To Your Architecture When Clients Expect 24/7 AI Availability

Most AI systems look stable until somebody depends on them operationally.

Internal demos tolerate downtime.

Experiments tolerate inconsistency.

Hackathon systems tolerate failure.

Enterprise environments do not.

The moment clients expect AI systems to stay available 24/7, architecture decisions change fast.

Things that looked acceptable during development suddenly become operational risks.

The First Thing That Breaks Is Assumptions

Early AI systems are usually built around optimistic assumptions:

  • APIs will respond quickly
  • Models will behave consistently
  • Traffic will remain predictable
  • Retries will solve temporary failures
  • Context windows will be enough
  • Logs will help debugging

None of those assumptions survive long in production.

Once systems run continuously, edge cases stop being edge cases.

They become normal traffic.

AI Infrastructure Fails Differently

Traditional backend outages are easier to detect.

You see:

  • crashed services
  • failed health checks
  • database connection errors
  • CPU spikes

AI infrastructure problems are slower.

The system still responds.

But:

  • answers become inconsistent
  • latency slowly increases
  • retrieval quality drops
  • memory state drifts
  • token costs explode
  • orchestration queues backlog
  • retries amplify failures

The dangerous part is that monitoring often shows “healthy” systems while users experience degraded reasoning quality.

Single Model Dependency Becomes Dangerous

One thing we learned quickly:

Building around a single model provider creates operational fragility.

Not because providers are unreliable.

Because upstream behavior changes constantly.

Things that change unexpectedly:

  • response formatting
  • tool calling structures
  • latency profiles
  • tokenization behavior
  • safety filters
  • rate limits

A prompt that worked perfectly last month can silently degrade after a provider-side update.

If your architecture depends heavily on exact model behavior, production stability becomes fragile.

We started treating model providers like unstable infrastructure dependencies.

That changed how we designed everything around them.

Retry Logic Starts Creating Problems

Retry systems look harmless early on.

Then traffic scales.

Now one slow dependency creates:

  • duplicated jobs
  • queue congestion
  • inconsistent state updates
  • race conditions
  • delayed workflows

One issue we hit involved async retrieval workers retrying aggressively during provider latency spikes.

The retries themselves caused more system pressure than the original outage.

The fix was not “more retries.”

The fix was:

  • retry isolation
  • queue prioritization
  • circuit breakers
  • failure backoff
  • partial workflow recovery

24/7 systems punish uncontrolled retries.

Stateful AI Systems Become Distributed Systems

The moment you introduce:

  • memory
  • retrieval
  • agent workflows
  • background processing
  • user context
  • long-running tasks

you are no longer building a stateless API layer.

You are building distributed infrastructure.

That changes debugging completely.

One production issue looked like hallucination problems from users.

The actual issue:

Two services cached different retrieval snapshots for the same conversation state.

The model output was technically valid based on the wrong context.

That kind of issue does not show up during small-scale testing.

It appears only after continuous operation.

Observability Becomes More Important Than Features

The longer systems run, the more debugging dominates engineering time.

Basic logging stops being enough.

You need visibility into:

  • prompt versions
  • retrieval sources
  • token usage
  • orchestration paths
  • worker execution timing
  • queue state
  • external dependency latency
  • memory mutations

Without that, production debugging becomes guesswork.

One thing we now treat as mandatory:

Full request trace reconstruction.

Not just logs.

Complete execution replay:

  • incoming request
  • context injection
  • retrieval outputs
  • model inputs
  • model responses
  • tool execution
  • final orchestration result

Because AI failures are rarely reproducible otherwise.

Infrastructure Decisions Start Outliving Models

One mistake teams make:

Optimizing heavily around current model capabilities.

Models change fast.

Infrastructure survives much longer.

The systems that age well are usually built around:

  • provider abstraction
  • observability
  • fault isolation
  • workflow recovery
  • deployment safety
  • data consistency
  • operational tooling

Not around one specific model workflow.

The AI layer evolves constantly.

Operational infrastructure accumulates permanent complexity.

The Biggest Architecture Shift

The biggest shift is psychological.

At some point you stop thinking:

“How do we get better AI output?”

And start thinking:

“How do we keep this operational under continuous uncertainty?”

That changes priorities completely.

Reliability starts beating novelty.

Recovery starts beating optimization.

Infrastructure starts mattering more than prompts.

And most engineering effort moves into keeping systems stable while everything around them changes continuously.

What’s Coming to Laravel Cloud: $5/month Plan, Spend Caps, and Instant Scale-to-Zero

Originally published at hafiz.dev

The two things that kept most Laravel developers off Laravel Cloud were cost and the fear of a surprise bill. The Laravel team just announced both are being fixed, along with three other significant changes coming over the next few weeks.

Here’s what was announced and what it actually means.

The Five Announcements

The announcement laid them out clearly:

1. A new $5/month plan. Previously, your options were Starter (free tier, pay-as-you-go, limited features) or Growth ($20/month). The new $5/month tier sits between them. The stated goal: “We want Cloud to be accessible to every Laravel developer.”

2. Spend caps. Set a spending limit on your account. When you hit it, Cloud will either alert you or shut off compute entirely. No more anxiety about waking up to an unexpected $300 bill.

3. True scale-to-zero with 10x faster wake-up. Hibernation has existed for a while, but wake-up previously took 5-20 seconds. App, database, and cache wake-up is now measured in milliseconds. Users won’t notice it.

4. Bot protection rules. Keeps hibernated apps dormant longer by filtering bot traffic before it wakes your environment. This directly reduces compute spend. Bots won’t trigger unnecessary wake-ups that eat into your budget.

5. Managed queues. Queue clusters are being rebuilt into a new “managed queues” product. Set a maximum worker count and Cloud handles everything else. It scales on queue depth (not just CPU) and scales to zero when idle. No more guessing at worker sizing.

These changes aren’t live yet. The announcement said “coming over the next few weeks,” so treat this as a planning-ahead post rather than a same-day implementation guide.

The Spend Cap Is the Real News

The $5/month price point gets the headline, but spend caps are what actually unlocks Cloud for cautious teams.

Usage-based pricing always carries anxiety. Even with hibernation enabled, one unexpected traffic spike, a crawling bot hammering your endpoints, or a runaway job queue could turn a $10 month into an $80 one. That unpredictability kept teams on fixed-cost Forge servers where the bill was predictable even if the compute was oversized.

Spend caps change the risk profile completely. You can enable hibernation, deploy a small instance, and know that the maximum you’ll pay in a worst-case month is whatever cap you set. That’s a fundamentally different product than “usage-based with no ceiling.”

Combine spend caps with the new bot protection rules and the hibernation economics get significantly better. Bots are the silent killers of scale-to-zero apps. They wake your environment constantly without generating real revenue. Filtering them before they trigger a wake-up means your app actually stays dormant during off-peak hours instead of yo-yoing on and off.

What Millisecond Wake-Up Changes

The previous 5-20 second cold start was the biggest practical barrier to using hibernation on anything user-facing. You could use it for staging environments and dev instances without issue, but putting a hibernating app in front of real users meant the first request after idle time got a slow response. Users noticed. Support tickets followed.

Millisecond wake-up removes that constraint. If the wake-up is imperceptible, hibernation becomes a pure cost optimization with no UX downside. You can run a production app with a low-cost Flex instance, hibernate between traffic windows, and pay only for active time, without needing a “warm-up” strategy or a cron job to keep things alive.

One question worth watching: the tweet mentions milliseconds for app, database, and cache wake-up. The database wake-up is the historically slow part. If serverless Postgres wakes in milliseconds consistently, that’s a genuine infrastructure achievement. The community will stress-test this quickly once it ships.

Managed Queues as a Quiet Win

The managed queues announcement is being slightly overshadowed by the pricing news, but it’s significant for teams running serious background processing.

Queue workers have always required manual tuning. How many workers does this job type need? What happens if the queue backs up? How do you prevent workers from sitting idle at 3am burning compute? The standard answer was either Horizon with careful configuration or accepting occasional over-provisioning, patterns covered in the Laravel queue jobs guide.

Managed queues take that decision away. Cloud monitors queue depth and adjusts worker count automatically. When the queue drains, workers scale to zero. When it fills, they spin up. You set a maximum and stop thinking about it.

For applications with variable job loads (processing payments, sending email campaigns, handling webhooks), this is cleaner than anything available on Forge or Vapor today. It ties directly into the monitoring and observability improvements that make Cloud worth the higher base cost.

The New Math: Cloud vs Forge vs Vapor

With the new plan, the comparison looks different for small projects.

Indie developer with multiple side projects:

  • Cloud (new $5 tier + usage with hibernation): $5 + ~$2-5/month per app
  • Forge: $17/month base (you can add cheap VPS servers, but Forge itself is $17)
  • VPS direct: $5-10/month per server (you manage everything)

One developer in the announcement thread shared they’re running seven apps with decent traffic for $6/month total using scale-to-zero. That number will improve further with bot protection and millisecond cold starts. For anyone currently running multiple low-traffic side projects on separate servers, the Cloud math starts looking favorable.

Pre-scale startup on Growth ($20/month):

  • Cloud Growth: $20/month + ~$16-25 usage = ~$36-45/month
  • Forge: $17/month + $20-40/month DigitalOcean/Hetzner VPS = ~$37-57/month

At this tier, the total cost is similar, but Cloud includes autoscaling, managed databases, preview environments, and zero DevOps overhead. Forge requires you to configure servers, manage Nginx, handle SSL renewal, and tune your own scaling. The value comparison has always favoured Cloud for teams without a dedicated DevOps person. The new pricing makes it viable to reach that tier sooner.

Vapor ($39/month):
Vapor runs on AWS Lambda, which gives you true serverless with different performance characteristics. It’s still the right choice for teams deeply invested in the AWS ecosystem. But for most Laravel developers who just want their app deployed and scaled without thinking about infrastructure, Cloud’s model is more Laravel-native and increasingly competitive on price. The CI/CD and deployment guide from last week covers how to automate your Cloud deployments via GitHub Actions.

Who Should Be Paying Attention

Indie developers and side project builders. The $5/month plan plus spend caps makes Cloud viable for projects that previously couldn’t justify $20/month. Multiple side projects on a single Cloud account with hibernation enabled could cost less than one Forge server.

Small agencies running client sites. Preview environments per pull request plus managed databases at $5-20/month per project is a clearly compelling agency workflow. The managed queues addition makes it viable for client projects with background job requirements.

Teams on Forge who don’t enjoy server management. If you’re paying someone to manage Forge servers (patching, monitoring, scaling), Cloud is worth a fresh look. The total cost might be similar, but the management overhead disappears. For teams building SaaS products on Laravel, the complete SaaS guide covers the broader architecture decisions where Cloud fits naturally.

Anyone who previously tried Cloud and left because of cost or cold start anxiety. Both objections are being addressed directly. Worth revisiting once the features ship.

Who Should Stay Where They Are

Developers who want full server control. Forge gives you root access and complete flexibility. Cloud abstracts that away by design. If you need custom Nginx configs, non-standard PHP extensions, or specific OS-level tooling, Forge is still the better choice.

Teams on Vapor with existing AWS infrastructure. The Lambda model suits certain workloads well and migrating away from a working setup for incremental cost savings rarely makes sense.

High-traffic, always-on applications. Hibernation economics only work when your app has genuine idle periods. If you’re running a busy production app that never sleeps, the cost advantage disappears and a dedicated server (Forge or otherwise) may still be cheaper.

The Bigger Picture

The Scotty vs Envoy post from April made the case that the Laravel deployment tooling ecosystem is maturing fast. This announcement reinforces that. A $5/month managed hosting tier with spend caps and millisecond cold starts is a product that didn’t exist anywhere in the PHP ecosystem six months ago.

The spend caps and bot protection show the team is listening to actual adoption blockers rather than just adding features. Those two additions together fix the core reason cautious developers stayed away.

The managed queues rebuild is the kind of unsexy infrastructure improvement that saves teams real operational hours once you’re running it in production.

Once these ship, the “why not just use Forge” argument gets harder to make for most new Laravel projects.

FAQ

When are these features available?

The announcement said “coming over the next few weeks.” No specific release date was given. Follow the official Laravel Cloud blog for the rollout timeline.

Will the $5/month plan include custom domains?

Not confirmed in the announcement. The current Starter (free) plan includes custom domains, so it’s likely the $5 tier does too. Check the official pricing page once it’s updated.

Does the spend cap pause the app or just alert you?

The announcement confirmed it will either alert you or shut off compute. The configuration details (which trigger, per-environment vs per-account) will be in the documentation once it ships.

Should I migrate existing apps before these features land?

Wait until they’re live and documented before making migration decisions. The announcements are forward-looking; current Cloud behavior still applies today.

How does bot protection work with legitimate crawlers?

Specific configuration details weren’t shared in the announcement. Presumably you’ll be able to allowlist crawlers like Googlebot. More detail will come in the docs.

Worth Watching

These announcements move Laravel Cloud from “interesting but expensive” to “worth serious consideration for almost any new Laravel project.” The spend caps alone change the risk calculation. The millisecond wake-up makes hibernation viable for production. The $5 plan removes the cost objection for small projects.

Whether this changes your deployment setup depends on where you are now. But if you wrote off Cloud six months ago, the next few weeks are worth another look.

If you’re evaluating Laravel Cloud for a specific project and want to think through the numbers, get in touch.

How I got my HP fingerprint sensor working on Linux.

I bought a used HP EliteBook 840 G5 last year. Cleaned it up, wiped Windows, put Ubuntu on it. Everything worked except the fingerprint reader, which I figured I’d get to “eventually.”

“Eventually” turned out to mean three sessions, several wrong turns, a USB reverse engineering side quest, and a one-line fix that fixes the same problem for a chunk of HP laptops nobody had been able to use on Linux.

The expectation: just install a driver

This is the thing non-Linux people (and a lot of Linux people, honestly) get wrong about hardware support. You don’t “just install a driver.” Either:

  1. The kernel already supports it (out of the box)
  2. Someone reverse-engineered the protocol and shipped a userspace driver
  3. The vendor ships a Linux driver (rare outside of mainstream chipsets)
  4. It doesn’t work

My sensor was case 4. Specifically: 138a:00ab, a Synaptics VFS7552 chip with their “PurePrint” anti-spoofing variant. Synaptics only ships a Windows driver. libfprint (the standard Linux fingerprint library) has no driver for this PID. python-validity (the heroic community reverse-engineering project) supports the related Lenovo Prometheus chips but not mine.

Of 649 systems with this exact device logged on linux-hardware.org, zero worked on Linux. There were three open GitHub issues asking for support, the oldest from 2023.

So that was the starting point.

Step one: figure out what you actually have

$ lsusb | grep -i validity
Bus 001 Device 019: ID 138a:00ab Validity Sensors, Inc.

Searching 138a:00ab led to linux-hardware.org’s page, which confirmed the model (“Validity Sensors Synaptics VFS7552 Touch Fingerprint Sensor with PurePrint”) and the bleak status: 649 reports, all failing.

The PurePrint suffix sounded ominous. Looking at the USB endpoint structure (5 endpoints: one bulk OUT, two bulk IN, two interrupt IN), I figured PurePrint probably added an encrypted channel on top of the plain VFS7552 protocol. Spoiler: I was half right.

The first dead end: the libfprint vfs7552 driver

libfprint ships a driver called vfs7552 for the Dell XPS variant of this chip (PID 0091). I cloned the repo, added 0x00ab to its PID table, rebuilt, and tried it:

cmd_01 (rom info) ............. OK, 38 bytes returned
cmd_19 ........................ OK, 68 bytes
vfs7552_init_00 (501 bytes) ... rejected, status 0x04be

The chip accepted the basic identification commands and returned valid-looking data. It just refused the 501-byte initialization blob that the Dell driver wanted to send.

Diffing the cmd_01 response against the Dell reference values, my chip and the Dell one agreed on the first 18 bytes (sensor family identifier) but differed in a few bytes that looked like firmware version (0x03d1 vs 0x0000) and a per-chip serial number. The init blob is firmware-version-specific. Sending the Dell’s blob to my chip is like sending Windows XP install media to a Mac.

I tried disassembling the HP Windows driver (synaWudfBioUsb.dll, extracted from the official HP softpaq) in radare2 to find the right bytes to send. I got far enough to confirm the driver does ECDH key exchange and signed firmware upload — BCryptGenerateKeyPair, BCryptSecretAgreement, BCryptSignHash all in the imports — but the actual byte sequences turned out to be dynamic, not static. They’re constructed from runtime crypto, not stored as constants you can grep for.

The libfprint path was over. The chip wasn’t really a vfs7552; it was something more sophisticated wearing a vfs7552 marketing label.

The pivot: python-validity

python-validity is uunicorn’s reverse-engineered userspace driver for the chips Synaptics calls “Prometheus” — 138a:0090, 0097, 009d, 06cb:009a. These chips do real TLS-like crypto handshakes and signed firmware upload, which matched what I’d seen in the disassembly.

The architecture made me hopeful: maybe 0x00ab was just a Prometheus chip wearing a vfs7552 marketing label. The protocol bits I’d already verified (cmd_01, cmd_19) are also what python-validity’s usb.send_init() sends first.

I wrote a minimal test script that:

  1. Opened the device
  2. Sent cmd_01, cmd_19, cmd_4302 (basic info queries) — all worked
  3. Sent python-validity’s init_hardcoded blob (a 581-byte crypto blob for the supported chips)
init_hardcoded (581 bytes): send 06 02 00 00 01 4a 23 14 06 e5 54 2f c6 dc 3b 1a ...
recv 2 bytes, status=00 00: 00 00

0000. Status OK. The HP chip accepted python-validity’s blob, which meant: same protocol family. Verified by going further and completing the full ECDH key exchange — the encrypted channel established cleanly against the chip’s factory TLS state.

The painful middle: I thought I was stuck

I patched python-validity to support 00ab. The service started. fprintd-enroll ran, asked for finger swipes, completed. fprintd-verify… hung forever.

$ fprintd-verify
Using device /net/reactivated/Fprint/Device/0
Listing enrolled fingers:
 - #0: right-index-finger
Verify started!
Verifying: any
^C   # me, after a minute of nothing

I figured the problem was that python-validity’s sensor.open() only knows two sensor type profiles (0x199 and 0xdb), and my chip reports 0xd51. With the wrong profile, image dimensions and calibration parameters would be wrong, producing garbage images.

I spent hours on this hypothesis. Tried both profiles. Looked for ways to extract the right values from Windows. Read the open issues. Issue #225 — same 0xd51 sensor type on a different USB ID — was a user who had hit the exact same wall with the exact same patches I’d applied. They got stuck at the same place.

The narrative I had in my head was: this is the calibration wall. Without per-chip data extracted from Windows, we’re done. I started writing the apologetic “we did good work but here’s where it ends” wrap-up.

Looking at the actual data

But before giving up I added one more piece of instrumentation: dump every TLS-decrypted response over 100 bytes to disk. Re-enrolled. Looked at what came out:

4104 B  cmd40   (× 4)     calibration frames
1966 B  cmd02   (× 9)     per-stage frame data
5040, 9584, 14128, 14128, 18672, 18672, 18672, 23304 B  cmd6b   enrollment template

The enrollment template was growing by ~4500 bytes per scan. That’s real feature data accumulating. If the chip were producing garbage images, the matcher wouldn’t have anything to extract features from — the template wouldn’t grow.

I rendered the cmd02 frames as 44×44 grayscale PNGs. They didn’t look like noise. They looked like fingerprint ridges.

The chip was capturing real images. The matcher was building real templates. So why did verify hang?

The actual bug

I went back and stared at sensor.capture():

def capture(self, mode):
    assert_status(tls.app(self.build_cmd_02(mode)))

    # start
    b = usb.wait_int()
    if b[0] != 0:
        raise Exception('wait_start: Unexpected interrupt type ...')

    # wait for finger
    while True:
        b = usb.wait_int()
        if b[0] == 2:
            break

    # wait capture complete
    while True:
        b = usb.wait_int()
        if b[0] != 3:
            raise Exception('Unexpected interrupt type ...')
        if b[2] & 4:
            break
    ...

Three interrupt phases: start, finger-detected, capture-complete. I went back to the journal logs and looked at what interrupts my chip was actually sending:

<int< 00 00 00 00 00    # b[0] = 0  (start)
<int< 03 20 07 00 00    # b[0] = 3  (capture event)

That’s it. Two interrupts. The chip never sent b[0] = 2 “finger detected.” It skipped straight from start to capture event.

So the wait-for-finger loop sat there forever, waiting for an interrupt that was never coming. The chip had captured the image, the matcher had a result, but the daemon couldn’t tell because of an infinite loop in user code.

The fix:

# wait for finger. Sensor type 0xd51 (138a:00ab, 06cb:00b7) does not
# emit b[0]=2; it jumps directly to capture events. Accept b[0]=3 as
# a substitute and save the interrupt for the next loop.
saved_b = None
while True:
    b = usb.wait_int()
    if b[0] == 2:
        break
    if b[0] == 3 and getattr(self, 'real_device_type', None) == 0xd51:
        saved_b = b
        break

# wait capture complete
while True:
    b = saved_b if saved_b is not None else usb.wait_int()
    saved_b = None
    ...

Eight lines, gated on the chip type so existing supported chips take the unchanged code path.

Restart the service. Re-enroll. fprintd-verify:

Verify result: verify-retry-scan (not done)
Verify result: verify-retry-scan (not done)
Verify result: verify-match (done)

A correct finger matched. Tried with a different finger:

Verify result: verify-no-match (done)

Real matching, real rejection. Wired it through PAM:

$ sudo -k && sudo whoami
[sudo] Place your finger on the fingerprint reader
root

What was actually hard about this

The thing that fooled me, and fooled everyone else who had tried was that the chip appeared to be calibration-stuck. Enrollment completed (because enrollment uses a slightly different code path that doesn’t wait for b[0]=2). Verify hung. The natural conclusion was “matching fails, images are bad.” Everyone tried fiddling with the sensor profile. Nobody tried instrumenting the interrupt stream.

Looking at the actual returned data was the move. The cmd6b templates growing by 4500 bytes per scan was the smoking gun: the chip was clearly producing real features, which meant the chip was capturing real images, which meant the image pipeline was working, which meant the problem had to be after image capture and before match results came back. That’s a small window and the wait-finger loop was sitting in it.

What’s in the patch

The PR is at uunicorn/python-validity#256. +37 / -3 lines across five files. It:

  • Wires 138a:00ab and 06cb:00b7 through the SupportedDevices enum, blob routing, firmware mapping, and udev rules
  • Aliases sensor type 0xd51 to the 0x199 profile so downstream SensorTypeInfo / SensorCaptureProg lookups succeed (no native profile exists yet — empirically 0x199 is close enough that the on-chip matcher accepts real images)
  • Patches Sensor.capture() to accept b[0]=3 as a substitute for b[0]=2, gated on the real device type

If it merges, three open issues get closed:

  • #181 — open since 2023
  • #225 — 06cb:00b7 user with the same wall
  • #238 — newer report

If you have one of these chips and don’t want to wait for the merge, you can clone my fork branch:

git clone -b feat/sensor-type-0xd51 https://github.com/SimpleX-T/python-validity.git
sudo pip install --break-system-packages --prefix=/usr ./python-validity
sudo systemctl restart python3-validity.service

(You’ll also need open-fprintd and the supporting D-Bus / systemd / udev plumbing, which the PR description and python-validity’s debian/ folder document.)

Takeaways

  • Hardware support on Linux is not “downloading a driver.” When the vendor doesn’t ship one, real people spend real weeks reverse-engineering protocols. Look at the python-validity codebase sometime; the existing supported chips have ~500-byte chip-specific crypto blobs in them that were extracted from USB captures of Windows driver sessions. That work doesn’t happen by itself.
  • When a hypothesis explains some of the evidence, don’t stop. I had a clean story — “calibration is wrong, images are bad, matching fails” — that explained the verify failure. It did not explain why enrollment completed cleanly and why the template was growing. I should have noticed the contradiction earlier.
  • Look at the actual data the chip is sending. Adding the TLS-response dump took ten minutes and changed everything. I had been reasoning about what I thought the chip was sending; the actual bytes told a different story.
  • AI-assisted reverse engineering is a real workflow now. I did this with Claude Code; the model held the protocol knowledge I didn’t have and ran disassemblers and code searches in parallel while I was deciding what to do next. The interrupt-handling insight was a result of “let me actually look at every byte the chip sent us and check the contradictions” — a kind of careful empiricism that’s much easier with an assistant doing the bookkeeping.

If you’ve got a Validity/Synaptics fingerprint reader that doesn’t work on Linux, check the USB ID and the open python-validity issues before assuming it’s hopeless. There’s a lot of “almost working” out there.

Credits

I didn’t do this alone. All three sessions of this — the libfprint dead end, the python-validity pivot, the calibration rabbit hole, and finally the interrupt-loop fix — happened in Claude Code, with Claude Opus as a pair. The model held protocol details I didn’t have, ran disassemblers and greps and test scripts in parallel while I was deciding what to do next, and kept track of every hypothesis we’d already ruled out. The interrupt-handling insight came out of “wait, the template is growing — that contradicts the bad-images story; what else could it be?” which is a kind of careful re-examination that’s much easier when you’ve got a partner doing the bookkeeping.

The hardware was mine. The patience for re-enrolling a finger thirty times was mine. The decisions about when to give up and when to push were mine. But the protocol memory and the second pair of eyes were Claude’s, and I want to call that out because “I built X” stories tend to hide the assist.