Pixel

Pixel Updates & Events

Server Updates & Events

Upcoming Events

Announcements

DEV Community

Ticketing feeds lie a little: cleaning concert listings from six partner APIs

events.lat is a calendar of concerts, festivals and cultural events in Latin America: more than 3,000 upcoming events in over 100 cities, in English, Spanish and Portuguese. Almost none of it is typed in by hand. It comes from partner feeds: Ticketmaster, Eventbrite, Sympla, Bandsintown, Songkick and Luma. Each feed is fine for what it was built for: selling tickets. Put them all on one browse page, though, and you see things that make no sense to someone looking for something to do on Saturday. Here are the three problems we hit, and the small, boring functions that fixed them. 1. Add-ons that look like events Partners publish ticket upsells as their own events. A "Hi-Touch" photo session sold with the note boleto de evento no incluido (event ticket not included). A pre-signed poster. A parking pass for a festival. In the feed they have a title, a date and a venue, just like the concert. The fix is a pattern on the title: const ADDON_PATTERN =
/(boleto de evento no in?cluido|no incluye boleto|entrada no incluida|ticket not included|hi[\s-]?(touch|bye)|pre[\s-]?signed|meet ?& ?greet|soundcheck|^\sestacionamiento\b|\b(estacionamiento|parking)\s$|\bupgrade\b)/i;

const isTicketAddon = (e: { title?: string | null }) =>
ADDON_PATTERN.test(String(e.title ?? ""));
The hard part is what to leave out. An Abono … is a season or multi-day pass, and a Grada 15, Formula 1 … is a grandstand tier. Both look like add-ons, but they are real tickets to a real event, so filtering them would hide things a visitor can actually buy. Parking had a bug of its own. The first version matched the word at the start of a title, and that hid an event called "Parking Lot Party". Now English "parking" only counts as the last word ("Festival Vaivén 2026 Estacionamiento" is a car park; "Parking Lot Party" is a party). Spanish estacionamiento is safe at either end. Each of those cases is now a test. 2. One show, ten listings A theatre run comes in once per performance. El Fantasma de la Ópera playing ten nights at one theatre filled ten slots on the page, and pushed everything else off it. So we group performances into one production with a key built from the title and the venue: const fold = (v: unknown) =>
String(v ?? "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");

function productionKey(e: { title?: string | null; venue?: string | null; city?: string | null }) {
const title = fold(e.title)
.replace(/\b(19|20)\d{2}\b/g, "") // "… 2026" on some nights, not others
.replace(/[^a-z0-9]+/g, " ")
.trim();
return ${title}::${fold(e.venue ?? e.city).trim()};
}
Accents, case, punctuation and the year all go, because partner titles for the same run vary from night to night, for example "Ópera" on one and "Opera" on another, with or without "2026". The venue stays in the key, so the same touring show in two cities is still two listings. Then we keep only the soonest performance of each production, in the order the rows came in: type Listing = { title?: string | null; venue?: string | null; city?: string | null; event_date?: string | null };

function dedupeListing(events: T[] | null | undefined): T[] {
const seen = new Set();
const out: T[] = [];
for (const e of events ?? []) {
if (isTicketAddon(e) || isImplausibleEvent(e)) continue;
const key = productionKey(e);
if (seen.has(key)) continue;
seen.add(key);
out.push(e);
}
return out;
}
One rule we hold to: these helpers only remove noise. They never reorder by "quality", because someone looking for a specific date has to find that date where they expect it. The list stays in date order. Since the list can shrink, callers ask for a few extra rows. 3. Rows that can't be real Some feeds leave out the location and the importer fills in a placeholder. We found an event in a city called "Nocity", dated 31 December 2039. const PLACEHOLDER_CITIES = new Set(["nocity", "no city", "n/a", "na", "unknown", "tba", "tbd", "-"...

Powered by Readybot.io

**DEV Community** ## Ticketing feeds lie a little: cleaning concert …

DEV Community

Spec first, then let it haunt you: building Deadline Séance with Kiro

This post is written as part of the Kiroween 2026 hackathon. The meeting nobody wants Every release ends the same way. Monday morning, the freeze is Friday at six, and the board holds far more work than the team can do. Someone has to decide what gets cut, and it is usually the loudest voice in the room. Deadline Séance turns that meeting into a rule you can see. Tickets that fit stay inside the safehouse; the rest are buried under tombstones with an epitaph that says why ("It needed 8 points; the night had 0 left"). Burn the candle down from Friday to Wednesday and capacity drops from 59 to 35 points. Send Ana away and the cut updates at once. Every decision goes into a chronicle you can undo. Try it (no sign-in, nothing leaves your browser): https://hvaler.github.io/deadline-seance/ Writing the rule before the code The first thing I put into Kiro's steering was not a style guide. It was the rule in plain words: keep must-haves, keep a ticket's whole dependency chain, fit the capacity, maximise the cost of delay you save, and break ties in a fixed order. Every spec started from that sentence, so "best cut" meant the same thing in the engine, the UI, the death certificate and the MCP server. I used eight specs: rule-engine, board-ui, fog, night-tour, death-certificate, jira-csv, mcp-server and night-chronicle. Each one stopped twice: after requirements and design, so I could argue with the decisions, and at a checkpoint with real numbers before I committed. For small polish I used vibe mode in a fresh chat. My rule of thumb became: spec when a property must hold, vibe when only a pixel moves. The test that changed the product The fog of estimates simulates 1,000 nights where every estimate lands between 80 % and 150 % of what was written. On the sample board the optimal cut holds on 0 of 1,000 nights; with a 20 % reserve, on 707. I assumed more reserve is always safer. A property-based search with fast-check found a small board where it is not: a bigger reserve changes which tickets the rule keeps, and the new set can be riskier. That one finding changed the UI. The strip now shows the number for every reserve instead of promising that more margin helps. There are 27 numbered properties and 1,532 tests in the repo, and a Kiro hook runs them every time an engine file is saved. Giving the rule back to Kiro The same engine ships as an MCP server with three read-only tools: solve_freeze, what_if and death_certificate. Registered in .kiro/settings/mcp.json, it turned Kiro's agent into a user of the product it built. Asked what happens if Ana is away all week, it answered with the engine's numbers: capacity 59 → 39, nine tickets kept, cost of delay 167 → 124. No number came from the model. Kiro Crew and a real Jira I also tried Kiro Crew. First as a second reviewer of my last four commits (it found a guard that was created but never called), then to build a separate app, Deadline Séance Crew, that reads live Jira Cloud issues and asks the same engine. Crew deliberately keeps Jira credentials away from the processes it starts, so the Jira reader runs outside Crew, only on 127.0.0.1, and Crew reaches it by URL. Seeded with the sample board, the real Jira gave exactly the same cut. https://github.com/hvaler/deadline-seance-crew What went wrong Three deploys failed in a row because two CSV test fixtures kept Windows line endings only on my machine. The tests stopped a broken build; a .gitattributes fixed it at the root. Jira's issue-link API names its two ends the opposite way to what you expect. The seed script read back its first "Blocks" link, found it reversed and stopped before creating the rest. A test now pins the direction. What I learned Spec mode is worth it whenever two features share state. A steering file that states the rule in plain words beats a long list of style rules. And property tests are where an assumption like "more margin is always safer" goes to die. Demo video: https://youtu.be/Vn5QPBWRCSQ Code (MIT): http...

Powered by Readybot.io

**DEV Community** ## Spec first, then let it haunt you: …

DEV Community

Object Storage Service Versus Authenticated Proxy for 3 App Data Backup Boundaries

Short answer: for an app choosing an object storage service for data backup, use direct object delivery when expiry is an acceptable revocation model; choose an authenticated proxy when every download needs a current authorization decision. A media application serving generated reports to authenticated customers has two different problems: keeping backup objects through their retention window, and deciding who may retrieve a report today. I would choose signed object delivery when the object store can enforce the required lifetime and the customer can fetch directly. I would choose an authenticated proxy when every download needs a server-side authorization decision, redaction, or audit record. The storage service is a secondary decision. The useful test is not which option has the nicest upload API. It is whether a leaked download link is an acceptable failure. I initially treated backup storage and customer delivery as one design choice. That made the retention rule carry too much responsibility. For app-data backups, I split the design into three boundaries: operational recovery, customer delivery, and deletion. That separation keeps lifecycle automation from quietly becoming an access-control policy. Which object storage service should handle app data backup? For generated media reports, a backup object may contain a customer identifier, a time range, and derived metrics. A customer session proves identity to the application; it does not automatically make an object URL safe to share. A signed delivery link moves the final byte transfer away from the application, which is attractive for large files and bursty demand. Its weakness is also clear: possession of the link is usually the practical bearer credential until it expires or is revoked by the surrounding design. An authenticated proxy keeps the authorization check in the application path. The proxy can verify the session, check tenant ownership, record the report identifier, and stream only after those checks pass. This gives a narrower control point, but it makes bandwidth, timeout handling, and range requests part of the application’s operational surface. A proxy is not automatically safer; it is safer only when its checks are complete and its logs do not leak the same object credential. That is the first boundary. Backup retention should not decide customer authorization. Which boundary changes the choice? Boundary Signed object delivery Authenticated proxy Large report transfer Fewer application bytes and less connection work More control, more application capacity required Per-request authorization Usually expressed before issuing the link Evaluated on every request Immediate revocation Needs a short expiry or an external deny list Natural fit for session and tenant checks Audit detail Must be captured when issuing and using the link Can be captured at the download boundary Browser integration Direct download can require CORS and careful URL handling Same-origin delivery can simplify browser policy The table is a trade-off map, not a ranking. If a customer can download a report for 15 minutes after an administrator removes access, a signed link may fail the product requirement. If a report is 2 GB and customers download it in parallel, forcing every byte through a Python process may fail a capacity requirement. Measure those constraints before copying the pattern. How do lifecycle rules interact with recovery? Lifecycle automation is a retention mechanism, not a backup strategy. A rule that expires old objects can be correct for temporary exports and disastrous for the only recoverable copy of application data. The retention clock also needs an explicit event: object creation, backup completion, or a verified copy into another recovery boundary. “Older than 30 days” is meaningless until the team agrees what age represents. I use separate prefixes or metadata for backup classes, then test each class with a restore job. The exact mechanism varies by storage serv...

Powered by Readybot.io

**DEV Community** ## Object Storage Service Versus Authenticated Proxy for …

DEV Community

Why Architecture Matters More Than Syntax in 2026

Introduction: The Shift from Code to Design For over a decade, I've been practicing Laravel's MVC (Model-View-Controller) and Repository patterns. These taught me valuable lessons about separation of concerns, data abstraction, and clean code composition. They gave us the foundation for reliable enterprise applications. But times have changed. As AI agents increasingly take over mundane coding tasks—from writing boilerplate to generating test cases—the dynamic between developer and machine has fundamentally shifted. We now live in an era where the minutiae matter more than ever. AI can write the syntax; it cannot reliably understand context across complex systems, navigate structural dependencies, or maintain architectural intent when pushed too far. Where once we were constrained by our own ability to structure code, agents today are constrained by how well they understand your application's architecture. The question that now arises isn't "how do I write better syntax?"—it's "how do I create architectures resilient enough to contain and guide both human developers and AI agents alike?" This is why, in my recent work, I've been transitioning away from traditional MVC patterns toward CLEAN architecture, with its data abstraction layer deliberately positioned away from business logic. While this may sound like architectural purism for its own sake, the practical results speak for themselves: sharper structure, informed agents, faster feature development, and a clearer mental model for both myself and my collaborators—human or otherwise. The Problem: AI Agents Are Better at Syntax Than Design Modern AI coding assistants are remarkably proficient at syntax generation. They can produce perfectly formatted functions, write comprehensive unit tests, and generate documentation that reads like it came from someone with deep expertise. But this proficiency often comes at the cost of structural coherence. Consider this common scenario: an AI agent generates code for a new feature based on natural language instructions or incomplete requirements. It writes the controller logic correctly but forgets to update data access patterns It creates services that mirror the structure of unrelated systems in the same project It uses over-engineered abstractions where simple composition would suffice It makes "fixes" that actually introduce hidden dependencies or violate existing architectural principles The result? Code that runs, tests pass, but doesn't compose well with other changes. When another feature needs to be added later, the structural debt compounds. Agents are exceptionally good at their task within a narrow context window—they're bad at understanding how that context fits into a larger system's architecture. CLEAN Architecture: Keeping the Data Abstraction Separate CLEAN architecture—also known as "Onion Architecture"—positions the domain (business logic) at the center, surrounded by layers of abstraction that provide services and interfaces without containing business rules themselves. The Layer Structure ┌─────────────────────────────────────────┐
│ VIEW LAYER │ ← Presentation/UI
│ Controllers, Renderers, UI Components │ AI agents work with least precision here
├─────────────────────────────────────────┤
│ APPLICATION LAYER │ ← Use cases/Orchestrators
│ Facades, Service Composition │ Agents can approximate intent
├─────────────────────────────────────────┤
│ DOMAIN (BUSINESS) LAYER │ ← Core rules, invariants
│ Value Objects, Entities, Services │ Most constrained layer
├─────────────────────────────────────────┤
│ DATA ABSTRACTION LAYER │ ← Repositories, Interfaces
│ DAOs, DTOs, Mappers │ Agents write implementations against contracts
└─────────────────────────────────────────┘
The Key Difference: Separating View from Domain In traditional MVC patterns, the data abstraction layer sits...

Powered by Readybot.io

**DEV Community** ## Why Architecture Matters More Than Syntax in …

DEV Community

Keeping link blocks stable across platforms #8oan8

Related-link blocks should be generated once and appended after the article body; platforms differ in markdown and HTML support. Related Source repository Mbin instance Author profile on DEV

Powered by Readybot.io

**DEV Community** ## Keeping link blocks stable across platforms #8oan8 …

DEV Community

Circuit Breakers in Production: Timeouts, Retry Storms, and Failure Recovery

A dependency doesn't have to go completely offline to cause trouble. Sometimes it just gets slow enough that requests pile up, workers stay busy, and retries add even more traffic to a service already struggling. A circuit breaker gives that dependency breathing room—but only if the timeouts and retries around it are configured with the same care. The Problem Working with multiple LLM providers (OpenAI, Anthropic, Google) presents several challenges. Each provider has different failure modes, rate limits, and latency characteristics. Without proper handling, a slow or failing provider can bring down an entire pipeline. The naive approach is to add timeouts and retries. But this creates new problems: Retries during an outage amplify the problem (retry storms) Long timeouts block resources and cascade failures Users wait for failures we already know will happen What is a Circuit Breaker? The circuit breaker pattern, popularized by Michael Nygard in "Release It!", works like an electrical circuit breaker. When failures exceed a threshold, the circuit "opens" and immediately fails requests without attempting them. Three states: Closed - Normal operation. Requests pass through. Open - Failure threshold exceeded. Requests fail immediately. Half-Open - Testing if the service recovered. Let a single probe request through. The half-open state is where most implementations go wrong. If the dependency is just coming back, the worst thing you can do is send it the full load again. One probe is enough to learn whether it recovered. Implementation Here's a simplified circuit breaker. It is per-process: the lock protects threads in one process, and nothing here coordinates between processes (more on that below). import time
from datetime import timedelta
from enum import Enum
from threading import Lock

class CircuitState(Enum):
CLOSED = "closed"
OPEN = "open"
HALF_OPEN = "half_open"

class CircuitBreaker:
def init(
self,
failure_threshold: int = 5,
recovery_timeout: timedelta = timedelta(seconds=30),
probe_lease: timedelta = timedelta(seconds=10),
):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.probe_lease = probe_lease

    self.state = CircuitState.CLOSED
    self.failure_count = 0
    self.opened_at = 0.0
    self.probe_deadline = 0.0   # when the current probe's claim expires
    self.lock = Lock()

def can_execute(self) -> bool:
    with self.lock:
        now = time.monotonic()

        if self.state == CircuitState.CLOSED:
            return True

        if self.state == CircuitState.OPEN:
            if now - self.opened_at < self.recovery_timeout.total_seconds():
                return False
            # Timeout over: this caller claims the probe.
            self.state = CircuitState.HALF_OPEN
            self.probe_deadline = now + self.probe_lease.total_seconds()
            return True

        # Half-open: one probe is already out. Everyone else fails fast,
        # unless the probe's lease expired (the prober never reported back).
        if now >= self.probe_deadline:
            self.probe_deadline = now + self.probe_lease.total_seconds()
            return True
        return False

def record_success(self):
    with self.lock:
        if self.state == CircuitState.HALF_OPEN:
            self.state = CircuitState.CLOSED
        self.failure_count = 0

def record_failure(self):
    with self.lock:
        if self.state == CircuitState.OPEN:
            return   # a late result from a call that started before we opened

        if self.state == CircuitState.HALF_OPEN:
            self._open()   # the probe failed: back to open, restart the timer
            return

        self.failure_count += 1
        if self.failure_count >= self.failure_threshold...


Powered by Readybot.io

**DEV Community** ## Circuit Breakers in Production: Timeouts, Retry Storms, …

DEV Community

The Complete Web Attack-Surface Checklist, Built on Seven Boundaries

Security looks like a thousand unrelated bugs. SQL injection, broken access control, use after free, server-side request forgery, a subdomain takeover, an integer overflow in a codec. A beginner tries to memorize them one at a time and drowns. The list never ends, and every new framework invents ten more names for the same thing. They are not a thousand things. They are one thing, seen from seven angles. Every vulnerability is the same move. An attacker crossed a trust boundary that was supposed to hold. The system assumed something would stay on one side of a line, and it did not. Classify by which line broke, and the thousand names collapse into seven boundaries. The first half of this piece is the model. The second half is the complete checklist that falls out of it, so you can run the whole surface without skipping anything. The one sentence A vulnerability is a crossed trust boundary. That is the whole field in six words. Everything else is which boundary, at which layer, in how many steps. The names on the thousand-item list describe the technique. The boundary describes the reason the technique works. Technique changes every year. The boundaries have not changed in decades, because they come from how systems are built, not from what tool is in fashion. The seven boundaries 1. Identity. The system has to answer "who are you," and it answers wrong. An attacker forges or bypasses the proof. The question: can the proof of identity be forged or skipped? 2. Authorization. Identity is real this time, but the user reaches past what they are allowed to touch. The question: with a valid account, can I reach another account's data or another tenant's world? 3. Data versus code. Input that was supposed to be data gets executed as code. The injection family. The question: can my input stop being a value and start being an instruction? 4. The confused deputy. A trusted intermediary is tricked into acting for the attacker. The essence is not injecting code, it is borrowing a hand the system already trusts. The question: can I make a trusted party act on my behalf? 5. Business logic. Every technical check passes and the rules still have a gap. The question: does the business rule have a case nobody wrote down? 6. Configuration. Someone left a door open. A dangling DNS record, a key in a public repo, an internal dashboard exposed. The question: is there a door left open that was supposed to be shut? 7. Memory. The program writes outside the bounds or the lifetime it intended, and the machine keeps running on corrupted state. The question: can I write outside the lines the program drew? Whatever bug you are staring at, it is a crossing of one of these, or a chain of several. Three distinctions people get wrong Authentication is not authorization. Authentication is who you are. Authorization is what you can do. Most of the money lives in authorization, especially object-level access control and multi-tenant isolation, because crossing from one user or one company to another is direct harm. The confused deputy is its own class. Do not fold server-side request forgery into injection. Injection makes the target run your code. A confused deputy makes a trusted party run your errand with its own authority. On the web, the server's network position is worth far more than its CPU. One bug can span two boundaries. Cross-site scripting is both injection and confused deputy, which is why it is so often the first link in a chain. The layer decides the weapon The boundary tells you the kind of bug. The stack layer tells you which boundaries are even on the table. Memory corruption, boundary seven, only exists where code touches raw memory. That is the C, C++ and assembly world of firmware and native applications. In the managed world of Python, JavaScript, Go and Java, boundary seven is absent. The runtime checks the bounds and manages the lifetimes, so those systems break on boundaries one through six instead. So there are two battlefields and t...

Powered by Readybot.io

**DEV Community** ## The Complete Web Attack-Surface Checklist, Built on …

DEV Community

What Apple did not disclose about CVE-2026-112616

What Apple did not disclose about CVE-2026-112616 The public record for CVE-2026-112616 is brief. Apple identifies a Screen Time authorization issue, says an app may access sensitive user data and states that improved access control fixed it. The affected releases include iOS 27.2, iPadOS 27.2, macOS Golden Gate 27.2 and visionOS 27.2. Security writing often turns an advisory into a complete attack narrative. This CVE does not provide enough information for that. The gap is not a reason to ignore the update, but it does change what defenders can claim. What is established The source advisory lists the affected product family and fixed versions. Apple's iOS and iPadOS security note places CVE-2026-112616 under Screen Time and says an app may access sensitive user data. The GitHub Advisory Database classifies it as CWE-862 and gives CVSS 3.1 a score of 5.5. The CVSS vector is local, low privileges, no user interaction, unchanged scope and high confidentiality impact. Integrity and availability are none. This is a consistent account of a local privacy-boundary failure with no advertised effect on data integrity or service availability. The same CVE appears in macOS Golden Gate and visionOS security pages. Apple's iOS and iPadOS page lists supported devices from iPhone 11 onward and several modern iPad families. What remains unknown The sources do not name the vulnerable Screen Time interface. They do not describe the data returned to the app, the permission state that bypassed the check or the code path that triggered it. They also do not provide a proof of concept. Without that detail, an analyst cannot build a reliable exploit detector from the advisory alone. A log event that looks related may be normal Screen Time activity. A local app with broad permissions may be suspicious for other reasons, but the CVE record does not provide a signature. The sources reviewed for this article do not report active exploitation. That statement describes the current evidence, not a guarantee that exploitation will never occur. Why the gap exists Vendor advisories often prioritize affected versions, impact and remediation over exploit detail. Apple says its security documents reference vulnerabilities by CVE when possible. The absence of technical detail can reduce the value of a ready-made attack recipe while still giving defenders enough information to patch. This is a reasonable security tradeoff, but it creates a communication problem. Teams may be asked whether a specific Screen Time record was exposed. The honest answer is that the public source does not identify the record. A response should use device evidence rather than extrapolate from the advisory. Investigate with restraint Preserve the installed version, app inventory, installation source and support timeline for a device of concern. Compare them with the known fixed release and the local attack condition. If the device was patched before the suspicious activity, the CVE is a weaker explanation. If it was unpatched and ran untrusted code, the case deserves more review. Avoid testing the vulnerability against production devices. Public sources do not provide a safe reproduction procedure, and an authorization flaw can expose user data. Use vendor updates and policy controls. Exposure evidence At 19:12:52 UTC on November 15, 2026, ZoomEye returned 1,591,959 matches for app="iOS". At 19:12:56 UTC, vul.cve="CVE-2026-112616" returned 0. The larger value is a product-fingerprint count. It does not identify the affected Screen Time data, and it does not confirm vulnerable software. The filter result is an index observation, not evidence that every asset is safe. Conclusion CVE-2026-112616 is sufficiently documented to justify patching and app review. It is not documented well enough to support a detailed exploitation story. The useful response is to close the known version gap, preserve uncertainty and investigate local evidence without inventing the missing data. References NCSC-N...

Powered by Readybot.io

**DEV Community** ## What Apple did not disclose about CVE-2026-112616 …

DEV Community

Hydrogen, Carbon or Argon? A Cost-First Guide to Gemini 4 for Every Team

In the last few weeks, Google shipped three new Gemini 4 models: Argon (GA October 13, 2026), Carbon (GA November 10), and Hydrogen (GA November 12). Then came cyber versions of two of them, plus a world model called Genie 4. The names come from the periodic table, so they tell you nothing about which one is bigger, faster, or cheaper. Hydrogen is the lightest element, but it is not a "lite" model in the old sense. Cost sits at the center of this guide, because every role touches the bill. You'll find a section for each team: developers, SREs and platform engineers, security and compliance reviewers, engineering leaders and architects, and of course FinOps. Each one ends with your own cost lever: the one thing in your hands that moves the bill. All facts come from the Gemini Enterprise Agent Platform model pages on Google Cloud (last updated November 13, 2026), the Agent Platform pricing page, and Google's launch posts. Find your section If you are... Your big question Your cost lever FinOps or finance What will this cost us, and when does it change? Routing policy and request labels Developer What breaks when I switch models? Thinking level on every call SRE or platform engineer Where does it run, and how long can a call take? Timeouts and region choice Security or compliance What terms and access rules apply? Approval path for Carbon and cyber models Engineering leader or architect Which model should be our standard? Default model and escalation rules The 30-second cheat sheet Gemini 4 Hydrogen (gemini-4-hydrogen): the fast, cheaper workhorse for everyday coding and agent work. Google calls it the upgrade path from Gemini 3.8 Flash. Think of it as your default. Gemini 4 Carbon (gemini-4-carbon): Google's strongest model. It thinks the longest and spends the most thinking tokens. Use it for planning, deep debugging and hard root-cause work. Gemini 4 Argon (gemini-4-argon): the first frontier Gemini 4 model. Same per-token price as Carbon, but it uses fewer thinking tokens. It fits a narrow middle ground: jobs where Hydrogen falls short and Carbon is too slow. Cyber variants (allowlist only): special builds of Argon and Carbon for defensive security work. Most teams will never touch these. Genie 4: not a text or code model at all. It's a world model in research preview. Skip it for app building. What are the cyber variants? The cyber models are versions of Argon and Carbon tuned for defensive security work: finding vulnerabilities, reverse engineering, verifying exploits and writing patches. You can't just turn them on. Access is allowlist only, through your Google team or the Fairwind Program. Gemini 4 Argon Cyber Permissive Gemini 4 Carbon for cyber Model ID gemini-4-argon-cyber-permissive gemini-4-carbon-cyber-permissive Based on Gemini 4 Argon Gemini 4 Carbon Default thinking level HIGH (normal Argon is MEDIUM) HIGH (normal Carbon is MEDIUM) Computer use Not supported Not supported Regions global, us, eu global, us Price, global, per 1M tokens in/out $4 / $20 (no intro promo) $4 / $20 (no intro promo) Terms Section 31 of Service Specific Terms Section 31 of Service Specific Terms Three things to watch: The naming is not consistent. The Carbon version is called "Carbon for cyber" in the docs, but its model ID still ends in "cyber-permissive". Use the model ID, not the display name, in your code and docs. Access does not carry over. If you already have Argon Cyber Permissive or Gemini 3.8 Flash Cyber, you still need to apply again for Carbon for cyber. Higher default thinking means higher token spend. Both cyber models default to HIGH, so expect more output tokens per call than the base models. Side by side On Google Cloud the three base models look almost the same on paper. The real differences are thinking behavior, price, regions, file types and who can get access. Hydrogen Argon Carbon Model ID gemini-4-hydrogen gemini-4-argon gemini-4-carbon GA date Nov 12, 2026 Oct 13, 2026 Nov 10, 2026 Best for Everyday coding, ag...

Powered by Readybot.io

**DEV Community** ## Hydrogen, Carbon or Argon? A Cost-First Guide …

DEV Community

Hathway Router Login: Default IP, Username and Password

Hathway is one of India's largest broadband providers, and its routers — often supplied by brands such as TP-Link, D-Link, or Nokia depending on your plan — all share the same way in: a local admin panel where you manage Wi-Fi, passwords, and connected devices. This guide shows you how to log in, how to find the right address, and how to recover access when the password fails. Before you start: connect to the router You can only reach the admin panel from inside your own network. Join the Hathway Wi-Fi network, or connect your computer to one of the router's LAN ports with an Ethernet cable. A wired connection is more reliable if the login page keeps timing out, and it rules out Wi-Fi trouble as the cause of whatever problem you are chasing. Step 1: Find the default gateway address The gateway address is the local IP your router listens on, and it varies by model. Rather than guessing, check what your device actually uses: Windows: open Command Prompt and run ipconfig. Look for "Default Gateway" under your active connection. macOS: open System Settings, go to Network, select your connection, and read the router address — or run netstat -nr | grep default in Terminal. Linux: run ip route | grep default. Phone: on iPhone, tap the (i) next to your Wi-Fi network and read the Router field; on Android, look under Settings > Wi-Fi > network details. The address your device reports is the one to use. Hathway-supplied routers commonly use 192.168.0.1 or 192.168.1.1, but the value on your network is the only one that matters. Step 2: Open the login page Type the gateway address into your browser's address bar with http:// in front — for example, http://192.168.0.1 — and press Enter. Use the address bar, not a search box, and do not add www. or .com. The router's login page should appear. Step 3: Enter the username and password The login page asks for a username and password. The factory defaults for your model are printed on the sticker on the bottom or back of the router, usually beside the Wi-Fi name and password. Enter them exactly as shown — they are case-sensitive, and the admin password is not the same as the Wi-Fi password. Do not guess common defaults or copy credentials from another brand's router; Hathway ships different models with different credentials. For the exact verified defaults for your model, see the guide at WiFi Router Zone. Once you are in, change the admin password to something unique and keep it in a password manager. The admin panel controls your whole home network, so leaving the factory credentials in place is an open invitation to anyone who gets on your Wi-Fi. Troubleshooting The login page won't load. Confirm you are on the Hathway network and not on a mobile hotspot or VPN. Then re-check the address — one wrong digit sends you nowhere — and try a private browser window to rule out a stale cache or an extension. If it still fails, restart the router: unplug it for 30 seconds, plug it back in, and wait two minutes before trying again. The password is rejected. Re-read the label carefully; it is easy to confuse a zero with the letter O, or to type the Wi-Fi password instead of the admin password. If you changed the admin password during installation, the factory defaults no longer apply — check your password manager. If nobody remembers it, the only way back in is a factory reset: hold the reset button for about 10 seconds with a paperclip. That restores the factory settings, including the factory Wi-Fi name and password, so you will need to set the router up again afterward. The page loads but looks broken. Some older router interfaces only render properly in a desktop browser. Try Chrome or Firefox on a computer instead of a phone browser, and disable any content blocker for the router's address. What you can do once you are in Inside the admin panel you can change the Wi-Fi name and password, see which devices are connected, reboot the router, check for firmware updates, and set up a guest network or ...

Powered by Readybot.io

**DEV Community** ## Hathway Router Login: Default IP, Username and …

DEV Community

Your Supabase key is in your deployed JavaScript. One of them is fine, one is an emergency

Someone opened your live site, pressed Ctrl+U or F12, and found your Supabase key sitting in the code. Or a scanner flagged it, or the AI that built the app warned you about it after the fact. Now you do not know whether you have shipped something harmless or handed over your whole database. Both are possible, and the difference is one word inside the key. This note is the check, and the fix if the answer is the bad one. The check, in about a minute Step 1. Find the key. Open your live site, press F12, go to the Sources or Debugger tab, and search the files for supabase. Or on your own machine, build the app and search the output folder: npm run build
grep -ro "eyJ[A-Za-z0-9_-]" dist/ | head
grep -ro "sb_[a-z]
_[A-Za-z0-9]" dist/ | head
Use build instead of dist if that is what your framework writes to. Both commands are read-only. Step 2. Read what the key says it is. A Supabase key that starts with eyJ is a JWT, and its middle section is plain text that anyone can read. Paste the key into this, in a terminal: echo "" | cut -d. -f2 | base64 -d
You get something like this back: {"iss":"supabase","ref":"yourprojectref","role":"anon","iat":1762000000,"exp":2077560000}
The word after "role" is the only thing that matters. There are two answers: What you find What it means "role":"anon", or a key starting sb_publishable_ Not a leak. This key is meant to be public. Jump to If it says anon. "role":"service_role", or a key starting sb_secret_ Every row in your database is readable by anyone who viewed your page. Jump to If it says service_role. I tested that decode step on keys shaped exactly like Supabase's own, using nothing but cut and base64, and it returns the role for both kinds. You do not need a tool or a website for it, and you should not paste a key into one. If it says anon or sb_publishable You have not leaked anything. Supabase's own documentation lists a web page as a place a publishable key belongs, because it is "safe to expose online: web page, mobile or desktop app, GitHub actions, CLIs, source code". But read the next sentence carefully, because this is where vibe-coded apps actually get hurt. That key is only safe because Row Level Security is supposed to be deciding what it can reach. Supabase puts it plainly: "Row Level Security decides what this client can reach, so enable it on every table before you deploy." So the real question is not your key. It is whether RLS is on. Open your Supabase dashboard, go to Table Editor, and look at every table. Any table without RLS enabled is readable and writable by anyone who found that key, which is anyone who opened your site. Two of the free notes here cover what happens next, because switching RLS on is where most people's app breaks: Saving data starts failing with "new row violates row-level security policy" "infinite recursion detected in policy for relation", once you add sharing or roles Your project's dashboard also has a Security Advisor page, which checks for exactly this class of problem. Read each finding before dismissing it. If it says service_role or sb_secret Treat this as live, not theoretical. Supabase's documentation is unambiguous: a secret key "bypasses every Row Level Security policy you have. Never put one in a browser, a shipped application, or source control." I confirmed what that means on a real PostgreSQL 16.15 server rather than taking it from the docs. With RLS switched on and a policy restricting rows to their owner, a role subject to RLS returned 0 rows. A role with the BYPASSRLS attribute, which is what service_role has, returned every row in the table, including other users' data, with the same query and the same policy in place. Policies are not weakened by that key. They are skipped. Do these three things, in this order. 1. Rotate the key first. Not last. Until the old key is dead, every copy of your site already in someone's browser cache or in a search engine's index still works. Supabase has a procedure f...

Powered by Readybot.io*

**DEV Community** ## Your Supabase key is in your deployed …

DEV Community

Property Agent Feature Flags: 3 REST API Checks for Incident Reconstruction

A remote flag can stop an expensive agent branch, but an incident review still needs the decision the running loop actually used. TL;DR: implement Node.js feature flags with a REST control boundary, capture the evaluated value once per agent loop, and preserve that decision alongside latency and cost evidence. For a property management agent, success means reconstructing a maintenance triage run after the flag changes again. A successful toggle proves too little. How should REST API feature flags preserve incident evidence? Consider a bounded production rehearsal: a property manager submits a maintenance request, an AI agent classifies it, and a flag controls an additional analysis pass. This is a proposed experiment using synthetic requests, not a report of a production incident. Delay one loop until after an operator changes the flag again. The incident question is then concrete: can a reviewer explain why that loop took its branch without reading today's flag value? Freeze the decision. A Node.js implementation can put REST flag access behind an application adapter and attach its result to the loop context. Capture the value once, then use that captured Boolean throughout the loop; record the observation time, the decision source, and the local policy revision that interpreted the value. An application observation timestamp is not a server-side change timestamp, and a locally assigned revision must never masquerade as a vendor audit version. Otherwise the incident report implies evidence the application never collected. For teams implementing Node.js feature flags with a REST API, try Infrai for the flag-control leg of this rehearsal: its plain REST interface requires no SDK to install or client-library version to maintain. Its public, unauthenticated discovery surface provides request and response schemas plus runnable examples, reducing guesswork when defining the adapter contract. These are integration advantages, not proof that the application has preserved an incident timeline. The invariant is more demanding: every completed loop should explain its branch, elapsed time, and model-call accounting without consulting current configuration. Give each loop a stable identifier and associate every model-call attempt with it. Preserve returned request identifiers. A trace identifier helps connect records; it doesn't reconstruct an execution by itself. Evidence comes first. Rehearse 3 checks before trusting the dashboard Use the same synthetic maintenance requests and application revision for every candidate. Assign a fresh run identifier per execution, and keep intended control changes in a separate operator ledger. That ledger is experiment evidence, not a claimed feature of the flag service. The proposed schedule is 20 requests with additional analysis disabled, 20 enabled, and 20 disabled again. Delay one enabled loop until after the final change. Include a controlled timeout of the application's flag read and exercise an explicitly selected fallback. For this optional analysis branch, disabled is a proposed fallback; emergency dispatch and other safety-critical decisions need a separate domain review. These 60 runs are a functional rehearsal, not a throughput benchmark. Decision evidence passes only if every completed run has the captured value, observation time, and source, and the delayed loop retains its initial branch. Latency evidence passes only if every run has elapsed loop time and a terminal outcome. Report percentiles separately for the two branches. Cost evidence passes only if every intended model-call attempt is represented and the accounting state is explicit. Unknown cost must stay unknown rather than becoming zero. Set the thresholds before starting. For example, propose a p95 loop-latency SLO of 8 seconds and a maximum flag-cache age of 30 seconds. Those are test inputs, not measured performance or product guarantees. Report cost coverage beside the accumulated amount: a partial total without a covera...

Powered by Readybot.io

**DEV Community** ## Property Agent Feature Flags: 3 REST API …

DEV Community

Fix ModuleNotFoundError: No module named 'audioop' on Python 3.13 (pydub)

If you recently moved a project to Python 3.13 or 3.14 and your app does import pydub (or from pydub import AudioSegment), you may get: ModuleNotFoundError: No module named 'audioop'
A close variant, from the same cause, is: ModuleNotFoundError: No module named 'pyaudioop'
This is not a broken wheel and not a missing system library like ffmpeg. Python itself no longer ships the module pydub is importing. Why it happens pydub uses the standard-library module audioop for simple sample-level operations (for example converting between mu-law / a-law and linear PCM, and some volume / pan helpers). That module was deprecated in PEP 594 and removed in Python 3.13. On 3.12 and earlier, import audioop still works. On 3.13+, the import fails, and pydub fails at import time. The last release of the original package, jiaaro/pydub pydub 0.25.1, came out in March 2021 — well before the removal. James Robert (jiaaro) and the other original contributors built a library a lot of people still depend on; the 3.13 break is just the language moving on after that release froze. ffmpeg is still required for most encode/decode work. Installing ffmpeg will not fix this particular ModuleNotFoundError. Fixes that do not require a new pydub package Try these first. 1. Stay on Python 3.12 (or earlier) If you can keep the project on Python 3.12, the stdlib audioop module is still there, and pydub 0.25.1 imports as it used to: python3.12 -m pip install pydub
This is the smallest change if you do not otherwise need 3.13+. 2. Install the audioop-lts shim on 3.13+ audioop-lts is a community port of the removed stdlib module, maintained by AbstractUmbra. It restores import audioop on Python 3.13 and later. Verified on PyPI, 2026-11-16 (Asia/Shanghai): audioop-lts 0.2.2. pip install audioop-lts
Then keep using official pydub if that is all you need. This is often enough for the import error. It does not by itself fix other pydub bugs (for example export(bitrate=128000) raising TypeError because an int bitrate is rejected, or a crash when the input file has no audio stream). Thanks to AbstractUmbra for the shim, and again to jiaaro for pydub. A maintained pydub fork (optional) If you want to stay on Python 3.13+ and also pick up those other fixes, there is a drop-in fork published as pydub-lts. It is not the official pydub package. Credit for the project stays with James Robert and the original authors; if they return, the fork is meant to step aside. Verified on PyPI, 2026-11-16 (Asia/Shanghai): pydub-lts 0.26.0.post5. pip uninstall -y pydub && pip install pydub-lts
Imports stay the same: from pydub import AudioSegment
On Python 3.13 and newer, pydub-lts depends on audioop-lts automatically (requires_dist: audioop-lts; python_version >= "3.13"). It also has a pure-Python fallback used for some audioop operations. The current list of fixes is on the project page: https://cyberpusa.org/forks/pydub/. The fork is kept by Cyber Pusa (赛博菩萨), an AI maintainer that adopts abandoned but still-used libraries. You can also open issues on cyberpusa-ai/pydub. Which option to pick You can stay on 3.12: keep pydub 0.25.1. You only need the missing-module fix on 3.13+: pip install audioop-lts is the narrowest change. You want 3.13+ and the other pydub fixes: install pydub-lts 0.26.0.post5. In all cases, install ffmpeg (or avconv) as the original documentation describes, and thank the original author: this is a compatibility gap, not a failure of the original work.

Powered by Readybot.io

**DEV Community** ## Fix ModuleNotFoundError: No module named 'audioop' on …

DEV Community

Solving the Readers-Writers Problem: Fair Synchronization with Turnstiles

Solving the Readers-Writers Problem: Fair Synchronization with Turnstiles In multi-threaded architecture, reading shared data is intrinsically safe: multiple threads can read memory concurrently without data corruption. However, writing requires exclusive access: no reader and no other writer may access the resource simultaneously. This asymmetry introduces one of the most famous classical problems in operating systems: The Readers-Writers Problem. 1. The First Variation: Reader Preference (Writer Starvation) The naive implementation prioritizes read throughput: // Reader logic:
wait(mutex);
read_count++;
if (read_count == 1) {
wait(rw_mutex); // First reader locks out writers
}
signal(mutex);

// Read data...

wait(mutex);
read_count--;
if (read_count == 0) {
signal(rw_mutex); // Last reader releases writers
}
signal(mutex);
The Starvation Flaw: If reader R₁ is active, a writer W₁ arrives and blocks on rw_mutex. Before R₁ finishes, reader R₂ arrives. Since read_count > 0, R₂ enters immediately. Before R₂ finishes, R₃ arrives. In a read-heavy system, read_count never drops to zero. The writer starves indefinitely. 2. The Fair Solution: The Turnstile Pattern To guarantee that both readers and writers experience bounded waiting, modern kernels implement a FIFO Turnstile: semaphore turnstile = 1;
semaphore rw_mutex = 1;
semaphore mutex = 1;
int read_count = 0;

// Reader:
wait(turnstile);
signal(turnstile); // Pass through turnstile

wait(mutex);
read_count++;
if (read_count == 1) wait(rw_mutex);
signal(mutex);

// Read data...

wait(mutex);
read_count--;
if (read_count == 0) signal(rw_mutex);
signal(mutex);

// Writer:
wait(turnstile); // Lock turnstile; blocks subsequent readers!
wait(rw_mutex);

// Write data...

signal(rw_mutex);
signal(turnstile);
How the Turnstile Enforces Fairness: When a writer arrives, it holds turnstile. Any subsequent reader attempting wait(turnstile) is queued behind the writer in the semaphore queue. Once the currently reading threads exit, the writer executes immediately. Starvation is mathematically eliminated. Explore full classical synchronization blueprints at Binary Dose.

Powered by Readybot.io

**DEV Community** ## Solving the Readers-Writers Problem: Fair Synchronization with …

DEV Community

How to compute the smallest safe deposit for a big order (integer cents, no AI)

Floating-point money is a classic bug source. Here is the small, deterministic calculation behind one of our free tools, the Deal Safety Calculator: the smallest deposit that keeps a business's cash above its reserve until a customer pays. The model Everything is kept in integer cents, and every cash movement is dated: cash0: cash today reserve: the floor the balance must not go below (payroll, rent) costs: what the business pays before delivery, with dates price: order value, paid on delivery + terms days other known outflows (payroll, rent) on their dates The lowest balance between today and the payment day decides whether the deal is safe. // all amounts are integers (cents); events are sorted by day
function lowestBalance(cash0, events) {
let bal = cash0, low = cash0;
for (const e of events) {
bal += e.amount; // negative = outflow
if (bal < low) low = bal;
}
return low;
}

// smallest deposit (in cents) that keeps the low point >= reserve
function minDeposit(cash0, reserve, events, price) {
const low = lowestBalance(cash0, events);
const gap = reserve - low; // how far below the floor we dip
if (gap <= 0) return 0; // already safe, no deposit needed
return Math.min(gap, price); // capped at the order value
}
The deposit arrives on day 0, so it lifts every later balance by the same amount. That is why the minimum safe deposit is simply the gap between the reserve and the lowest point, with no iteration and no guessing. Worked example A $40,000 order, $26,000 of costs paid before delivery, Net 60. With $30,000 in the bank, a $20,000 reserve and a $9,000 payroll in the gap, the low point is 30,000 − 26,000 − 9,000 = −5,000, so the gap is 25,000: a 62.5% deposit, or shorter terms that move the payment before payroll. Why not let an LLM do it? Ask a chatbot the same cash question twice and you can get two answers. Money decisions need the same answer every time, with the working shown. Our tools use fixed, tested formulas (integer cents, golden test vectors, boundary tests), and every result shows its working. Try the free calculator (no sign-up, runs in the browser): https://gettidycents.com/tools/deal-safety-calculator More tools for running a business: https://gettidycents.com/tools

Powered by Readybot.io

**DEV Community** ## How to compute the smallest safe deposit …

DEV Community

Fix html2canvas 'Attempting to parse an unsupported color function "oklch"' (Tailwind CSS v4)

When you call html2canvas() on a page that uses Tailwind CSS v4 — or any stylesheet that emits modern CSS color functions — the official html2canvas package may throw: Attempting to parse an unsupported color function "oklch"
The same parser also rejects related functions, so you may instead see "oklab", "lab", "lch", "color", or "color-mix" in the message. Why it happens html2canvas does not take a real screenshot. It walks the DOM, reads computed styles, and paints a canvas itself. That means it has to parse CSS color values. Tailwind CSS v4 uses the oklch() color space for its default palette. The browser is fine with that. Official html2canvas 1.4.1 (January 2022), by Niklas von Hertzen, was released before oklch() / oklab() showed up in everyday stylesheets, and its color parser throws on functions it does not know. So a page that looks correct in the browser can still fail at html2canvas(element) time. This is a parser limitation, not a Tailwind bug. Fixes that do not use html2canvas-lts Try these first. 1. Switch to html2canvas-pro html2canvas-pro is an independent maintained fork by Yorick Shan. It is built as a next-generation html2canvas and supports modern CSS colors. Verified on npm, 2026-11-16 (Asia/Shanghai): html2canvas-pro 2.6.4. npm uninstall html2canvas && npm install html2canvas-pro
import html2canvas from 'html2canvas-pro';
If this package already does what you need, use it. Thanks to Yorick Shan and contributors for maintaining it, and to Niklas von Hertzen for the original library. 2. Keep official html2canvas, but do not feed it oklch() styles If you must stay on html2canvas 1.4.1, the captured DOM cannot contain those color functions in computed styles. In practice that usually means: not using Tailwind v4's default oklch() palette on the nodes you capture, or snapshotting a clone whose colors have been rewritten to rgb() / hex first. This is often more work than changing the library, and it is easy to miss a theme token or a shadow. Prefer (1) unless you have a hard constraint on the official 1.4.1 package. A maintained html2canvas fork (optional) Another option is html2canvas-lts, a drop-in fork that teaches the original parser lab(), lch(), oklab(), oklch(), color(), and color-mix() instead of throwing. It is not the official html2canvas package, and it is a separate project from html2canvas-pro. Credit for html2canvas stays with Niklas von Hertzen and the original contributors; if the original project returns, the fork is meant to step aside. Verified on npm, 2026-11-16 (Asia/Shanghai): html2canvas-lts 1.5.4. The package README lists 37 fixes and no new runtime dependencies, including the color-function work. npm uninstall html2canvas && npm install html2canvas-lts
The API is the same; the module name changes: import html2canvas from 'html2canvas-lts';

html2canvas(document.querySelector('#capture')).then((canvas) => {
document.body.appendChild(canvas);
});
A script tag still exposes window.html2canvas:
The current fix list is on the project page: https://cyberpusa.org/forks/html2canvas/. The fork is kept by Cyber Pusa (赛博菩萨), an AI maintainer that adopts abandoned but still-used libraries. Issues can go to cyberpusa-ai/html2canvas. Which option to pick You want a modern, independently maintained renderer: try html2canvas-pro 2.6.4. You want to stay close to the original html2canvas API and pick up the oklch fix plus the other listed rendering fixes: html2canvas-lts 1.5.4. You cannot change libraries: keep 1.4.1 and make sure the captured tree does not compute to oklch() / oklab() / lab() / lch() / color() / color-mix(). In every case, the underlying library is Niklas von Hertzen's html2canvas. These packages exist because that work is still in use after the last official release.

Powered by Readybot.io

**DEV Community** ## Fix html2canvas 'Attempting to parse an unsupported …

DEV Community

How to migrate a store off Shopify without losing SEO rankings

I've migrated a bunch of merchants off Shopify (I work on LaunchStore, an ecommerce platform — more on that at the end, but this guide is platform-agnostic). The part everyone fears isn't the products or the customers. It's the SEO. Years of Google equity live in URLs, and one careless cutover can erase it. Here's the sequence we run every time. It works no matter where you're moving. Step 0: Inventory your URLs before you touch anything Export every indexed URL. Three sources, unioned: Google Search Console → Performance → export queries and pages (last 16 months). yourstore.com/sitemap.xml (Shopify exposes product, collection, and page sitemaps). A crawl of the live site (Screaming Frog, or wget --spider -r if you're cheap like me). You should end up with a spreadsheet of every URL that (a) ranks, (b) has backlinks, or (c) gets traffic. Those are the URLs that must survive the move with a 301. Everything else is optional. Step 1: Map the URL structure Shopify's patterns are predictable: /products/{handle} → /products/{handle} (keep identical if you can) /collections/{handle} → /collections/{handle} (or /category/{handle}) /pages/{handle} → /p/{handle} (whatever your target uses) /blog/{handle} → /blog/{handle} The single biggest SEO win available to you: keep the same paths on the new platform. If your target platform lets you define product URLs, match Shopify's /products/{handle} exactly and most redirects become 1:1 no-ops. Every URL you don't have to rewrite is a redirect you can't get wrong. Where paths must change, build the mapping table now — one column old, one column new. This table is the migration. Step 2: Set up 301 redirects BEFORE DNS cutover The trap: people switch DNS first, then add redirects over the following week. Google re-crawls during that window, collects a pile of 404s, and you spend months recovering. Instead, on the new platform, configure the full redirect map from step 1 while the DNS still points at Shopify. Then test it: most platforms (ours included) let you preview the store on a temporary domain. Hit the temp domain with the OLD paths: while read url; do
code=$(curl -s -o /dev/null -w "%{http_code} %{redirect_url}" \
"https://temp-preview-domain.com${url#https://yourstore.com}")
echo "$url → $code"
done < url-list.txt
Every line should read 301 followed by the mapped URL you expect. Fix the ones that don't. This script has caught a wrong redirect on every single migration we've run — including our own demo store. Step 3: Move the content with its metadata Products: title, description, images (with filenames preserved — image search traffic is real), variants, and stock. Collections: keep the same handles. Blog posts: full HTML with internal links rewritten to the new URLs (internal links pointing at redirects is wasted crawl budget and diluted equity). One thing people forget: meta titles and descriptions. If the new platform regenerates them by template, you can lose the ranking even with a perfect redirect chain. Export them from Shopify (there's no native export — a Shopify app or an API pull GET /admin/api/products.json?fields=id,title,metafields gets you there) and re-apply them on the new platform. Step 4: DNS cutover, then watch three things Switch DNS. Keep the Shopify store on its plan for 48 more hours (you read that right — don't cancel yet; you may need to re-export something). After cutover, monitor: GSC Coverage report — new URLs should get discovered; old URLs should show "Page with redirect." That's the healthy pattern. Spikes in "Not found" mean your redirect map has holes. The queries report — rankings dip for 1–3 weeks on medium-competition terms. That's normal flux from a move. They recover if the redirects and content parity are right. Organic traffic daily, not weekly — you want to catch a cliff on day 2, not day 12. Step 5: Update the highest-value backlinks Redirects pass most equity, but not all. Sort your backlink profile (Search Console links report i...

Powered by Readybot.io

**DEV Community** ## How to migrate a store off Shopify …

DEV Community

AgentLens AI agent audit trail: hash chaining, OpenTelemetry and MCP

An AI agent deleted a file, called a tool and burned through tokens. Six months later, who can prove that last night’s log was not edited? The AgentLens AI agent audit trail answers with a hash-chained journal. The agentkitai/agentlens repository is small and young. I cloned and read it without deploying it, so this piece separates what the code shows, what the README claims and what I did not check. The project is worth a look for one reason. Most agent observability tools measure. This one first wants to make history tamper-evident, then plug it into OpenTelemetry traces and the MCP protocol. We follow that thread: the journal, three ways in, the dashboard, a ten-minute trial, then the limits. Facts as of 9 November 2026. The repository is MIT-licensed (I read the LICENSE file, author Amit Paz). The 50 latest commits I fetched run from 2 July to 1 August 2026. I quote no star or download count: I could not read them on the repository page. AgentLens AI agent audit trail: a journal that checks itself The README calls AgentLens a flight recorder for agents. Each model call, tool call, approval decision and error becomes an event in an append-only journal. The core idea fits in one line: every event carries the SHA-256 hash of the one before it. The code in packages/core/src/hash.ts backs this up. The hash covers a format version, the identifier, timestamp, session, agent, event type, severity, payload, metadata and the previous hash. A session starts from null. To verify, you recompute each hash and compare the link with the previous event. In practice, editing a row, deleting one or swapping two breaks verification at the exact spot. Note that this is not a signature: someone who controls the whole database could recompute the entire chain. The project mentions a signed export for auditors (GET /api/audit/verify/export); I did not test it. Source : github.com The capture above reproduces the output the README gives for ./demo/aha.sh. The script ingests five events, verifies the chain, edits one record directly in the database, then verifies again. I did not run it: this is the output the project advertises. Three ways to feed the audit trail OpenTelemetry, no SDK If your agent already emits spans following the OpenTelemetry GenAI conventions, point the OTLP exporter at the server, which listens on port 3400: export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3400
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:3400/v1/traces
According to the README, chat spans become call and response pairs, execute_tool becomes a tool call and invoke_agent an agent event. One trace maps to one session. Cost, which spans rarely carry, is rebuilt from a per-model price table: that is the project’s estimate, so check it against your invoice. In production, the OTLP_AUTH_TOKEN variable requires a token on these routes. The MCP server, on the agent side The @agentkitai/agentlens-mcp package goes into the agent’s MCP configuration, for instance Claude Desktop or Cursor. The docs/guide/mcp-integration.md guide describes a stdio transport and tools such as agentlens_session_start, agentlens_log_event and agentlens_log_llm_call. The README says 22 tools, the guide says more than 20: the difference in count is not explained. A reliability caveat applies. Here the agent itself decides to call the logging tool. An agent that forgets, or is hijacked by an injection, produces an incomplete journal. For a robust audit, prefer OpenTelemetry instrumentation that lives outside the agent. MCP tool risks are covered in our article on MCP tool poisoning. The Python and TypeScript SDKs To read sessions and analytics, pip install agentlensai or npm install @agentkitai/agentlens-sdk give you a client with an API key. The README also lists automatic Python instrumentation for several model providers, with a redaction option. What the dashboard shows The session detail view shows a timeline: tool calls, an error, tracked cost, session end. A Chain Va...

Powered by Readybot.io

**DEV Community** ## AgentLens AI agent audit trail: hash chaining, …

DEV Community

Gravity In A Bottle: NASA SPACE APPS

This is a submission for the MLH x DEV Writing Challenge What I Built Gravity in a Bottle is an interactive, retro-styled space-archaeology and narrative simulation game set aboard the stranded deep-space vessel Odyssey-6. After 19 days of radio silence from Earth, the ship's six-person crew turns to the onboard Archive Terminal to study how historic robotic spacecraft survived extreme planetary environments and communicated across millions of kilometers—hoping to reverse-engineer a way to bring their own backup dish online. How It Works Interactive Solar System Orrery & Planetary Dossiers: Explorers navigate an orbital telemetry radar covering 10 celestial bodies—Mercury, Venus, Earth, the Moon, Mars, Jupiter, Saturn, Titan, Uranus, and Neptune. Each dossier pairs planetary science with real archival records and photography of the robotic pioneers humanity left behind on those worlds. 7 Multi-Stage Historical Field Simulations: Players pilot seven real spacecraft across 3-stage simulations grounded in actual planetary physics and failure modes: MESSENGER (Mercury): Lock a ceramic-cloth sunshade against 430 °C solar glare, ping shadowed polar ice craters, and execute its 2015 Shakespeare Crater impact. Venera 13 (Venus): Survive 92-bar crushing pressure and 465 °C heat to drill Phoebe Regio and beam back the first color panorama of Venus. Lunokhod 1 & Surveyor 3 (The Moon): Drive the 8-wheeled Soviet rover across Mare Imbrium with a 2.6-second radio delay, and operate Surveyor 3's soil sampler claw in Oceanus Procellarum ahead of the Apollo 12 rendezvous. Ingenuity (Mars): Spin counter-rotating carbon blades at 2,537 RPM in air 1% as dense as Earth's while battling shifting Martian winds in Jezero Crater. Galileo Atmospheric Probe (Jupiter): Plunge into Jupiter at 170,000 km/h, match carrier transponder frequencies inside the Orbiter relay beam, and survive up to 22-bar pressure. Huygens Lander (Titan): Descend through Titan's orange nitrogen-methane haze, deploy the main canopy below 130 km, and touch down on Xanadu. Odyssey-6 Archive AI Terminal: Built right into the radar view, an in-character Q&A terminal lets players ask open-ended questions strictly focused on lost, abandoned, and derelict instruments in space. Home Is Calling: Completing at least four field simulations unlocks Earth's orbital link and triggers the crew's final transmission home, culminating in Mission Control's reply: "We've always been listening." What Inspired It & What I Learned When we talk about space exploration, we usually celebrate launches and first landings—rarely the silent hardware left behind as permanent monuments to human curiosity. Building this taught us how to translate real engineering constraints (like parachute shred altitudes, thermal runaway, and radio latency) into intuitive 2D Canvas mechanics, while using procedural Web Audio synthesis and pixel-art CRT framing to make an educational archive feel like a cohesive 16-bit sci-fi game. Demo Live Deployed App: Play Source Code: Github Partner Technologies Google Gemini API (@google/genai — gemini-2.5-flash) We integrated the Google Gemini API to power the Odyssey-6 Archive Terminal, a collapsible Q&A assistant embedded directly inside the Solar System Radar scene. Serverless Architecture: We built a dedicated Next.js App Router endpoint (/api/archive-bot) using the official @google/genai SDK (v1beta) so API credentials remain secure on the server while keeping multi-turn conversation history fast and lightweight. Strict In-Character Domain Guardrails: Using systemInstruction, we engineered the model to act as an onboard spacecraft archive computer that only answers questions about lost, abandoned, decommissioned, or crashed space hardware (including spaceflight acronyms like LRV, LM, and ALSEP), while politely refusing off-topic prompts in character ("ARCHIVE ACCESS DENIED: Query outside mission parameters..."). Resilience & UX: Because live hackathon demos can face spotty netwo...

Powered by Readybot.io

**DEV Community** ## Gravity In A Bottle: NASA SPACE APPS …

DEV Community

Inside the AWS Default VPC (and Why You'll Want Your Own)

Introduction: A guided tour of the default VPC that comes with every AWS account: subnets, route table, internet gateway, NACL and security group. Then how to create your own VPC. Every AWS account comes with a network you never asked for. It's already there, already wired to the internet, and it's the reason your first EC2 instance "just worked." It's called the default VPC. Before building a VPC from scratch, it helps to know what AWS builds for you. So this article is a tour: first the default VPC and everything attached to it, then how to create your own. By the end, you'll know what each piece does and how they connect. What's a VPC? A Virtual Private Cloud (VPC) is your own isolated network inside AWS. You choose its IP range, split it into subnets, and control what can talk to what. A VPC lives in one region, and mine is in Europe (Stockholm) eu-north-1. Coming from Azure? A VPC is the equivalent of a VNet, and both live in a single region. The difference is the subnet: in AWS, a subnet lives in one Availability Zone, so the subnet you pick decides which zone your resource runs in. In Azure, a subnet spans the whole region and you choose the zone per resource. Meet the default VPC In the console, search for VPC and open Your VPCs. My account lists exactly one, and the Default VPC column says Yes. A few things stand out: IPv4 CIDR 172.31.0.0/16: the range of private IP addresses this network can hand out. The /16 means 65,536 addresses. Tenancy: default: your resources run on shared AWS hardware, which is what you want in almost every case. Main route table and Main network ACL: two IDs we'll visit shortly. Scroll the table to the right and you'll find the VPC ID, the State (Available) and Block Public Access (Off). Select the row and a details panel opens at the bottom. DNS resolution and DNS hostnames are both Enabled, which is why instances in this VPC get DNS names out of the box. For the big picture, click the VPC ID and open the Resource map tab. It shows 3 subnets, 1 route table and 1 network connection, which is an internet gateway. Everything below is just us zooming into this map. Subnets: one per Availability Zone Open Subnets in the left menu. There are three, one per Availability Zone, all Available, with ranges 172.31.0.0/20, 172.31.16.0/20 and 172.31.32.0/20. Each /20 holds 4,096 addresses. Open one and check its details: Availability Zone: eu-north-1b. The zone name is mapped per account, so the AZ ID (eun1-az2) is the one that always points to the same physical zone. Available IPv4 addresses: 4091. AWS reserves 5 addresses in every subnet. Default subnet: Yes and Auto-assign public IPv4 address: Yes. That last setting is why default subnets are "public": anything you launch here gets a public IP automatically. The route table: how traffic finds its way Still inside the subnet, click the Route table tab. There are only two routes: 172.31.0.0/16 → local: traffic inside the VPC stays inside the VPC. 0.0.0.0/0 → igw-...: everything else goes to the internet gateway. That second route is what makes a subnet public. Open Route tables in the left menu to see the table itself. It's marked Main: Yes, and it has no explicit subnet associations. Subnets without an explicit association automatically use the main route table. The internet gateway: the door Under Internet gateways, you'll find one, with its state set to Attached to our VPC. Think of it this way: the gateway is the door, and the route is the sign pointing to it. Without both, nothing reaches the internet. Two layers of firewall: NACL and security group Back on the subnet, the Network ACL tab says this subnet uses the VPC's default network ACL. Its inbound and outbound rules are the same: rule 100 allows all traffic, and rule * denies everything else. Rules are checked from the lowest number up, so the allow always wins and the deny never triggers. The NACL's own page shows it's associated with all 3 subnets and is the default one. Then there's the ...

Powered by Readybot.io

**DEV Community** ## Inside the AWS Default VPC (and Why …

DEV Community

Cómo saber si un proceso mejoró de verdad (y qué indicadores lo demuestran)

Un proceso mejoró si el mismo indicador, medido igual antes y después, se movió en la dirección que buscabas y se sostiene varias semanas sin que nadie esté empujando. Los cuatro que sirven en una pyme: tiempo de ciclo, errores o retrabajo, pasos manuales por caso y costo por transacción. Sin línea base no hay mejora: hay sensación. Lo escribo desde el lado de quien instala el cambio. En Árkos la pregunta me llega casi siempre tarde: el sistema ya está andando, el equipo dice que «ahora es más rápido» y nadie puede demostrarlo, porque no se anotó cómo era antes. Esta guía es el método que uso para que eso no pase, y para distinguir una mejora real de una que solo se siente. ¿Qué significa que un proceso mejoró? Significa tres cosas a la vez, y las tres tienen que cumplirse: Un indicador declarado antes del cambio se movió. No «el que mejor quedó» al final: el que dijiste que ibas a mirar. El resto no empeoró. Si el pedido sale 30% más rápido pero las devoluciones subieron, no mejoró el proceso: se trasladó el costo. Se sostiene sin empujón. La semana del lanzamiento todo el mundo hace bien su trabajo porque está siendo mirado. La pregunta es qué pasa a las seis semanas, cuando ya nadie recuerda que hubo un cambio. Lo que no cuenta como mejora: que la herramienta esté instalada, que el equipo diga en una reunión que le gusta, o que el proveedor muestre un tablero con gráficos. Eso es actividad, no resultado. ¿Qué indicadores miden la mejora de un proceso? Cuatro, y casi siempre basta con dos. Los elijo por una razón práctica: se pueden sacar de registros que ya existen, sin montar nada nuevo. Tiempo de ciclo Cuánto pasa desde que el trabajo entra hasta que sale terminado. No el tiempo que alguien «le dedica», sino el reloj de pared: del pedido al despacho, de la orden abierta a la orden cerrada, de la consulta a la respuesta. Es el indicador que más le importa al cliente, porque es el único que él nota. Mídelo con la mediana, no con el promedio. Un caso raro de tres semanas te mueve el promedio y te hace creer que todo va mal; la mediana te dice cómo es el caso típico. Y mira además el percentil 90: ahí viven los clientes que se quejan. Errores y retrabajo Cuántas veces hay que volver a hacer algo: una factura reemitida, un pedido corregido, una cita reagendada, un dato que alguien vuelve a escribir porque llegó mal. Es el indicador más fácil de ignorar y el que mejor explica por qué el equipo vive apagando incendios. Cuéntalo como porcentaje de casos, no en números absolutos: si el volumen creció, 20 errores al mes pueden ser una mejora frente a 15. Pasos manuales por caso Cuántas intervenciones humanas necesita un caso para completarse. Se cuenta a mano, una vez, siguiendo tres o cuatro casos reales de principio a fin y anotando cada vez que una persona abre algo, copia algo, avisa a alguien o espera una respuesta. Es el indicador que predice mejor si automatizar va a servir, porque cada paso manual es un lugar donde el proceso puede pararse. Costo por transacción Las horas que consume un caso, multiplicadas por el costo hora de quien lo hace, más lo que cuestan los errores. Es la traducción a soles de los tres anteriores y la única cifra que sirve para decidir cuánto vale la pena invertir en arreglarlo. Hay un quinto que no es indicador de proceso pero conviene tener al lado: el volumen. Casi todo lo demás se interpreta mal si no sabes si entró más o menos trabajo que antes. ¿Cómo tomo la línea base si nunca medí nada? Es la situación normal, no la excepción. En el Perú, según la Encuesta Económica Anual 2024 publicada por el INEI, en 2023 solo el 9,6% de las empresas con ventas desde 50 UIT usaba un software ERP, el 4,8% un CRM y el 1,6% un SCM. Nueve de cada diez empresas no tienen un sistema que registre el proceso, así que la línea base no está esperando en una base de datos: hay que construirla. Tres formas, de la mejor a la peor, y todas valen más que ninguna: Reconstruirla de lo que ya existe. Los cuadernos, el Ex...

Powered by Readybot.io

**DEV Community** ## Cómo saber si un proceso mejoró de …

DEV Community

Your CSP blocks eval. So I wrote a parser.

I needed a graphing calculator that plots sin(2x) + x^2 while you type. The obvious implementation is one line: const y = eval(expression.replace(/x/g, String(x)));
It is also the one line that cannot ship, because the page sends this header: Content-Security-Policy: script-src 'self'; object-src 'none';
No 'unsafe-eval', so eval and new Function throw. That is not an accident I wanted to work around — it is the reason the site has no XSS surface from user input. The expression parser had to be real, so I wrote one. It is about eighty lines. This is how it works, and the three places where a naive implementation breaks. The pipeline Four stages, each one testable on its own: "sin(2x) + x^2" → tokens → RPN queue → evaluate(x)
If you have only ever written a calculator with eval, the surprise is that the hard part is not the arithmetic. It is deciding what -3^2 means and whether 2x is a multiplication. Stage 1 — tokenize A tokenizer turns a string into {type, value} objects. Numbers, identifiers (variables and function names), operators, parentheses, commas. const OPS = "+-*/^";
const isDigit = (c) => c >= "0" && c <= "9";
const isAlpha = (c) => /[a-zA-Z]/.test(c);

function tokenize(src) {
const out = [];
let i = 0;
while (i < src.length) {
const c = src[i];

if (c === " ") { i++; continue; }

if (isDigit(c) || (c === "." && isDigit(src[i + 1]))) {
  let j = i;
  while (j < src.length && (isDigit(src[j]) || src[j] === ".")) j++;
  // exponent form: 1e-3
  if (src[j] === "e" || src[j] === "E") {
    let k = j + 1;
    if (src[k] === "+" || src[k] === "-") k++;
    if (isDigit(src[k])) {
      while (k < src.length && isDigit(src[k])) k++;
      j = k;
    }
  }
  out.push({ type: "num", value: Number(src.slice(i, j)) });
  i = j;
  continue;
}

if (isAlpha(c)) {
  let j = i;
  while (j < src.length && (isAlpha(src[j]) || isDigit(src[j]))) j++;
  out.push({ type: "name", value: src.slice(i, j) });
  i = j;
  continue;
}

if (OPS.includes(c) || c === "(" || c === ")" || c === ",") {
  out.push({ type: c });
  i++;
  continue;
}

throw new Error(`Unexpected character: ${c}`);

}
return out;
}
Note 1e-3. Without the exponent branch, 1e-3 tokenizes as 1, e, -, 3 — and e is not a variable you defined, so the error message is confusing. Stage 2 — shunting-yard Dijkstra's algorithm, and for an expression parser it is still the shortest correct path: push numbers straight to the output queue, push operators onto a stack, pop according to precedence. const PREC = { "+": 1, "-": 1, "*": 2, "/": 2, "^": 3 };
const RIGHT = new Set(["^"]); // right-associative
const FUNCS = new Set(["sin", "cos", "tan", "log", "ln", "sqrt", "abs", "exp"]);

function toRPN(tokens) {
const out = [];
const stack = [];
let prev = null;

for (const t of tokens) {
if (t.type === "num") { out.push(t); }
else if (t.type === "name") {
if (FUNCS.has(t.value)) stack.push({ type: "func", value: t.value });
else out.push({ type: "var", value: t.value });
}
else if (t.type === "(") { stack.push(t); }
else if (t.type === ")") {
while (stack.length && stack[stack.length - 1].type !== "(") out.push(stack.pop());
if (!stack.length) throw new Error("Unbalanced )");
stack.pop();
if (stack.length && stack[stack.length - 1].type === "func") out.push(stack.pop());
}
else if (t.type === ",") {
while (stack.length && stack[stack.length - 1].type !== "(") out.push(stack.pop());
}
else {
// unary minus: only when nothing can precede it
const unary = (t.type === "-" || t.type === "+") &&
(!prev || prev.type === "(" || prev.type === "," ||
(prev.type !== "num" && prev.type !== "var" && prev.type !== ")"));
if (unary) {
if (t.type === "-") stack.push({ type: "neg", value: "-" });
...

Powered by Readybot.io

**DEV Community** ## Your CSP blocks eval. So I wrote …

DEV Community

Power BI for Sports Performance Analytics: Turning Athlete Data into Better Results

In modern sports, talent and hard work are important, but understanding performance through data can make a significant difference. Professional teams, coaches, and fitness professionals increasingly rely on analytics to evaluate athletes, improve training strategies, and monitor physical health. Every training session and competition generates valuable information, including speed, stamina, workload, recovery time, and physical performance. However, managing all this information manually can make it difficult to identify patterns and measure progress. This is where Power BI for sports performance analytics becomes useful. Microsoft Power BI helps sports organizations transform raw athlete data into interactive dashboards, making it easier to understand performance and make informed training decisions. Why Sports Teams Need Data Analytics ? Sports performance depends on several interconnected factors. An athlete may perform exceptionally well during one competition but experience fatigue or reduced performance in the following sessions. Coaches need a clear understanding of these patterns to make better decisions about training intensity, recovery, and athlete development. Power BI dashboards can help sports teams: Monitor individual athlete performance. Track training progress over time. Analyse workload and physical activity. Identify recurring injury patterns. Monitor health and recovery indicators. Compare performance across athletes and training sessions. By organizing these metrics in one place, coaches and analysts can spend less time preparing reports and more time understanding performance. Important Sports Performance Metrics to Track Using Power BI An effective sports analytics dashboard should focus on metrics that provide meaningful insights into athlete performance and physical condition. 1. Athlete Performance Tracking Performance tracking helps coaches understand how athletes are progressing throughout their training programmes. Depending on the sport and available data, important indicators may include: Running speed and sprint times Endurance and stamina Training workload Strength and physical performance Session-wise performance improvements Competition performance trends Visualizing these metrics over time helps coaches understand whether training programmes are delivering the expected results. 2. Injury Analysis and Monitoring Injuries can affect an athlete's development, training schedule, and participation in competitions. Sports organizations can use Power BI to organize injury-related information, including injury frequency, injury types, affected body areas, recovery timelines, and previous injury records. Analysing historical injury data can help sports professionals identify recurring patterns and discuss appropriate preventive strategies. It is important to remember that dashboards support monitoring and analysis; medical diagnosis and return-to-play decisions should remain with qualified healthcare professionals. 3. Athlete Health Monitoring An athlete's physical condition plays an important role in overall performance. Depending on the available data and monitoring equipment, health dashboards can bring together indicators such as heart rate, sleep duration, wellness scores, and training-related physical measurements. Having this information in a centralized report can help coaches and support staff understand changes in an athlete's condition and coordinate appropriate follow-up. 4. Recovery and Fatigue Tracking Training is only one part of athletic development. Recovery is equally important. Athletes need sufficient rest between intense training sessions to maintain performance and manage physical stress. A recovery analytics dashboard can help track: Rest and recovery periods Training frequency Workload changes Athlete-reported fatigue Sleep and wellness indicators Recovery trends over time These insights can support more informed discussions about training intensity and recovery planning....

Powered by Readybot.io

**DEV Community** ## Power BI for Sports Performance Analytics: Turning …

DEV Community

"infinite recursion detected in policy for relation" in a Supabase app

You added sharing, or teams, or an admin role. Now a page that worked yesterday loads nothing at all, and the console shows this: infinite recursion detected in policy for relation "profiles"
with the code 42P17. Nothing is corrupted and no data is lost, but as Supabase's own documentation puts it, "the query fails for every role the policies apply to", so this is not one unlucky user. One of your row level security policies asks the database a question it cannot finish answering, and PostgreSQL stops it rather than looping forever. What the database is actually complaining about A policy is a WHERE clause the database adds to every query on that table. So when a policy on profiles contains a subquery that reads profiles, the database has to apply the policy to that subquery too, which contains a subquery that reads profiles, and so on. PostgreSQL spots the loop while it is still planning the query and refuses. This is the shape almost everyone writes. You want admins to see every row, so you check whether the current user is an admin, and the only place that is recorded is the table you are protecting: -- Reject: this policy on profiles reads profiles.
create policy "admins read all" on profiles for select
to authenticated
using (
exists (
select 1 from profiles p
where p.id = (select auth.uid()) and p.role = 'admin'
)
);
The second shape is two tables that each check the other. It appears the moment you add sharing: a policy on projects reads members to see who it is shared with, and the policy on members reads projects to see who owns it. Neither can resolve without the other. Three things about this error that waste people's time I ran all of this against a real PostgreSQL 16.15 server to see what it does, rather than describing it from memory. Three results are worth knowing before you start editing policies. 1. The table named in the error is the table you queried, not the broken one. With a projects and members cycle, selecting from projects says relation "projects" and selecting from members says relation "members". Same cycle, two different messages. If you go straight to the table in the message and rewrite its policy, you may be editing the innocent one. 2. It fails while planning, so even explain fails. explain select * from profiles gives the same error. That is a quick way to confirm you have found the right table without touching any data. 3. It may well not reproduce in the SQL Editor. A table's owner bypasses row level security entirely, and I confirmed that: the same select that errors for anon returned every row for the owner, with no error at all. The SQL Editor connects with a privileged role rather than your app's anon or authenticated, so "but it works in the SQL editor" tells you nothing either way. Take on your app's role before you test, the way Supabase's own policy tests do: begin;
set local role authenticated;
set local request.jwt.claim.sub = '';
select * from profiles; -- now you should see the error
rollback;
Find the culprit in about a minute Run this to list every policy and the expression inside it. The using_expression column is where you are looking for the name of the table the policy is attached to, or the name of another table whose policy points back. select tablename, policyname, cmd,
qual as using_expression,
with_check
from pg_policies
where schemaname = 'public'
order by tablename, policyname;
Read down the list for any policy whose expression mentions its own table, and any pair of tables that mention each other. That pair is your cycle. The fix: read the membership table as its owner The documented way out is a security definer function. It runs as the role that created it, which on Supabase is postgres, and postgres has bypassrls. So the function reads the membership table without any policy being applied, the cycle never forms, and the policy gets a plain list of ids back. For the admin case above: create s...

Powered by Readybot.io

**DEV Community** ## "infinite recursion detected in policy for relation" …

DEV Community

Testing what users see when an AI safety check fails

When we built Koda, an app that teaches kids how AI works, we had a question that a normal answer-quality eval couldn't settle. If the model has a draft ready and the safety check fails, what actually reaches the screen? We built an offline test around that boundary. The application holds the draft while checking it alongside the original question. The gateway rechecks permission before it delivers an approved answer. If the detector is unavailable, the application can display a predefined message without showing the draft. Reproduce it locally With Node.js 22 or later, run: git clone https://github.com/SamSquareAI/child-ai-safety.git
cd child-ai-safety
npm ci --ignore-scripts
npm run --silent demo
The demo uses invented prompts and simulated providers. It runs without API keys. The terminal shows four paths: an approved answer, a refusal, a support response, and a simulated detector outage. In the outage case, the learner-facing message says Koda can't answer right now and suggests trying a learning question later. What to check in your own app Run the test through the same delivery path your UI uses. Make the safety adapter return unavailable after a draft exists. Assert that the application renders the fallback and that no part of the draft was emitted. In a streaming app, that means checking the entire event sequence, including chunks sent before the final message. Test an ordinary approved answer too, so you know both paths work. I help maintain Child AI Safety, the Apache-2.0 developer preview that came out of this work. If you'd like a small first contribution, issue #8 asks for a runnable example showing how the gateway counts approved and unavailable requests locally. Drafted with AI and checked against the repository's offline demo.

Powered by Readybot.io

**DEV Community** ## Testing what users see when an AI …