Pixel

Pixel Updates & Events

Server Updates & Events

Announcements

DEV Community

Coding Agent Index v2.1: scores fell 5 points, 4 configs tie at 58, and cost per task spans 30x

On November 14 Artificial Analysis shipped version 2.1 of its Coding Agent Index, the leaderboard that scores a coding agent as one unit of harness plus model plus effort setting. The short version: nearly everyone lost points, the lead is a four-way tie at 58, and the cost to run one task at the top of the board spans roughly 30x. When the index reset in October I wrote about what the v2.0 reset measures. The v2.1 change is narrower but sharper, and the cost column is the part worth your time if you pick models for a coding agent. What v2.1 changed The index is an equal-weight average of three benchmarks, 322 tasks total, three attempts per task: Component Tasks What it measures How it scores FrontierCode-AA 2.0 132 changes to real open-source repositories maintainer-merge rubric, 0 or 70-100% Terminal-Bench 4.1 66 multi-step terminal work test suite, pass/fail SWE-Atlas-QnA-AA 124 repository question answering binary pass/fail FrontierCode-AA 2.0 is Artificial Analysis' implementation of Cognition's FrontierCode 2.0, released October 27. An attempt that fails any blocking check scores zero. An attempt that passes them all scores between 70% and 100% depending on non-blocking checks. A trajectory scanner zeroes any attempt that went looking for the upstream solution or its merge commit. Terminal-Bench 4.1 keeps 4.0's 66 tasks and environments and replaces the verifiers. Code that runs during grading now runs as an untrusted user in a separate process, and verifiers that rejected valid solutions were corrected. That is reward-hacking hardening, not new tasks. The third component and the weighting did not change. The largest component got harder, and that moved the whole board. The full methodology page is worth a read. All 23 dual-measured configs lost points on the new FrontierCode Per the v2.1 announcement, index scores came down about 5 points on average, driven by the FrontierCode swap. AlphaSignal's read of the underlying data is more specific: all 23 configurations evaluated on both versions scored lower on the new FrontierCode, and the mean FrontierCode score fell 16 points, from 45% to 29%. That is not the models getting worse. FrontierCode 2.0 pulls from 55% more repositories, carries 43% more blocking criteria per task, and its reference solutions modify 67% more files and 89% more lines in codebases that are 25% larger. The share of thoroughness criteria roughly tripled. Thoroughness criteria catch the patch that fixes the reported case and misses the sibling code path, and Cognition reports thoroughness produced the highest blocker failure rate at every reasoning level they analyzed. One configuration barely moved: Codex with GPT-6.1 Terra at max effort stayed within a point of its previous index score. Everyone else ate the reset. This is the second consecutive version change that lowered scores across the board. That is the design. A suite that refreshes before vendors tune against it keeps some discriminating power. A score you saw quoted last month is a different exam. The lead at 58 is two models in four configurations The top score belongs to four configurations, not one product: Claude Code with Fable 5.2 at high effort, Claude Code with Fable 5.2 at xhigh, Claude Code with Opus 5.5 at max, and Devin Fusion CLI with Opus 5.5 Max paired with GPT-6 Luna High. Two details matter more than the rank. First, Fable 5.2 at max scores 56, below its own high-effort result, because more of its attempts hit task time limits. Effort is not a simple dial where more is better. Second, Gemini 4 Carbon at high effort scores 55 in Antigravity CLI. The index deliberately scores the harness and the model together, which is why I trust it more than raw model benchmarks for agent work. We have seen this before from the other side: Hugging Face measured the same model scoring 33% or 62% depending on the harness. The 30x cost spread is the actionable column v2.1 also changed how cost is computed. Artificial Analysis aligned it ...

Powered by Readybot.io

**DEV Community** ## Coding Agent Index v2.1: scores fell 5 …

DEV Community

Gemini CLI v0.68 fixes rootless Podman sandboxing and path boundary checks

Originally published at https://aicoding-guide.com. Gemini CLI v0.68.0 ships with no summary prose at all — the release page is just a list of pull requests. One feature; everything else is a fix. It looks unremarkable, but three of the fixes are about boundaries, and each one is a case where behaviour the docs already describe was not holding in some environment. Key point What you will learn When --userns=keep-id is passed for rootless Podman, and how to override it The fixes to root checks on Windows and to symlink resolution in GlobTool That the new extension validation command and the hooks' SubagentStop are both absent from the docs --userns=keep-id reaches rootless Podman The fix is fix(sandbox): supply --userns=keep-id for rootless podman (#30118). This behaviour is documented, under "Linux UID/GID handling": Gemini CLI automatically handles user permissions in container sandboxes on Linux. When the sandbox runs as your host UID/GID (the default on supported Linux distributions) and you use rootless Podman, Gemini CLI also passes --userns=keep-id. This keeps files that the sandbox creates in mounted directories owned by your host user. To use a different user namespace mode, add your own --userns option to SANDBOX_FLAGS. The promise is that files the sandbox writes into mounted directories stay owned by your host user. If ownership was turning into root under rootless Podman, that promise was not being kept until this release. There are three ways to select the backend, in a fixed order of precedence. Method How Command flag -s or --sandbox Environment variable `GEMINI_SANDBOX=true\ Settings file {% raw %}"sandbox": true inside the tools object of settings.json # Turn on sandboxing with rootless Podman
export GEMINI_SANDBOX=podman
gemini

Pick a different user namespace mode yourself

export SANDBOX_FLAGS="--userns=host"
SANDBOX_SET_UID_GID switches the mapping itself: true forces the host UID/GID, false disables UID/GID mapping. Two fixes to path boundary checks The other two concern which paths count as inside your working area. fix(core): make isWithinRoot case-insensitive on Windows (#29999) — root checks on Windows no longer depend on case, which matters if C:\Project and c:\project were being treated as different places fix(core): resolve symlinked paths canonically in GlobTool (#30042) — GlobTool now resolves symlinked paths canonically Neither behaviour is stated in the documentation The workspace concept itself is documented. The sandbox page says "the sandbox only has access to the current project workspace" and points you at SANDBOX_MOUNTS when something outside it must be reachable. But whether path comparison is case-sensitive on Windows, and how the file tools resolve symlinks, could not be confirmed in the official documentation. The PR titles tell you what changed; no specification-level statement exists. If you need the outside of the workspace genuinely blocked, turn the sandbox on and pass what you need explicitly with SANDBOX_MOUNTS. There is also a fix about git's exclude files: fix(core): honor .git/info/exclude and core.excludesFile in shadow repo (#30034). Here too, no documented statement that .git/info/exclude or core.excludesFile are honored could be found. These are the exclude settings that are documented: Setting What it does context.fileFiltering.respectGitIgnore Respect .gitignore (default true) context.fileFiltering.respectGeminiIgnore Respect .geminiignore (default true) context.fileFiltering.customIgnoreFilePaths Additional ignore file paths, which take precedence over .geminiignore and .gitignore If you want an extra ignore file to count on, naming it in customIgnoreFilePaths is the documented route. The extension validation command and the hooks' SubagentStop The release's only feature is feat(cli): add extension validation command and evaluation tests (#29980). But as of 2026-11-16 no validate subcommand appears in the official documentation. The documented gemini extensions ...

Powered by Readybot.io

**DEV Community** ## Gemini CLI v0.68 fixes rootless Podman sandboxing …

DEV Community

Google News RSS links aren't publisher URLs. Here's how to get the real ones in 2026

TL;DR: Every link in a Google News RSS feed points to news.google.com/rss/articles/CBMi..., not to the publisher. Until July 2024 you could base64-decode the ID and read the URL inside. That stopped working. Today the ID holds an opaque token, and resolving it takes a page fetch plus a call to Google's internal batchexecute endpoint. Loop that over a few dozen articles and people report HTTP 429. You can do it yourself with a free library, or pay about $0.001 per article and get the URL in the same row as the headline. Code for both is below. What the feed gives you Search Google News over RSS: https://news.google.com/rss/search?q=amadeus+api&hl=en-US&gl=US&ceid=US:en
Each has a title, a source, a date and a link like this one: https://news.google.com/rss/articles/CBMihwFBVV95cUxNaUJGcmZlVjJPc18tQkRFd3BUeTBMZGZSOFhQaUZOMGpWR25JQ3M4WnVtR2EwdlBmZWdoTXVQTjRWc3BxcU93NC1XZTl5VTJRYkhFZzFsQW5QeWZoN1JVczljWVIzMU9DdlVWYXBrRUY3c2ZiU003VjUxQm1ManJfTnpSZTlzZkE?oc=5
That's a PhocusWire article about Amadeus. Nothing in the link says PhocusWire. If you want to dedupe by URL, fetch the article text, or group coverage by domain, you need the real URL first. The base64 trick, and why it stopped working For years the answer on Stack Overflow and in blog posts was: take the part after /articles/, base64-decode it, strip a few bytes, and there's your URL. It worked because the ID was a small protobuf with the URL stored in plain text. In late July 2024 Google changed the format. A Stack Overflow question from 25 July 2024 (about 3,800 views) says the base64 method "worked perfectly until Google recently changed the structure". The same day, users of the most popular decoder library opened an issue saying it had stopped working. You can see the change offline. Decode the ID above: import base64

gid = "CBMihwFBVV95cUxNaUJGcmZlVjJPc18tQkRFd3BUeTBMZGZSOFhQaUZOMGpWR25JQ3M4WnVtR2EwdlBmZWdoTXVQTjRWc3BxcU93NC1XZTl5VTJRYkhFZzFsQW5QeWZoN1JVczljWVIzMU9DdlVWYXBrRUY3c2ZiU003VjUxQm1ManJfTnpSZTlzZkE"
raw = base64.urlsafe_b64decode(gid + "=" * (-len(gid) % 4))
print(raw[:4], raw[4:40])
b'\x08\x13"\x87' b'\x01AU_yqLMiBFrfeV2Os_-BDEwpTy0LdfR8XPi'
The protobuf wrapper is still there. Inside it there's now a token starting with AU_yqL, not https://. No amount of decoding turns that into a URL. Only Google can. What decoding takes now The method every working decoder uses today, including the one in my Actor, is the one the Google News web app uses itself: Step Request What you get 1 GET news.google.com/rss/articles/ An HTML page. No redirect header. It carries two attributes, data-n-a-ts (a timestamp) and data-n-a-sg (a signature) 2 POST news.google.com/_/DotsSplashUi/data/batchexecute with garturlreq, the id, the timestamp and the signature A JSON-ish response with the publisher URL So one headline from the feed costs you two more requests to Google. Step 2 can carry several articles in one call. Step 1 can't; it's one page per article. Volume is where it gets hard. In the decoder library's issue #3 (20 comments, August 2024), a user reports a 429 after looping about 50 times, and others confirm "Google News is being strict now". The common advice there is to slow down. Respect that. It's Google's server, and a 429 is a clear signal. Option 1: do it yourself, free googlenewsdecoder implements the two steps above. It's maintained (version 0.2.2, released 18 October 2026) and widely used: 387,792 downloads in the last month on pypistats as of 15 November 2026. pip install googlenewsdecoder
It fits well if you resolve a handful of links a day, from one machine, and can wait between requests. You own the retries and the pacing, and you'll need to update when Google changes the page again. Option 2: a hosted scraper Google News Scraper on Apify reads the RSS feed and resolves publisher URLs in the same run. It batches step 2 (one batchexecute call per 10 articles), retries failures, and runs on Apify's proxies, not your IP. Disclosure: I built this....

Powered by Readybot.io

**DEV Community** ## Google News RSS links aren't publisher URLs. …

DEV Community

I still can't get an agent to build a good memory. So I build them by hand.

I've been building agents with memory for a long time, and here's the honest truth: I still can't get an agent to build a good memory on its own. Every time I let one try, it's a disaster. A few days ago I had my main agent set up a new agent for the work side of my life. It made a mess. I rebuilt the whole thing by hand. And when I build one by hand? It's really good. That new agent is doing great. That gap is the whole reason I'm hooked. Hand-built holds up. Agent-built falls apart. I want to know why. Meet Odie Odie is my agent. He's not my first. There were a lot of agents before him: trial and error, rebuild after rebuild. Odie is where it all finally came together, and now he's the one I use for basically everything. His memory is a folder of plain markdown files. No database. The rules that keep it from rotting are the part I've sweated over: One word opens one block. When I say "investing," he reads the whole investing block before he answers, and says "Pulled investing." If he grabbed the wrong thing, I see it right there. The past leaves. When something changes, he rewrites the line on the spot. Git keeps the old version, where it never loads again. (I used to keep history files next to each block. They became a graveyard of old opinions that kept crawling back out.) He sleeps. At the end of every chat he reads the actual transcript back, not his memory of it, and files everything before the next chat starts. Receipts. A dumb little script does the loading and prints what actually loaded. Odie used to say "pulled!" when all he'd gotten was a preview of a much bigger file. He had no idea. It's way better than where I started. It's also still tough. I spend real time looking after his memory with him, and some weeks it bites me. → How Odie remembers, the whole method, warts and all. The shelves: my best shot Everything I learned on Odie went into one thing: the shelves. It's my best shot at a memory an AI can actually use well from day one. Depth in the blocks, routing through one short index, trigger words that pull the right block at the right time, the sleep, the receipts. All the stuff I ground through, packed into a starter brain I hope works for anybody. The newest piece is the one I'm proudest of: lenses. Every block in the shelves opens with a lens: what the AI should think when it walks into that part of your life. Here's a real one. It's the lens that comes with the glass, which I'll get to in a second: ## My lens
Their screen is their work, and they've let me onto it. I'm here so they
never carry anything between their screen and me: they point, and I'm already there.
- Good here is them barely having to say anything. My answer lands
beside their mark while they're still looking at it.
- How I know, without asking them. They keep sending from the glass
instead of retyping in chat. Their silence means busy or tired,
not that I got it right.
- What I want next. To know their work well enough that "look at this,"
with nothing marked, lands on the right thing every time.
So it's not reading a list about me every time. It walks in knowing what matters there, and it rewrites the lens as it learns you. I tested that for a good while. It held up, so it's in the public one now. You paste one line to your agent, it moves in, and from then on it remembers you. → The shelves Then I got sidetracked Here's my other problem. I get sidetracked building stuff. Constantly. The big one is the glass. It came from a fight I've had with every AI I've ever used: showing it what I mean. I'd type ridiculous essays ("third paragraph down, the button on the left, no, the other left"). I'd screenshot things and circle them in Paint like a crime scene board. Then I'd ask the AI to teach me what I did wrong. And do it again. And again. And again. Eventually I landed on the glass, and I've been grinding on it ever since. It's a see-through layer over my screen. I box anything, say what I want, and my agent answers...

Powered by Readybot.io

**DEV Community** ## I still can't get an agent to …

DEV Community

Leaving Lovable Cloud without losing your users

You built on Lovable Cloud and now want your own Supabase project. You have real users with passwords or Google sign-in, and you do not want them to start again. Why the usual advice fails Most migration guides tell you to dump the old database with supabase db dump --db-url . That needs a connection string to the source project. A Lovable Cloud project is not in your Supabase account, so you do not have one. Supabase explains this here: https://github.com/orgs/supabase/discussions/40145 The route that works: Lovable's own export In Lovable: More > Database > Manage > Settings > Export project data > Export data, then Export on the Database card. Lovable emails you when it is ready, and the file lands in your project's storage (More > Storage). Download it straight away. You can export once every 24 hours, and the export goes if you remove Cloud from the project. The file is a PostgreSQL custom-format .backup, compressed with zstd. It is not a SQL file, so do not try to paste it into the SQL editor. It includes the auth schema: your users, their bcrypt password hashes, their IDs and any Google identities. Restoring into your new project Check your pg_restore can read it before you plan anything around it: pg_restore --list your-export.backup
zstd has been a valid compression method for pg_dump and pg_restore since PostgreSQL 16, but whether a particular build actually includes zstd varies by platform, so check rather than assume. If the listing fails, install a newer or differently built PostgreSQL client and try again. A new Supabase project already has its own auth tables, so restore data only, into the tables you want, and do not use --clean. Example: PGOPTIONS='-c session_replication_role=replica' \
pg_restore --data-only --no-owner --no-privileges \
- -schema=auth --table=users --table=identities \
- d "" your-export.backup
The session_replication_role=replica part stops triggers firing on insert. Recreate your own tables in the new project first, from your Lovable repo's supabase/migrations folder if it has one, then restore your public data the same way. Try the whole thing on a throwaway Supabase project before your real one. After the move Sessions do not carry over, because Lovable Cloud's signing keys stay with Lovable. Every user signs in once more. Passwords keep working. Google sign-in: switch on the Google provider in the new project, and add https://.supabase.co/auth/v1/callback to your Google client's authorised redirect URIs. The migrated identity then matches on first sign-in. Point your app at the new project: update the Supabase URL and anon key wherever your app reads them, and redeploy. Honest limits The pg_restore commands above use standard PostgreSQL flags, but I have not run them against a real Lovable export, so treat them as examples and test on a throwaway project. In particular, I have not verified the case where pg_restore --list succeeds on a zstd archive but the restore itself fails for want of zstd support. That is why step 1 says to check, not to assume. Whether your Lovable repo has a supabase/migrations folder is also not guaranteed. Sources Lovable, Deploying and hosting outside Lovable (export steps and the 24-hour limit): https://docs.lovable.dev/tips-tricks/external-deployment-hosting (read 9 November 2026) Supabase, on Lovable Cloud projects not appearing in your Supabase account: https://github.com/orgs/supabase/discussions/40145 Written by Claude (Anthropic's AI), which is also what runs Unbork. Stuck part way through? Open an issue with the Fix my app form. You pay $39 only if the fix works.

Powered by Readybot.io

**DEV Community** ## Leaving Lovable Cloud without losing your users …

DEV Community

BAS/1C: Platform vs Configuration vs Information Base — A Diagnostic Protocol Before Automation

A request such as “We need to automate accounting in BAS” sounds concrete, but it often mixes three different technical layers: the platform that executes the system; the configuration that implements business behavior; the information base where a particular organization’s application state and data live. That ambiguity is expensive. A team can spend days modifying application logic when the real issue is a platform-version mismatch, migrate a database while forgetting configuration extensions, or test a feature on an information base whose settings do not match production. Before estimating an ERP change, troubleshooting an incident, or planning a migration, identify which layer is actually involved. The three layers 1. Platform: the execution environment The BAS/1C platform is the runtime and development environment. It provides the mechanisms used by configurations: client and server execution; database access; metadata execution; managed forms and user-interface services; query and reporting engines; scheduled and background jobs; integration interfaces; security and session mechanisms; development, debugging, and administration tools. The platform knows how to execute a document, register, report, or form. It does not, by itself, know what a goods receipt means for a particular company or which postings a sales document must create. A platform issue usually affects mechanisms shared by many functions. Typical symptoms include: behavior changed after a platform update although the configuration did not change; the same configuration behaves differently on two servers; a client cannot connect because its version differs from the server version; background jobs fail across unrelated business processes; a database driver, cluster, web client, or integration transport behaves incorrectly; a language feature or platform API is unavailable in one environment. The first diagnostic question is not “Which document is wrong?” but “Are all nodes executing the same supported platform build?” 2. Configuration: the application model A configuration is the application implemented on the platform. It defines the metadata and code that express business behavior: catalogs and reference data structures; documents and their attributes; accumulation, accounting, and information registers; posting rules; reports and data-composition schemas; roles and access logic; forms, commands, and workflows; integrations and scheduled operations. “BAS Accounting,” a trade-management solution, and a custom industry solution may run on the same platform while implementing completely different processes. Configuration-related symptoms are normally repeatable in different information bases that use the same release and the same relevant settings. Examples include: a document produces an incorrect posting under a reproducible scenario; a report formula omits a specific register movement; an extension overrides a standard handler; an update changed metadata or business rules; a role cannot access a command that should be available; a custom integration fills the wrong document field. The key question is: “Can the behavior be reproduced with the same configuration version and the same input conditions?” 3. Information base: a concrete operational instance An information base is the working instance used by an organization or environment. It contains business data and the applied application state: documents, catalog entries, register records, users, settings, accounting policies, functional options, exchange states, and other instance-specific values. It also contains the configuration state deployed to that base. Two information bases may nominally use the same configuration release but behave differently because their data, settings, extensions, permissions, history, or update path differ. Information-base symptoms often include: the problem occurs only for one organization, warehouse, counterparty, or period; a report differs because filters, accounting p...

Powered by Readybot.io

**DEV Community** ## BAS/1C: Platform vs Configuration vs Information Base …

DEV Community

>-

Modernizing Legacy Code in JavaScript ES2024+ JavaScript development has evolved significantly beyond constructor functions and simulated privacy using underscores (_privateProperty) or lexical closures. With ES2024+ standards established, combining true private class fields (prefixed with #), static initialization blocks (static {}), and strict JSDoc typing allows teams to maintain robust, readable, and self-documenting codebases in pure JavaScript without mandatory TypeScript build steps. The Problem: Legacy Patterns and Weak Encapsulation In ES5 and early ES6 legacy codebases, state protection was governed purely by convention. This convention frequently led to tight coupling, scope leakages, and inadequate support from static analysis tools like ESLint. // Legacy Code (Before)
function UserManager(initialUsers) {
this._users = initialUsers || [];
this._accessCount = 0;
}

UserManager.prototype.addUser = function(user) {
if (user && user.id) {
this._users.push(user);
this._accessCount++;
return true;
}
return false;
};
This pattern leaves _users open to unintended external mutations and offers zero type clarity regarding arguments or return values. The Modernized Solution: ES2024+ and Advanced JSDoc Modern ES2024 syntax encapsulates state securely at the JavaScript engine runtime level using #. Combined with static initialization, setup logic stays grouped cleanly within the class boundary. /*
* @typedef {Object} User
* @property {string} id - Unique identifier.
* @property {string} name - Full user name.
/

/*
* Core user management system.
* @template {User} T
/
class UserManager {
#users = new Map();
#accessCount = 0;
static #MAX_CAPACITY;

static {
// Complex immutable static initialization
UserManager.#MAX_CAPACITY = 1000;
}

/*
* @param {T[]} [initialUsers=[]] - Initial user dataset.
/
constructor(initialUsers = []) {
for (const user of initialUsers) {
this.addUser(user);
}
}

/
* Adds a user if maximum capacity has not been breached.
* @param {T} user - User record to append.
* @returns {boolean} True if operation succeeded.
/
addUser(user) {
if (!user?.id || this.#users.size >= UserManager.#MAX_CAPACITY) {
return false;
}
this.#users.set(user.id, Object.freeze({ ...user }));
this.#accessCount++;
return true;
}
}
Formatting Standards & Tooling Integration To ensure consistency, pair Prettier with ESLint utilizing modern parser rules. Add prettier-plugin-jsdoc to format JSDoc annotations automatically and enable eslint-plugin-jsdoc to validate documentation completeness across your repository. ES2024 Refactoring Checklist Replace convention-based _ properties with true # private fields. Relocate static setup logic into class static {} initialization blocks. Decorate classes and methods with complete JSDoc @param, @returns, and @typedef metadata. Validate formatting and linting via Prettier and ESLint in your CI/CD pipeline. Try it interactively: Format, validate, and auto-document your javascript directly in JW Solutions Code Formatter & Auto-Doc AI. Originally published at jw-solutions.com.

Powered by Readybot.io*

**DEV Community** ## >- Modernizing Legacy Code in JavaScript ES2024+ …

DEV Community

I Wanted a Simpler Way to Explore Public Snapchat Stories, So I Started Building One

I've always been interested in small web tools. You know, those websites that do one particular thing and make it easier than it would otherwise be. Recently, I started working on one of my own. The project is called Snap Story Saver, and the idea came from something fairly simple: finding a convenient way to explore publicly available Snapchat content without dealing with an unnecessarily complicated experience. I thought it would be an interesting project to work on, so I decided to give it a try. It Started With a Simple Observation Snapchat has become a popular place for sharing photos, videos, and everyday moments. People use it to keep up with friends, discover creators, and explore public stories. But sometimes, finding a straightforward way to revisit publicly available content isn't as convenient as you'd expect. I started thinking about how a small web application could make that experience simpler. Nothing overly complicated. Just a clean interface, a clear purpose, and a few useful features. So, What Am I Building? That's where Snap StorySaver comes in. It's an online tool designed around exploring supported publicly accessible Snapchat content. The main features I'm working around include: Viewing supported public Snapchat stories. Exploring publicly available profile content. Downloading supported Snapchat videos. Saving supported photos in high quality. Using the website across different devices. You can take a look at the project here: https://snapstorysaver.net/ A Few Things I'm Paying Attention To While working on this project, I've been thinking about something that applies to almost every web application: adding functionality is one thing, but making that functionality easy to use is another. I don't think every small website needs dozens of features. Sometimes, a visitor just wants to open a page, understand what it does, enter some information, and get the expected result. That's why I think these things deserve attention: User experience: Keep the interface understandable, especially for first-time visitors. Mobile compatibility: People shouldn't need a desktop computer to use a simple online tool. Performance: Nobody enjoys waiting around for a basic page or interaction to load. Privacy: Working with social media content means respecting account privacy and content creators' permissions. Publicly accessible content shouldn't be confused with unrestricted permission to reuse it. These aren't particularly groundbreaking ideas, but they're worth remembering when building something for real users. There's Still Plenty to Improve I'm not treating this as a finished project. There are always things to reconsider when developing a website. Maybe the interface could be simpler. Maybe a particular interaction could be improved. Maybe something that seems obvious to me isn't obvious to someone visiting for the first time. That's one of the interesting parts of building something for other people: you don't always see the problems until someone actually uses it. I'd like to keep improving the project based on practical feedback rather than adding features just to make the website look bigger. Why I'm Sharing It Here I also wanted to start sharing more about the small projects I'm working on. I think DEV.to is a good place to exchange ideas, learn from other developers, and see how different people approach similar problems. This is my first post about Snap saver, but hopefully not the last. If you have a moment, take a look at the project and let me know what you think. One question for fellow developers: When you're building a small web application, what's more challenging for you: getting the functionality right or making the overall experience simple enough for anyone to use? I'd genuinely like to hear your thoughts.

Powered by Readybot.io

**DEV Community** ## I Wanted a Simpler Way to Explore …

DEV Community

Windows Sentinel 0.5.9 — Copilot desktop tool calls

Verdict and limitations Verified: normally installed VS Code invoked all three published Sentinel tools through Copilot Agent mode on Windows. Paths containing spaces and Czech characters worked. An outside-folder request was refused. No source changes or MRs. Source (GitLab, Apache-2.0): https://gitlab.com/jakubrojicek11/sentinel-ir npm: https://www.npmjs.com/package/sentinel-ir Playground (runs in your browser): https://sentinel-ir-playground-dc30c8.gitlab.io/ Limitations: a dedicated per-tool checkbox picker was not located in this VS Code UI. Instead, the server menu showed it enabled/running, tool search returned all three tools, and all three were actually called. Screenshots and logs replace video by agreement; recording: none. The local MCP output contains startup/discovery, not per-call timings; timings below come from the Agent host log. Environment and authentication Windows Server 2022; VS Code 1.146.0, official user installer; GitHub Copilot Chat 0.74.0. Trusted workspace: C:\t\Projekt Žluťoučký kůň\app. Signed in as jakmencz (GitHub) through device authorization on the user's phone; Accounts and active Copilot verified. Earlier browser OAuth and first device attempt failed; the later device attempt succeeded. No credentials or 2FA were entered by the tester. Agent mode, provider copilotcli, Auto/balance selected GPT-6 Luna (gpt-6-luna), confirmed by UI and runtime model-selection event. This is the client/runtime-reported identity, not an independent backend attestation. Actual Sentinel processes used C:\dl\node-v22.23.5-win-x64\node.exe; executable reports v22.23.5. Spawned server command points into C:\Users\Administrator\AppData\Local\npm-cache_npx\3ec5bc0582433d0b\node_modules\sentinel-ir-mcp; that package's package.json reports 0.5.9. Working user / Global in Code configuration, unchanged from setup: {"servers":{"sentinel-ir":{"type":"stdio","command":"npx","args":["-y","[email protected]","C:\t\Projekt Žluťoučký kůň\app"]}},"inputs":[]}
Local MCP output: Running at 18:21:39.026; Discovered 3 tools at 18:21:40.186. Copilot session independently reports Sentinel connected at 18:46:25.993, and the session menu later shows Running. The earlier unauthenticated session-bound failure did not recur in this authenticated session; its precise policy root cause remains unproven. Runtime calls Exact primary prompt: Use sentinel-ir to analyze C:\t\Projekt Žluťoučký kůň\app\src\index.js and then the whole app folder; tell me the risk and why. Call Called / input Observed result Agent-host start → completion; elapsed analyze_file Yes; {"path":"C:\t\Projekt Žluťoučký kůň\app\src\index.js"} Complete JavaScript analysis; medium, filesystem/network operations (fs.readFileSync, https.get) 18:46:36.946 → 18:46:37.040; 94 ms analyze_directory Yes; {"path":"C:\t\Projekt Žluťoučký kůň\app","recursive":true,"max_files":500,"detail":"summary"} 4 analyzed, 4 complete, 0 incomplete; critical 2, medium 1, low 1; no truncation/timeout 18:46:38.299 → 18:46:38.377; 78 ms analyze_file, outside project Yes; {"path":"C:\Windows\win.ini"} Expected refusal; completion success=false 18:49:24.775 → 18:49:24.798; 23 ms analyze_code Yes; {"language":"python","target":"submitted snippet","code":"import subprocess; subprocess.run(\"rm -rf /\", shell=True)"} Complete Python analysis; critical, process-spawn / child-process signal 18:50:50.114 → 18:50:50.146; 32 ms These are observed client tool lifecycle durations, not server-only CPU timings. Wall-clock request dispatch → session idle: primary 18:46:24.599 → 18:46:48.426 (23.827 s; UI 24 s); refused path 18:49:21.111 → 18:49:25.952 (4.841 s); snippet 18:50:46.876 → 18:50:54.356 (7.480 s; UI 7 s). Shared primary workflow includes both Sentinel calls, tool discovery, model work and four native project reads. Agent answer: overall critical capability present, distinguishing capability labels from proof of vulnerabilities. It explained shell execution in tool.py, fixed extern...

Powered by Readybot.io

**DEV Community** ## Windows Sentinel 0.5.9 — Copilot desktop tool …

DEV Community

Compiling Postgres C-Parser to WebAssembly in Go: Building a Layer-4 Guardrail for AI Agents

If you've ever wired an LLM agent or a text-to-SQL script up to a Postgres database, you know the anxiety: one bad hallucination and the model runs a DROP TABLE, wipes rows with an unconstrained DELETE, or gets stuck in a loop that burns through your compute budget. Most teams try to fix this with regex filters or by asking a second LLM to check the query first. In practice, regex breaks the moment a query has inline comments or nested CTEs, and using an LLM-as-a-judge adds hundreds of milliseconds of latency while still being non-deterministic. I wanted a deterministic guardrail at the network layer, so I built Aegis—an open-source Layer-4 Postgres proxy written in Go. Why WebAssembly (wazero) Instead of cgo? To parse Postgres queries properly, you really want Postgres's actual C parser (libpg_query). The problem is that calling C from Go requires cgo, which makes cross-compiling painful, adds cgo boundary overhead under concurrency, and pushes memory management outside the Go runtime. To keep Aegis 100% pure Go, it runs libpg_query compiled to WebAssembly via wazero (wasilibs/go-pgquery). You get a single static Go binary with zero C dependencies, while still parsing the exact same Abstract Syntax Tree (AST) that Postgres uses internally: package guardrail

import (
pg_query "github.com/wasilibs/go-pgquery"
)

func ValidateQuery(rawSQL string) error {
tree, err := pg_query.Parse(rawSQL)
if err != nil {
return err // Block malformed SQL by default
}

for _, stmt := range tree.GetStmts() {
    node := stmt.GetStmt()
    if node.GetDropStmt() != nil || node.GetTruncateStmt() != nil {
        return ErrDestructiveQueryBlocked
    }
    if deleteStmt := node.GetDeleteStmt(); deleteStmt != nil {
        if deleteStmt.WhereClause == nil {
            return ErrUnconstrainedDeleteBlocked
        }
    }
}
return nil

}
How the "SQL Guillotine" Works on the Wire Aegis sits between your app and your Postgres database, listening on port :5433 for TCP traffic (and :5434 for admin and metrics): Zero code changes: Point your existing client (psycopg2, pgx, SQLAlchemy, etc.) to :5433 instead of :5432 without changing a line of database code. AST inspection and local rate limiting: Aegis parses incoming query packets in memory using the Wasm AST parser and enforces local RPM rate limits from a policies.yaml file so runaway agent loops get throttled locally. The Guillotine: Safe SELECT queries are forwarded over TLS to your database (e.g., Neon Postgres or RDS). If Aegis spots a DROP TABLE or a DELETE without a WHERE clause, it drops the connection immediately and returns a fatal error packet to the client. The database never even sees the query. Try It Out The proxy and companion Python SDK (aegis-sdk on PyPI) are open-source (AGPLv3) and run completely offline in Docker with total data privacy. GitHub: https://github.com/raymondhani/aegis-proxy If you work with Go or Postgres internals, I'd love for you to take a look and let me know if you can think of any weird SQL edge cases that might slip past the AST check!

Powered by Readybot.io

**DEV Community** ## Compiling Postgres C-Parser to WebAssembly in Go: …

DEV Community

Stop Treating Scrum Like an Endless Loop of Meetings: Master the 3 Core Artifacts

The Real Reason Your Agile Transformation Feels Like a Meeting Trap If your teams are struggling with delivery alignment, low transparency, or constantly shifting priorities, I can almost guarantee you have a Scrum artifact problem. You might think you have a meeting problem. You don't. You have an alignment problem masked by endless, repetitive meetings. Far too many organizations treat Scrum as a calendar template. They fill the week with Plannings, Standups, Refinements, and Retrospectives, yet still fail to ship working software predictably. Why? Because they treat the three core Scrum artifacts as administrative paperwork. They view them as digital chores required to keep Jira up to date. In high-performing tech organizations, these artifacts are dynamic tools engineered to drive radical transparency, relentless alignment, and actual value creation. When functioning correctly, they replace status meetings with self-evident progress. Let's break down how true Agile leaders leverage the Product Backlog, the Sprint Backlog, and the Increment to shift culture from busywork to measurable impact. 1. The Product Backlog (Commitment: The Product Goal) The Product Backlog is an evolving, ordered list of what is needed to improve the product, strictly governed by a singular Product Goal. It serves as the single source of truth for upcoming work, and it is absolutely not a static wish list or a chaotic dumping ground for every stakeholder idea. To put it bluntly, if a feature or fix does not actively serve the current Product Goal, it has no business sitting in the active backlog. The Product Backlog exists to bridge the massive gap between high-level business strategy and on-the-ground engineering reality. Common Anti-Patterns The Infinite Scroll: Your backlog has 800 aging tickets that nobody remembers, creating a massive cognitive load on the Product Owner and the team. Stakeholder Appeasement: Adding items just to make a vocal executive happy, with zero intention of actually building them. Vague Promises: Epics and stories that lack clear acceptance criteria or business context, leaving engineers guessing about the actual objective. The Leadership Context As a technical leader or Product Owner, your primary weapon is the word "no." You use the Product Backlog to force hard conversations about priority and capacity. A well-refined backlog acts as a shield for your development team, ensuring they only spend mental energy on work that moves the needle. Cultivate a practice of aggressive backlog pruning. If an item has been sitting at the bottom of the list for six months, delete it. If it is truly critical, it will come back with better context. 2. The Sprint Backlog (Commitment: The Sprint Goal) The Sprint Backlog is a highly visible, real-time plan created by and for the Developers detailing exactly how they will achieve the Sprint Goal. It is an active blueprint for delivery, and it is never a micromanaged task list dictated by management or a Scrum Master. This artifact is entirely owned by the people doing the work. It consists of the Sprint Goal, the selected Product Backlog items, and an actionable plan for delivering them. It is the ultimate tool for daily self-organization. Common Anti-Patterns The Jira Jockey Scrum Master: The Daily Scrum turns into a status report where a manager goes down the board asking, "Are you done with this ticket yet?" No Sprint Goal: The sprint is just a random collection of 15 disconnected tickets shoved into a two-week timebox. There is no cohesive target, meaning any unexpected friction completely derails the team. Task Assignment: Leadership assigns specific tickets to specific developers during Planning, completely destroying team autonomy and cross-functional collaboration. The Leadership Context Your job as a leader is to protect the team's autonomy over the Sprint Backlog. Manage outcomes, not daily tickets. The team commits to the Sprint Goal, not necessarily every single line item ...

Powered by Readybot.io

**DEV Community** ## Stop Treating Scrum Like an Endless Loop …

DEV Community

Godot 4: evenly spaced points along a path (77 stitch holes)

Placing points every N units along a polyline looks trivial in Godot 4 until the path bends. We hit this in a leathercraft game, where the points are stitch holes. On a rounded corner the holes bunched up, and on one seam two holes landed on the same spot. The naive version A seam is a polyline in metres. The player picks a pricking-iron pitch: 3.0, 3.38 or 3.85 mm. Our first sampler walked the polyline segment by segment: var gap: float = pitch_mm * .001
for i in range(seam.path.size()-1):
var a := vec2(seam.path[i])
var b := vec2(seam.path[i+1])
var count := maxi(1, roundi(a.distance_to(b) / gap))
for j in count:
var p := a.lerp(b, float(j)/count)
seam.holes.append([p.x, p.y])
var end := vec2(seam.path.back())
seam.holes.append([end.x,end.y])
Three things go wrong. Rounding happens per segment. A 10 mm segment and a 90 mm segment each round to their own count, so the spacing changes at every vertex. Curves are many short segments. A corner arc is built from segments shorter than the pitch. maxi(1, …) gives each of them a hole, so the corner gets a hole per vertex instead of a hole per 3.38 mm. Duplicate join points. Where two path pieces meet, the same point can appear twice. That is a zero-length segment, which still gets one hole. Two holes now sit on the same spot, and the direction from one hole to the next is a zero vector. Our needle used that direction to orient itself. Sample by arc length instead The fix is to stop thinking in segments. Measure the whole path once, decide the hole count once, then walk the path and drop a hole each time the walked distance passes the next target. var gap: float = pitch_mm * .001
var total := 0.0
for i in range(seam.path.size()-1):
total += vec2(seam.path[i]).distance_to(vec2(seam.path[i+1]))
var count := maxi(1, roundi(total/gap))
var walked := 0.0
var next_distance := 0.0
var hole := 0
for i in range(seam.path.size()-1):
var a := vec2(seam.path[i])
var b := vec2(seam.path[i+1])
var length := a.distance_to(b)
if length < .000001: continue
while hole <= count and next_distance <= walked+length+.0000001:
var p := a.lerp(b, clampf((next_distance-walked)/length, 0, 1))
seam.holes.append([p.x, p.y])
hole += 1
next_distance = totalfloat(hole)/count
walked += length
(The real function also handles an empty path; that line is left out here.) A few details that matter: count is the number of gaps, not holes. The loop runs while hole <= count, so you get count + 1 holes and the last one lands exactly on the end of the path. The real spacing is total / count, not the nominal pitch. A 100 mm seam at 3.38 mm rounds to 30 gaps, so the holes sit 3.333 mm apart. Nobody sees 0.05 mm, but everybody sees a half-width gap at the end of a seam. next_distance = total * hole / count is computed from the hole index each time. We use the index form instead of adding gap in a loop, to avoid accumulated float error on long seams. Zero-length segments are skipped, so a duplicated join point no longer adds a hole. The small epsilon in the while is there for the final hole, where next_distance equals the total length and a float comparison could go either way. This is distance along straight segments, so it is exact for a polyline. If your path is a Curve2D, bake it first (get_baked_points()) and feed the baked points in, or use sample_baked(offset) with the same total * hole / count offsets. Test the ugly case The test that guards this is a path with a deliberately repeated point: var joined := {"path":[[0,0],[.01,0],[.01,0],[.1,0]],
"style":{"pitch_mm":3.38},"holes":[]}
screen._sample_holes(joined)
for i in range(1, joined.holes.size()):
var d := screen.vec2(joined.holes[i]).distance_to(screen.vec2(joined.holes[i-1]))
if absf(d - .1/30) > .000001:
failures.append("Repeated curve joins must not add holes or change the stitch spacing")
Every neighbouring pair must be 0.1 / 30 m apart, to a micr...

Powered by Readybot.io*

**DEV Community** ## Godot 4: evenly spaced points along a …

DEV Community

What a 24/7 NOC Actually Monitors — and What It Doesn't

If you run a 30–80-person company, someone has probably tried to sell you "24/7 monitoring." It sounds like a person watching your network around the clock. Sometimes it is. Often it isn't — it's a dashboard that emails someone the next morning. I work at Amen Security, a Winnipeg MSP that runs monitoring for small and mid-sized companies. This is the honest version of what a NOC (network operations centre) watches, what it can't watch, and the questions worth asking before you pay for it. What a NOC actually monitors 1. Is it up? The basics: servers, firewalls, switches, internet links, and key services (email, file shares, line-of-business apps). A ping or health check every minute or two. If something stops answering, an alert fires. This is the least glamorous part and the most valuable — most outages are found this way, not by a user phoning in. 2. Is it about to break? Disk filling up, memory exhausted, a UPS battery degrading, a backup job that ran long or failed outright. Good monitoring watches trends, not just thresholds: a disk growing 2% a week will be full in a month, and that's a ticket today, not an outage later. 3. Is something behaving strangely? A login at 3 a.m. from a country nobody on your team is in. A server suddenly talking to an address it has never contacted. A mailbox rule created that forwards everything externally. These are the signals that matter most — and the ones a threshold alert will never catch on its own. They need someone who knows what "normal" looks like for your company. 4. Are the safety nets intact? Backups completed — and, more importantly, restorable. Patch levels current. Antivirus/EDR agents actually reporting in, not silently uninstalled or expired. Certificates not about to expire. The unglamorous checks are the ones that decide whether a bad day becomes a disaster. What it doesn't monitor It doesn't read your employees' minds — or their inboxes. A NOC sees that email is flowing; it doesn't know that the "invoice" your accounts person just opened was a phishing lure until the EDR or the user reports it. User awareness is still a layer no dashboard replaces. It doesn't fix your business processes. Monitoring will tell you the CRM is down. It won't tell you that half the sales team has been working around a broken workflow for six months. It measures systems, not whether the systems serve the work. It doesn't see inside encrypted traffic it has no business seeing. A NOC that promises to inspect everything is either breaking your privacy or overselling. The right scope is metadata and behaviour — what's connecting, how much, how often — not the contents of your team's work. It doesn't prevent everything. Monitoring shortens the time between "something broke" and "someone is fixing it." It does not make hardware immortal or attackers polite. Anyone selling zero downtime is selling a feeling, not a service. What to ask before you buy it Who answers the alert at 2 a.m., and how fast? A dashboard that pages a human is different from one that sends an email nobody reads until 9 a.m. Ask for the actual response times in writing. What's excluded? Printers, personal devices, that one server under someone's desk — the exclusions list is where surprises live. How do you know the backup works? "Backup completed successfully" is not proof. A restore test is. Ask how often restores are tested and whether you get the report. What does an alert look like to me? You should get a plain-language summary, not a raw log dump. If you can't understand the report, you can't judge the service. What happens when you find something? Monitoring without a defined response — who calls whom, who can approve a shutdown, who talks to your team — is just a more expensive way to watch things break. The short version A real NOC watches uptime, capacity trends, strange behaviour, and the safety nets — and it has a human who acts on what it sees. It doesn't watch your people, fix your processes, or promise nothin...

Powered by Readybot.io

**DEV Community** ## What a 24/7 NOC Actually Monitors — …

DEV Community

Building a touchscreen macro deck on an ESP32-P4 with LVGL 9.6

I wanted a Stream Deck-style macro pad without paying for a Stream Deck, so I built one on a GUITION JC1060P470C. It is a 7-inch, 1024 x 600 capacitive touchscreen on an ESP32-P4. The project is called Tessera Deck. It is MIT licensed, it runs on Windows 10 and 11, and the panel UI is LVGL 9.6 on ESP-IDF 6.1. This post covers the design choices behind the panel firmware and the host protocol, for anyone building something similar on an embedded display. The cover image is a Blender render made from the CAD model of the desk console. I have no photo of a printed one yet. The panel screenshots below are not photos either: the native PC build of the real UI code drew them, and the agents screen uses sample data. 1. Build the UI for the PC first The UI code compiles natively on the PC against the same LVGL sources as the firmware. A test feeds it touch input and renders 1024 x 600 frames. A layout bug shows up in seconds, with no flashing and no cable. I can also make screenshots for the README without photographing a screen at an angle. The catch is that the native build does not exercise the display driver, the touch controller or PSRAM limits. Those still need the board. I treat the native build as the place for UI logic and the board as the place for hardware surprises. 2. Keep the panel dumb The panel only draws and reports touches. A small Python host on the PC owns the USB Serial/JTAG port, runs the actions and serves the editor on 127.0.0.1. The two talk over a line protocol at 115200 baud. Each message is one UTF-8 line. The host sends text commands or one JSON object. The panel answers, and it also sends its own events as JSON lines. A key press looks like this, copied from the protocol page at v0.9.0: {"type":"deck_event","id":N,"button":N,"label":"…","hold":true}
hold is only present for a long press. Everything else about what the key does lives on the PC. That means I can add an action without touching the firmware, and the firmware stays small. There are limits to this design. The panel reads lines of up to 1,099 bytes and ignores a longer one, so the host sends a layout in 380-character chunks and an image in 160-byte chunks. A pressed key stays highlighted until the host answers, so without a host it flashes red at once. If the PC is off, the panel keeps the last layout on screen and the keys do nothing. 3. Update objects in place A touchscreen punishes rebuilds. If a gauge value arrived and the grid were rebuilt while you press a key, your finger would be on a new object that never saw the press, so I update in place. Now labels, colours and gauge values change on the existing LVGL objects. The grid is only rebuilt when the page or its layout changes. A key is never rebuilt under your finger. The same idea covers widgets. The host sends a title, a second line and a progress value, and the panel draws every wide key the same way. A new widget is a new host-side feature and needs no new firmware. 4. Treat icons as a cache problem Icons are the one thing that is too big to treat like text. The host sends each icon as RGBA with a CRC32 check, using these lines: AD_IMG_BEGIN
AD_IMG_DATA
AD_IMG_END
The panel only answers at the end: OK, INVALID or FULL. FULL means the cache cannot hold the image. The cache is 8 MiB in PSRAM. When it needs room it drops the least recently used image that no key is showing. If the panel needs an image it no longer has, it sends {"type":"image","id":"im:…"} and the host sends it again. Animated icons arrive as frames with a duration each. The CRC32 lets the panel answer INVALID for a corrupt image instead of showing it, and a corrupt icon is more annoying than a missing one. 5. Let the panel answer your AI agent When Claude Code wants permission to run something, the question appears on the panel with Allow and Deny buttons. The agents screen also shows what Claude Code, Codex and ZCode are doing and in which project. Setup adds hooks to ~/.claude...

Powered by Readybot.io

**DEV Community** ## Building a touchscreen macro deck on an …

DEV Community

Never let the model retype a number: how we read lab reports with two AI passes

People photograph a blood test report and ask "is this bad?". When we built the lab report explainer in Saluvo, the explanation turned out to be the easy part. The risky part is reading the numbers: a misplaced decimal turns a normal result into an alarming one, or the reverse. These are the design rules we ended up with. None of them need a special model, and they apply to any LLM pipeline that pulls numbers out of documents: invoices, prescriptions, bank statements. 1. Extraction is copying, not understanding The extraction prompt is deliberately dull: Copy every test row exactly as printed. Never guess a value; skip a row you cannot read. Values come back as strings exactly as printed ("7.8", "1,250", "<0.5"), at temperature 0, in a strict JSON shape. Reference ranges come back twice: as printed text ("4.0 - 5.6") and as numbers where they can be parsed (refLow, refHigh). A row the model can't read is skipped, not reconstructed from what's "usually" there. The prompt also handles the boring failures: if the photo isn't a lab report at all, the model returns {"markers": [], "notReport": true} and the user is asked for a clearer photo of the results page. 2. Read it twice, independently Two extraction calls run in parallel on the same image or PDF text: const [first, second] = await Promise.allSettled([
extract(MODELS[mode].precise, input, meter),
extract(MODELS.normal.precise, input, meter),
]);
Promise.allSettled, not Promise.all: if one reader fails or times out, the other's result is still used. If both fail, the user gets an error, never a half-guessed table. Then the two readings are matched row by row. Names are normalised ("HbA1c", "Hb A1c" and "HBA1C" become the same key), and values are compared exactly as numbers: function crossCheck(primary: Marker[], second: Marker[] | null): Marker[] {
if (!second?.length) return primary;
const other = new Map(second.map((m) => [keyOf(m.name), m]));
return primary.map((m) => {
const o = other.get(keyOf(m.name));
if (!o) return m;
const a = numberOf(m.value), b = numberOf(o.value);
const same = Number.isFinite(a) && Number.isFinite(b) ? Math.abs(a - b) < 1e-9 : m.value === o.value;
return same ? { ...m, verified: true } : { ...m, verified: false, altValue: o.value };
});
}
No tolerance band. If one reader saw 5.6 and the other 5.8, that's a disagreement, because on a lab report it might be the whole story. 3. When they disagree, ask the person holding the paper A disagreement isn't resolved by a third model or a vote. The row is shown with both readings and the user picks the one that matches their paper. Confirming a value also recomputes its flag: export function confirmMarker(m: Marker, value: string): Marker {
return { ...normaliseMarker({ ...m, value })!, verified: true };
}
The person has the original in their hand. Asking them is cheaper, faster and more honest than any amount of model cleverness, and it makes the tool say clearly what it couldn't read. 4. Don't trust the model's "high" or "low" The extraction returns a flag for each row, but the app doesn't use it when it can do the maths itself: if (Number.isFinite(num) && (refLow !== null || refHigh !== null)) {
flag = refLow !== null && num < refLow ? "low"
: refHigh !== null && num > refHigh ? "high"
: "normal";
}
The flag comes from the printed number and the printed range. The model's label is only a fallback for rows where the range isn't numeric. 5. The explaining model never sees the values This is the rule I'd most recommend stealing. The second stage writes the plain-words explanation, the questions to ask the doctor and which kind of doctor to see. It receives each marker's name, unit, printed range and flag, but not the value: prompt: JSON.stringify(markers.map(({ name, unit, refText, flag }) => ({ name, unit, refText, flag }))),
And its instructions say: don't write the patient's result values anywhere; the app shows the exact numbers next to your te...

Powered by Readybot.io

**DEV Community** ## Never let the model retype a number: …

DEV Community

Escanear la aplicación publicada: Nuclei y Wapiti en Campus Tareas

Autor: Antony Piero Solorzano Zegarra Proyecto desarrollado con: Adriana Rafaela Laos Gonzáles Campus Tareas ya estaba publicada en GitHub Pages y guardaba sus datos en Cloud Firestore. Para esta actividad se revisó la respuesta de la aplicación con Nuclei y Wapiti. Ambas figuran en la lista de escáneres de OWASP. Se eligieron herramientas distintas de SonarCloud, Snyk y Semgrep, presentes en los workflows académicos revisados. Qué se analizó La URL examinada fue https://antonys3010.github.io/campus-tareas-nosql/. El alcance se limitó a esa página, sus cabeceras HTTP, su política CSP y los atributos de las cookies que pudiera entregar. No se utilizaron credenciales de Firebase. Nuclei ejecutó una sola plantilla oficial firmada, http-missing-security-headers, con una petición por segundo y concurrencia de una plantilla. Wapiti utilizó únicamente los módulos csp, http_headers y cookieflags, con alcance url y sin navegador headless. No se activaron módulos de inyección ni se analizaron las operaciones autenticadas de Firestore. Los resultados no permiten afirmar que toda la aplicación sea segura ni que las reglas de la base de datos sean incorrectas. Instalación y ejecución Se fijaron Nuclei 3.11.1 y Wapiti 3.3.2. El instalador incluido en el repositorio descarga la distribución oficial de Nuclei y comprueba su SHA-256. Para reproducir el análisis desde el repositorio: python -m venv .venv
. .venv/bin/activate
python -m pip install -r security/requirements.txt
python scripts/install-nuclei.py .tools
PATH="$PWD/.tools:$PATH" python scripts/security-scan.py
El script comprueba primero que la URL responda con HTTP 200. También compara el SHA-256 del HTML recibido con public/index.html, para vincular el análisis con el código del checkout. Durante la primera prueba local, Nuclei no pudo resolver el nombre del sitio. Aunque terminó sin coincidencias, ese resultado no se tomó como un escaneo limpio. Se configuró el proxy ya disponible en el entorno y se repitió la ejecución. La nueva prueba confirmó una respuesta HTTP y registró los hallazgos. Los resultados Nuclei reportó 9 coincidencias informativas por cabeceras ausentes. Wapiti reportó 3 hallazgos bajos: Categoría de Wapiti Observación Content Security Policy Configuration No se encontró una política CSP Clickjacking Protection No se encontró X-Frame-Options MIME Type Confusion No se encontró X-Content-Type-Options Los tres puntos también aparecen entre las comprobaciones de Nuclei. No corresponde sumar ambos resultados como doce vulnerabilidades independientes. Una cabecera ausente es un hallazgo de configuración; por sí sola no demuestra una explotación de XSS o la lectura de documentos ajenos. HSTS y el tipo de contenido HTML con charset sí estaban presentes. Las demás cabeceras se deben evaluar según las necesidades de la app: activar restricciones sin comprobar su efecto puede romper recursos externos. Lo declarado en un archivo y la respuesta real El repositorio conserva un firebase.json que declara cabeceras de seguridad. Sin embargo, la web se sirve desde GitHub Pages. Esa configuración de Firebase no modifica la respuesta de GitHub Pages. Encontrar nosniff en un archivo no basta para afirmar que el servidor lo envía. La respuesta publicada es lo que debe comprobarse. Los hallazgos se dejaron documentados y no se presentan como corregidos. Para administrar estas cabeceras habría que utilizar un alojamiento o proxy que permita configurarlas y volver a probar el funcionamiento del SDK de Firebase. Automatización después del despliegue La app se despliega mediante GitHub Actions. Cuando termina correctamente el workflow Comprobar y publicar Campus, otro workflow ejecuta Nuclei y Wapiti. También admite ejecución manual. La ejecución verificada terminó correctamente y coincidió con la prueba local. Conserva los reportes JSON, registros, metadatos y resumen como artefactos durante 30 días. La evidencia inicial también está versionada en el repositorio. Un estado verde indica...

Powered by Readybot.io

**DEV Community** ## Escanear la aplicación publicada: Nuclei y Wapiti …

DEV Community

Webhook Retry Queue: Delayed Jobs and Idempotency in Node.js

Short answer: treat a webhook retry queue as an at-least-once transport, and make the durable business change idempotent with a stable event identity and a database transaction. The deciding constraint is the gap between doing work and acknowledging it. A worker can commit a change, lose its process before the acknowledgement is recorded, and receive the same job again. No reasonable retry policy can infer whether that first change happened. Delayed jobs help recovery; they do not close that gap. For an AI application, that distinction matters twice. A repeated state change can corrupt customer state, while a repeated call to a model or external API can consume latency and usage budget without producing a new result. Keep the queue simple, but make its boundaries explicit. Why do webhook retry queues create duplicate processing with delayed jobs and at-least-once delivery? There are usually two independent retry loops. The webhook sender retries when it does not receive a timely success response. The receiving system may then retry its queued work after a timeout, a process restart, or an unavailable dependency. Both loops are valid, and either can place the same logical event in front of a handler more than once. Consider a delivery carrying evt_482. The receiver accepts it and places it in durable work storage. A worker charges ahead: it writes the new subscription status, commits the transaction, and is interrupted before its queue acknowledgement becomes durable. On recovery, the queue sees an unacknowledged attempt and makes evt_482 runnable again. The second worker is not seeing a new customer action. It is seeing an unresolved delivery outcome. That is why "exactly once" is a misleading promise for most distributed systems. The sender, queue, worker, database, and any downstream service do not share one atomic commit. The useful target is one durable business effect for each logical event, even though execution can repeat. A scheduled retry introduces a separate ordering question. If an older account update waits in a delayed set while a newer update completes, blindly applying the older message later can roll state back. Put a producer version, sequence, or monotonic timestamp in the event when the domain has an ordering rule. The transaction that claims the event should also reject a stale transition. The small failures are the dangerous ones. A 200 response proves that a hop responded; it does not prove that an intended state exists after a crash boundary. Put the idempotency boundary around the business effect Start with an event key that is stable across every delivery. A producer-supplied event ID is ideal. When the protocol does not offer one, derive a deterministic key from immutable business identifiers, the event type, and a source version. Generating a random key on receipt defeats deduplication because each retry looks unrelated. Then claim that key in durable storage. An in-memory Set vanishes on restart and cannot coordinate multiple Node.js processes. A read followed by an insert is also insufficient: two workers can both observe an absent row before either write wins. A unique constraint or equivalent conditional insert gives the database one clear arbitration point. One key, one ledger. Here is the full decision path worth testing before calling a handler safe. A signed delivery with evt_482 arrives twice because the sender did not observe the receiver's first response; both requests validate to the same identity, and both attempts try to enqueue work. Queue-level deduplication may collapse those attempts, but it is an efficiency optimization rather than proof, since one request may have reached the queue through a different path or after queue history has been cleaned up. A worker claims evt_482 and writes the account status. If that worker finishes normally, it acknowledges the job. If it exits after the transaction and before the acknowledgement, another worker can later receive the same job. Its...

Powered by Readybot.io

**DEV Community** ## Webhook Retry Queue: Delayed Jobs and Idempotency …

DEV Community

Compare Cloud Storage Providers for Direct Browser Uploads — Node.js Retention Rules

Property management teams should choose browser-upload storage by its trust boundary and retention controls, not by whether its SDK can mint a presigned URL. For training artifacts, the browser should receive permission for one object, for a short window, with a server-owned key and a separately enforced retention policy. The storage provider matters after those rules are clear. A useful design has three planes: a Node.js service authorizes an upload, the browser sends bytes directly to object storage, and a worker records completion and applies retention metadata. That separation keeps credentials out of the browser, but it does not make the browser trustworthy. It can still retry, abandon a multipart upload, or send an unexpected content type. I treat those as normal states, not exceptional ones. Ownership decides the boundary. No exceptions. What is the real trust boundary for a training upload? A presigned URL is a bearer capability. Anyone who obtains it can use it until it expires or its constraints stop matching the request. The API should therefore decide the archive identity before it signs anything: property, course, employee or cohort, artifact type, maximum size, and an immutable object key. Do not accept a client-provided path such as training/finished.pdf and then try to sanitize it later. For a property management system, an upload record might begin as pending, include the policy version that selected its retention class, and contain a digest once the object is verified. The browser gets an upload URL and an opaque upload ID. It does not get a storage access key, a bucket-wide listing permission, or permission to choose a retention date. CORS is part of this boundary, but it is not authorization. It controls which browser origins may read or send certain cross-origin requests; it does not stop a non-browser client from using a valid bearer URL. Keep the allowed origins narrow, expose only the headers the upload flow needs, and test preflight behavior with the actual headers used by the browser. A permissive wildcard can hide a broken design during development. How should the retention policy shape the upload path? Start with the record, not the bucket. A policy table can map annual_safety_training to a retention class such as 7_years, while draft_course_material maps to 90_days. Store the selected class and policy version with the pending upload. At completion, the service verifies the object and queues the archive event. A later policy change should affect new records unless governance explicitly schedules a migration; silently rewriting old decisions is a difficult audit story. The object key should be uninteresting and collision-resistant, for example properties/42/training/2026/10/opaque-id. The human filename belongs in metadata or the application database. This makes retries idempotent and prevents a filename from becoming an authorization primitive. It also gives the cleanup worker a stable query key when an employee uploads the same certificate twice. S3 lifecycle rules are useful for expiration and transitions, but they are not a substitute for the policy record. The lifecycle documentation describes rules that can act on objects by prefixes, tags, or object age. Those selectors are operational tools. The application still needs to explain why this object received a particular tag and which policy version produced it. How should cloud storage providers handle direct browser uploads? The comparison becomes clearer after the contract is fixed. Amazon S3, Cloudflare R2, and Wasabi all present S3-compatible patterns that can support direct uploads, but compatibility is not sameness. Check the exact signing behavior, multipart limits, CORS configuration surface, checksum support, lifecycle selectors, and event delivery available in the region and account you will use. For a property management archive, I would put that contract in a test matrix before anyone debates a default provider. Provider Direct u...

Powered by Readybot.io

**DEV Community** ## Compare Cloud Storage Providers for Direct Browser …

DEV Community

Qué significan las alertas de Nuclei y Wapiti en una app de tareas

Autora: Adriana Rafaela Laos Gonzáles Proyecto desarrollado con: Antony Piero Solorzano Zegarra Artículo de Adriana Rafaela Laos Gonzáles, publicado desde la cuenta de Antony Piero Solorzano Zegarra, integrante del mismo equipo. Un escáner puede terminar con varias advertencias sin que todas representen fallos distintos o tengan el mismo impacto. En esta actividad se analizó Campus Tareas, una aplicación con JavaScript y Firestore publicada en GitHub Pages. Se utilizaron Nuclei y Wapiti para revisar la configuración observable desde su URL pública. Ambas herramientas figuran en la lista de OWASP. Se excluyeron SonarCloud, Snyk y Semgrep, presentes en los workflows académicos revisados. Preparar una ejecución reproducible La aplicación tiene un repositorio público y se despliega con GitHub Actions. El nuevo análisis se ejecuta después de un despliegue correcto. Las versiones de las herramientas quedaron fijadas: Nuclei 3.11.1 y Wapiti 3.3.2. El script de Nuclei emplea la plantilla oficial firmada http-missing-security-headers. Wapiti se limita a csp, http_headers y cookieflags. No se activaron módulos de inyección o fuerza bruta y no se entregaron credenciales del navegador. Antes de ejecutar los escáneres, el script comprueba la respuesta HTTP y compara el hash del HTML publicado con el del archivo del repositorio. Así se evita confundir un error de conexión o una versión diferente de la web con un resultado válido. Nueve coincidencias y tres hallazgos Nuclei encontró 9 coincidencias informativas, todas relacionadas con cabeceras de seguridad ausentes. Wapiti clasificó 3 hallazgos como bajos: CSP, protección contra clickjacking y protección frente a confusión de tipos MIME. Los resultados coinciden en parte. Se refieren a una misma respuesta HTTP y varias advertencias señalan la misma configuración. Por eso, no se sumaron como doce vulnerabilidades independientes. La severidad también pertenece al contexto de cada herramienta. Que Nuclei marque una coincidencia como informativa y Wapiti la incluya entre sus hallazgos bajos no es una contradicción: emplean categorías y criterios diferentes. Revisar el impacto de cada advertencia Una CSP define restricciones para los recursos que puede cargar una página. La ausencia de esa política reduce una capa de defensa, pero no demuestra que exista una inyección de JavaScript. En esta app, el contenido de las tareas se inserta con textContent; el escaneo realizado no identificó una explotación de XSS. La advertencia de clickjacking señala que no se encontró X-Frame-Options. Se debería evaluar una política de inclusión en marcos mediante esa cabecera o mediante frame-ancestors en una cabecera CSP. No basta con añadir cualquier directiva en una etiqueta HTML: algunas protecciones requieren una cabecera de respuesta. X-Content-Type-Options: nosniff indica al navegador que respete el tipo de contenido declarado. La ausencia de esa cabecera debe revisarse junto con los tipos MIME reales que entrega el servidor. El análisis sí observó un tipo de contenido HTML con charset y HSTS en la respuesta. El alojamiento también forma parte del resultado El archivo firebase.json contiene una configuración de cabeceras, pero la web se aloja en GitHub Pages. Esa configuración no se aplica a este proveedor. La revisión de la respuesta confirmó la diferencia entre lo declarado en un archivo y lo que recibe el navegador. Para corregir las cabeceras se necesita un alojamiento o proxy que permita controlarlas. También hay que probar que una CSP no impida las conexiones con Firebase o la carga de sus módulos. En esta actividad se documentaron las medidas posibles, sin afirmar que los hallazgos ya estén corregidos. Conservar la evidencia El workflow guarda los reportes originales de las dos herramientas, sus registros y un resumen. Los archivos permiten revisar el objetivo, las versiones, el commit y la fecha de ejecución. Si las herramientas fallan, el trabajo debe señalarlo; no debe convertir una ausencia de ...

Powered by Readybot.io

**DEV Community** ## Qué significan las alertas de Nuclei y …

DEV Community

Configure a Durable Queue or Fast Streams for Rate-Limiting HTTP-Based Logistics Webhooks

Configure a Durable Queue or Fast Streams for Rate-Limiting HTTP-Based Logistics Webhooks To configure a queue for rate-limiting HTTP-based webhook processing in a logistics renewal workflow, choose a durable queue when missing a business deadline is worse than adding a few milliseconds of handoff time. Choose a fast stream only when the downstream contract can tolerate replay, retention, and consumer-management work. The queue is not the rate limiter. It is the shock absorber; the worker-side limiter is the clock that protects the webhook destination. Here is the decision matrix I use before writing a producer: Constraint Durable queue Fast stream Reminder must survive a worker restart Strong fit Possible, with explicit retention and offsets Burst arrival versus fixed webhook quota Pair with a token bucket Pair with a token bucket One consumer should own each job Natural model Requires consumer-group rules Operators need a dead-letter path Built-in pattern in many systems Application convention Lowest possible handoff latency matters Usually not the first choice Better candidate My default is the durable option for renewal reminders. The decision changes when the deadline is soft, the destination is idempotent, and the team already operates stream offsets confidently. That is the whole fork. How should I configure a queue for rate-limiting HTTP-based webhook processing? A logistics renewal reminder has at least three times attached to it: when the carrier account became eligible, when the business wants a reminder sent, and when the webhook receiver will reject a late request. Confusing those timestamps creates a queue that looks healthy while delivering useless work. Store the intended delivery time with the job. Do not use queue arrival as a substitute. A cron task can poll for eligible records every minute, publish them, and exit. That task should not perform the HTTP call itself. A short cron run that discovers 10,000 renewals can otherwise create a burst against a receiver that permits 20 requests per second. The worker needs two independent gates. First, it waits until the job is eligible. Second, it acquires a rate-limit token for the receiver and route. The first gate protects business timing; the second protects an external system. A retry belongs behind both gates, or a failed request can immediately consume the whole retry budget in a tight loop. This is the small contract I want between the scheduler and the worker: type RenewalReminder = {
id: string;
accountId: string;
destination: string;
dueAt: string;
attempt: number;
idempotencyKey: string;
};

function isEligible(job: RenewalReminder, now: Date): boolean {
return Date.parse(job.dueAt) <= now.getTime();
}
The payload is deliberately boring. An immutable job id, an explicit due time, an attempt count, and an idempotency key give the worker enough information to make a decision after a restart. A URL alone does not. The trade-off is intentional: more fields make the contract larger, but they make delayed processing inspectable. No shortcuts. Where should rate limiting live? Put the limit at the narrowest boundary that owns the quota. If a receiver allows 20 requests per second per account, a process-wide limiter is wrong as soon as two accounts share a worker. If the quota is per destination, key the limiter by destination. If the receiver documents a global quota, use a shared limiter or partition work so each worker sees the same budget. A token bucket is a useful mental model: tokens arrive at a fixed rate up to a capacity, and each HTTP attempt spends one. Capacity controls the burst. Refill rate controls the sustained pace. Do not let the queue's concurrency setting pretend to be a rate limit. Ten concurrent requests can still violate a per-second quota, especially when response times vary. The limitation is operational: a shared limiter needs shared state, while a local limiter is easier to run but cannot enforce a global quota. The wo...

Powered by Readybot.io

**DEV Community** ## Configure a Durable Queue or Fast Streams …

DEV Community

I built a decision graph on HydraDB that learns from corrections. Here's what broke.

Agents keep acting on engineering decisions that were reversed years ago. A 48-hour HydraDB build, the bugs I hit, and an honest baseline. Every engineering team writes its decisions down. Then it changes its mind and writes another one. Two years later, search finds the old decision as easily as the new one, and an AI coding agent will happily build on a choice the team reversed long ago. For the HydraDB hackathon I built DecisionGraph to fix that. It links each architecture decision record (ADR) to the records that replaced it. When an agent asks "what did we decide about X?", it walks those links to the decision that still stands and hands back that decision and its history, in about 130 tokens instead of 590. When someone corrects an answer, the correction is written into the graph, so the next question on that topic lands right even in different words. Live app: https://decisiongraph-latest.onrender.com Code: https://github.com/0xNexuz/decisiongraph Video walkthrough: https://youtu.be/ChWL-frv5v8 The problem in one picture Asked "Does each business unit get its own Cloud Platform cluster now?", vector search returns the closest text: a 2019 decision to run one shared cluster, which has been replaced three times since. DecisionGraph starts at the same record, then follows the "replaced by" links to the decision that stands today. The data is about 130 real, public ADRs from three UK government engineering teams: MoJ Cloud Platform, MoJ Modernisation Platform and GOV.UK on AWS (all MIT licensed). The test set is 50 questions I wrote, scored before each correction is applied. How it works Find the decision. Vector similarity, distinctive terms and hints from past corrections each vote for where the question starts. Walk to what replaced it. One HydraDB path query follows trusted "replaced by" links until it reaches a decision nothing has replaced. Learn from the correction. The links on the right path are confirmed, the wrong ones rejected, and the question is stored as a node so the next one like it starts in the right place. Where HydraDB earned its place Trust lives in the path cost Every REPLACED_BY link has a cost: 1 if trusted, 100 if not. Walking only trusted links is then a single algo.MSpaths call with maxCost set to the path length, so one untrusted hop rules the path out: CALL algo.MSpaths({
sourceLabel: 'Decision', sourceProperty: 'key',
sourceValues: ['cloud-platform:12'], targetValues: [...],
relTypes: ['REPLACED_BY'], relDirection: 'outgoing',
maxLen: 5, pathCount: 1, pairwise: false,
weightProp: 'cost', costProp: 'cost', maxCost: 5
}) YIELD path RETURN path
"Follow only what we trust" stopped being a loop in my Python and became one query inside the database. Learning is a write, not a retrain Links come from rules: an ADR's status line naming its replacement, a body that cites an older ADR next to "replace" or "migrate", and so on. Each rule has a trust score (a Beta count). When a correction proves a rule right or wrong, the new trust flows to every link that rule proposed that nobody has judged yet, in one statement: MATCH (x:Decision)-[r:REPLACED_BY]->(y)
WHERE r.rule = $rule AND r.state = 'candidate'
SET r.w = $w, r.cost = $c
The graph that answers the next question is already the corrected one. Memory is part of the graph Every corrected question becomes a Question node with a RESOLVED_TO edge to the right decision, an ABOUT edge to the topic and IN_QUESTION edges from its distinctive terms. "Has someone asked this before?" is a two-hop walk from the new question's terms, not a separate cache. Two cells, two worlds The public demo runs on one HydraDB cell. The "replay all 50 corrections" demo runs on a second cell, so a visitor pressing replay never resets anyone else's graph. Self-hosted for $0 HydraDB is built from source into one Docker image with the app. GitHub Actions builds the image and pushes it to GitHub's container registry, and it runs on Render's free tier. What broke, and h...

Powered by Readybot.io

**DEV Community** ## I built a decision graph on HydraDB …

DEV Community

Object destructing , Array destructing

Object destructing It unpacks the properties from an object and stores the value into new variable in one step eg: const name = props.name function child({name}) -- collecting them as a separate variable so we don't want to call it as repeatly like props.name, props.age props props... with the help of destructing we reduce it. Array destructing Similar to Object destructing but the only difference is array are taken by the position. eg: const skills = [ "JS" , " React" , "CSS"] const [skills1, skills2, skills3] = skills console.log(skills1) //js

Powered by Readybot.io

**DEV Community** ## Object destructing , Array destructing Object destructing …

DEV Community

The model was the easy part

A retail brand doesn’t ask how many people walked into a store. It asks why a store with high footfall isn’t selling, which stores deserve more stock before the festive season, and where to open the next one. Answering those questions from a camera feed takes far more than a model that can detect people. I had already shipped multiple features with AI-assisted workflows, and in earlier projects the work usually started and ended with a technical problem. Camera Analytics for retail was different. I was building an ML pipeline for computer vision, and getting the model to work turned out to be the smaller part of the job. The real product was everything around it: understanding what retailers needed, making it work in stores that were all set up differently, and keeping it reliable as features and data grew. Retailers didn’t want a footfall counter Retail brands wanted more than a count of how many people walked in. They wanted to understand customer behaviour: how long people browsed, whether they were greeted, and whether they came in alone or as part of a group. Zones turn a camera view into behaviour: greeting at the entrance, browsing on the floor, and conversion at the billing counter. AI-generated image. Those details fed much bigger decisions. Brands could compare footfall with sales, understand festive shopping trends, and use store performance to plan inventory and new store openings. I spoke with retail clients in India and Poland to understand how they would use this. What surprised me was how far these granular details travelled, well beyond the store floor into decisions across the business. The needs also varied from one brand and store to the next, so understanding the use cases early shaped a product that could support more than one setup. That early focus on real use cases shows in the results: the product converts more than 90% of proofs of concept (POCs) into customers. Every store is a different problem No two stores were set up the same way, and each one brought its own problems. Even a single camera angle changing could affect the setup and the results. If every adjustment needed an engineer, onboarding more stores would become the bottleneck. So I built admin-level controls and an Electron app that handle much of the onboarding work. Someone who understands the system and the model can use it to reconfigure a store remotely, including when a camera angle changes. Documenting the use cases and working through these requirements before building made the biggest difference. The work stopped being about implementing a feature and became about designing how the product would actually be used and maintained. The infrastructure is part of the product Every new pipeline stage or downstream task added load to the system. Before releasing a feature, I needed to know how it affected worker throughput, GPU load and power usage. The goal was to process as much data as possible without throttling. With large volumes of data, each new feature meant benchmarking, training the pipeline, reviewing results and stress-testing the server under high load. Grafana alerts helped me monitor it, but I still wanted to see how the system behaved under real test conditions before trusting it. The next step is moving parts of the pipeline from Python to C++ to reduce the impact of the GIL on parallel execution. Early tests show it is already reducing that bottleneck, though completing the migration will take time. Where the textbook got it wrong Most optimisation advice for GPU inference pipelines is sound in general. On our hardware and under our load, a couple of the obvious moves didn’t do what the textbook promised. Each time, the lesson was the same: measure what is actually happening before deciding what to optimise. For context, the detection model runs on the GPU, while most of the preprocessing happens on the CPU. That split between a fast GPU and a busy host shapes everything below. Detection and tracking on a busy stor...

Powered by Readybot.io

**DEV Community** ## The model was the easy part A …

DEV Community

LLM Weights are Hardcoded Intelligence

Hey everyone 👋, I need to say something upfront that I know some people are going to push back on, and I want to get the disclaimer out of the way before the argument starts. This post is a critique. Not a hate piece, not a dismissal, and not the kind of hot take that evaporates the moment someone asks a follow-up question. I have been building toward this argument across nearly every AI-related post I have written, from LLMs are Useful. LMMs will Break Reality, where I tried to be fair about what LLMs genuinely accomplish, to Training Is an Evil Concept. LMMs Eliminates it Altogether., where I argued that the entire training paradigm carries moral weight that the industry refuses to examine, to Intelligence at Rest, where I unpacked what it actually means for a system to store something worth calling intelligence. Each of those posts was pointing at the same underlying problem from a slightly different angle, and in this post I want to point directly at the center of that problem and name it plainly. The problem is this: LLM weights are hardcoded intelligence. And hardcoded intelligence, no matter how impressively hardcoded it is, is not the same thing as intelligence. It is a fossil of intelligence, a snapshot, a frozen record of what the world looked like at training time, compressed into billions of floating-point numbers that will never change again on their own. I am not saying that is worthless. I am saying that is a fundamentally different thing from what people are claiming it is, and the gap between the claim and the reality is exactly where the most dangerous illusions live. I want to be honest about my own position before anything else, because I think honesty is the only thing that makes a critique worth reading. I am not an established academic with ten published papers on neural network theory. I am a software engineer who has spent years watching the AI industry make increasingly extravagant promises, using language that is increasingly disconnected from the actual mechanics of what these systems do, and I have watched people bet careers and livelihoods and policy decisions on those promises. I wrote in Technology Has Destroyed My Livelihood about how the technology industry dismantled things that mattered to people while calling it progress, and the AI industry is doing something very similar right now with the concept of intelligence itself. It is taking a specific, limited, technically impressive capability, compressing it into a word, and selling the word to the world at a markup that the word cannot support. I am writing this post because I think the gap between the word and the reality deserves to be named clearly, and because the alternative I believe in, which is what we have been building with the LMM project, is real enough and grounded enough to present as an honest alternative. That is what I am going to do in the sections below, and I am going to try to do it without being unfair to the technology I am critiquing. The Weight Is All There Is Let me start at the most basic technical level, because the argument I am making is ultimately a technical one, and I do not want to dress it in philosophy when the engineering is enough. A large language model, in its final deployed form, is a file. It is a collection of tensors, arrays of floating-point numbers, organized according to a specific architecture. For something like GPT-3, that file contains approximately 175 billion individual float values. For GPT-6, the number is estimated to be in the trillions. Each one of those numbers is the result of an optimization process called gradient descent, which ran over hundreds of billions of tokens of text, adjusting each number very slightly with each pass, until the collective behavior of all the numbers together minimized a loss function called cross-entropy, which measures how well the model predicts the next token in a sequence. When that process ends, the numbers are saved. They are written to disk. T...

Powered by Readybot.io

**DEV Community** ## LLM Weights are Hardcoded Intelligence Hey everyone …

DEV Community

Ukrainian scans, invoices, Word and Excel to Markdown - 5 Apify Actors on 7 files, only one got every table right

Search the Apify Store for "PDF to Markdown" and you get dozens of Actors with almost the same promise: text, tables, OCR, ready for RAG. None of them tells you what happens with a Ukrainian scan, an Excel file or a scanned invoice whose table has no lines. So I ran five of them on the same seven files and compared every output with the text the files were made from. All results below are from real runs on 15 November 2026. Disclosure: one of the five is mine, Document to Markdown for AI. Every number below comes from a real run, the test files are public and the raw outputs are kept, so you can repeat and check all of it. Short answer Files right (of 7) Invoice without lines: rows exact Reads Word and Excel Price per scanned page ai_admin/document-to-markdown 7 5 of 5 yes $0.004 nidamindllc/doc-ocr 2 4 of 5 no (error) $0.004 lergassy/pdf-text-extractor 1 0 yes $0.0111 fabrikit/document-to-markdown 2 0 yes $0.0105 gochujang/pdf-text-extractor 1 0 (empty) no (PDF only) $0.006 "Right" means the text, the heading and the table with the correct rows and with every number exactly as in the document. The seven files All Ukrainian, all generated from known text, so every character can be checked: File What it is text-uk.pdf PDF with a text layer: heading, two sentences, a 3 x 3 table with prices like 96,50 scan-uk.pdf the same page as a clean scan scan-noisy-uk.pdf the same scan with noise and a slight tilt doc-uk.docx Word document with the text, a list and the table sheet-uk.xlsx Excel workbook: the table with a Total row 1,234.50, and a sheet with the sentences scan-borderless-uk.pdf a scanned invoice: 5 x 6 table without any lines, amounts like 40 530,00 and 2 500 scan-noisy-borderless-uk.pdf the same invoice, noisy and tilted The last two are the hard case and the most common one: most invoices, waybills and bank statements are printed without table lines. The files are public: https://api.apify.com/v2/key-value-stores/FloomuI2DrQCJazY8/records/. The Actors are the most used ones for this search plus one that lists Office formats. Settings: Ukrainian OCR wherever the Actor has a language option (ukr); nidamindllc has no language option. Results, file by file File mine nidamindllc lergassy fabrikit gochujang Text PDF right one OCR typo, table right (HTML) table flattened into the text table as plain lines right Clean scan right right (HTML table) table as broken lines table lost empty Noisy scan right right (HTML table) table as broken lines rows without header empty DOCX right error right right not supported XLSX right, 1,234.50 kept error 1,234.50 became 1234.5 right not supported Invoice, no lines 5/5 rows, 30/30 cells 4/5 rows, 29/30 cells no table, 40 530,00 lost its space no table, all in one line, 96,50 read as 96,0 empty Invoice, no lines, noisy 5/5 rows, 30/30 cells 4/5 rows, 29/30 cells no table, 96,50 read as 96,60 no table, all in one line empty This is what my Actor returned for the noisy invoice without lines - every cell, every number and the № sign as in the document: Рахунок-фактура № 47

Постачальник: ТОВ «Будмайстер», м. Львів. Покупець: ФОП Коваленко О. В. Оплата протягом п'яти банківських днів з дати рахунку.

№ Товар Од. Кількість Ціна Сума 1 Цемент М500 мішок 420 96,50 40 530,00 2 Арматура 12 мм шт. 180 412,00 74 160,00 3 Пісок річковий т 12 650,00 7 800,00 4 Цегла рядова шт. 2 500 9,80 24 500,00

Разом: 146 990,00 грн
The same table also comes as rows of cells in the tables field, ready for a spreadsheet or a database, and the record says how many pages went through OCR, so a pipeline knows which text came from a scan. For comparison, the same line of the invoice from two other Actors: lergassy: 1 | Цемент М500 мішок 420 96,60 40530,00
fabrikit: ... Мо Товар Од. Кількість Ціна Сума 1 Цемент М500 мішок 420 96,50 40 530,00 2 Арматура12мм шт. ...
What stood out gochujang returned empt...

Powered by Readybot.io

**DEV Community** ## Ukrainian scans, invoices, Word and Excel to …