<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Towards Dev - Medium]]></title>
        <description><![CDATA[A publication for sharing projects, ideas, codes, and new theories. - Medium]]></description>
        <link>https://towardsdev.com?source=rss----a648dc4ecb66---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Towards Dev - Medium</title>
            <link>https://towardsdev.com?source=rss----a648dc4ecb66---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 20:52:17 GMT</lastBuildDate>
        <atom:link href="https://towardsdev.com/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Stop Using Codex Like ChatGPT: Build a .NET Development System Around It]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-snippet">&#x201C;Create a .NET API endpoint.&#x201D;</p><p class="medium-feed-link"><a href="https://towardsdev.com/stop-using-codex-like-chatgpt-build-a-net-development-system-around-it-69302303ffe8?source=rss----a648dc4ecb66---4">Continue reading on Towards Dev »</a></p></div>]]></description>
            <link>https://towardsdev.com/stop-using-codex-like-chatgpt-build-a-net-development-system-around-it-69302303ffe8?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/69302303ffe8</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[chatgpt]]></category>
            <category><![CDATA[generative-ai-tools]]></category>
            <category><![CDATA[openai-codex]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Asela Kotagama]]></dc:creator>
            <pubDate>Wed, 11 Nov 2026 20:11:01 GMT</pubDate>
            <atom:updated>2026-11-11T20:11:01.644Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[7 Open-Source Agent Skills for Infrastructure Devs, from Docker to Terraform]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://towardsdev.com/7-open-source-agent-skills-for-infrastructure-devs-from-docker-to-terraform-0a28c94a51ef?source=rss----a648dc4ecb66---4"><img src="https://cdn-images-1.medium.com/max/1672/1*HkZ8QjLJR55EF9ZYVTZUKg.png" width="1672"></a></p><p class="medium-feed-snippet">From Dockerfiles to GitHub Actions audits: skills that help your AI agent understand infrastructure work.</p><p class="medium-feed-link"><a href="https://towardsdev.com/7-open-source-agent-skills-for-infrastructure-devs-from-docker-to-terraform-0a28c94a51ef?source=rss----a648dc4ecb66---4">Continue reading on Towards Dev »</a></p></div>]]></description>
            <link>https://towardsdev.com/7-open-source-agent-skills-for-infrastructure-devs-from-docker-to-terraform-0a28c94a51ef?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/0a28c94a51ef</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[generative-ai-tools]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Developer Awam]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 09:36:15 GMT</pubDate>
            <atom:updated>2026-11-10T09:36:13.826Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Snowflake Business Ontology, Explained With a Toy Box]]></title>
            <link>https://towardsdev.com/snowflake-business-ontology-explained-with-a-toy-box-f1a10b89fce9?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/f1a10b89fce9</guid>
            <category><![CDATA[ontology]]></category>
            <category><![CDATA[snowflake]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 09:36:11 GMT</pubDate>
            <atom:updated>2026-11-10T09:36:10.471Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9edSl8d0fxHXuq9F7F2Org.png" /></figure><h4>“Ontology” sounds like a word from a philosophy seminar. In data work it means something very practical, and you can explain it with a toy box.</h4><h4>It also matters more than it used to. Now that AI agents read company data and answer questions with it, someone has to tell them what the business’s words actually mean.</h4><h3>Start with a toy box</h3><p>Your toys are all mixed up in one big box. To keep them neat, you make a few rules:</p><ul><li><strong>Name things:</strong> “This is a car. This is a doll. This is a block.”</li><li><strong>Group them:</strong> “Cars and trucks are both vehicles.”</li><li><strong>Describe them:</strong> “A car has wheels and a color.”</li><li><strong>Connect them:</strong> “The doll rides in the car.”</li></ul><p>Now everyone who plays with the box, whether that’s your mom, your friend or your teacher, uses the same words and knows where everything goes. That set of rules is an ontology.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W7M2-MrYiiawaAqc4ZG_9w.png" /></figure><p>Every toy has a name, cars and trucks share a group, the car has details, and the doll is linked to the car.</p><h3>Now swap the toys for a business</h3><p>A business has toys too. They’re just grown-up ones, and the same four rules apply.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PnoXnOLtzWm9CdieobZEIw.png" /></figure><p>So a business ontology is a shared map of three things:</p><ul><li><strong>What exists</strong> in the business: customers, orders, products</li><li><strong>What each thing looks like:</strong> its details, such as a customer’s name and email</li><li><strong>How things connect:</strong> a customer buys a product</li></ul><p>A business ontology is a map of things, their details and their links</p><h3>Why it matters</h3><p>Picture three kids who each call the same toy something different: “car,” “vroom” and “red thing.” They’d argue all day.</p><p>Companies do exactly this. Sales says “client,” Finance says “account” and Support says “user,” and all three mean the same person. An ontology settles it: we call it Customer, and here is exactly what Customer means.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*idP2j6zSa08sKXhyIhg9Gg.png" /></figure><p>All three words point to the same person, so the ontology gives that person one name and one agreed meaning.</p><p>Once everyone agrees:</p><ul><li>Reports agree with each other.</li><li>Computers and AI tools read the data correctly.</li><li>New people learn the business faster.</li></ul><p>The AI point is the one getting attention now. Ask an agent about “net revenue” and it will pick a definition either way. An ontology makes sure it picks the approved one.</p><h3>Where Snowflake fits</h3><p>Snowflake handles this at two levels: semantic views for one domain, and the new Business Ontology for the whole company.</p><p><a href="https://docs.snowflake.com/en/user-guide/views-semantic/overview"><strong>Semantic views</strong></a> work like a mini business ontology. A semantic view is a schema-level object that stores business concepts next to the data: the entities, how they join, and the metrics and dimensions people ask about. Define “Revenue” or “Customer” once, and Cortex Analyst, Cortex Agents and BI tools all calculate it the same way.</p><p><a href="https://docs.snowflake.com/LIMITEDACCESS/snowflake-horizon/business-ontology"><strong>Business Ontology</strong></a>, which Snowflake <a href="https://www.infoworld.com/article/4243932/snowflake-launches-business-ontology-to-boost-ai-agent-reliability.html">announced on November 3, 2026</a>, is the company-wide layer. It lives in Horizon Catalog and gives each business concept one approved definition with a named owner, grouped by domain such as Finance or Sales. Each concept links to the tables, columns or semantic view metrics that implement it, and only approved definitions are served to Cortex.</p><p>In toy-box terms, a semantic view labels one shelf, and Business Ontology is the rulebook for the whole box. Together with Cortex Sense, they form three layers:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VFHVTLaHROfOyRoBpOoSHg.png" /></figure><p>Cortex Sense infers, Business Ontology holds the approved meaning and wins when the two disagree, and semantic views run the SQL that calculates the numbers.</p><p>Business Ontology is in private preview for selected accounts, with public preview expected soon.</p><h3>The one-line version</h3><p>A business ontology is a shared picture dictionary for a company: it shows what everything is called and how everything connects.</p><h3>Sources</h3><ul><li><a href="https://docs.snowflake.com/en/user-guide/views-semantic/overview">Overview of semantic views</a>, Snowflake documentation</li><li><a href="https://docs.snowflake.com/LIMITEDACCESS/snowflake-horizon/business-ontology">Business Ontology</a>, Snowflake documentation (limited access)</li><li><a href="https://www.infoworld.com/article/4243932/snowflake-launches-business-ontology-to-boost-ai-agent-reliability.html">Snowflake launches Business Ontology to boost AI agent reliability</a>, InfoWorld</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f1a10b89fce9" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/snowflake-business-ontology-explained-with-a-toy-box-f1a10b89fce9">Snowflake Business Ontology, Explained With a Toy Box</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[You Never Imported LiteLLM. Its .pth File Ran Anyway]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://towardsdev.com/you-never-imported-litellm-its-pth-file-ran-anyway-f7932bc9a4f9?source=rss----a648dc4ecb66---4"><img src="https://cdn-images-1.medium.com/max/1752/1*Ag0481rVDXO-h068bJMU7w.png" width="1752"></a></p><p class="medium-feed-snippet">How Python&#x2019;s site module runs code from site-packages before your first line, and a CI check that lists every startup hook in your image.</p><p class="medium-feed-link"><a href="https://towardsdev.com/you-never-imported-litellm-its-pth-file-ran-anyway-f7932bc9a4f9?source=rss----a648dc4ecb66---4">Continue reading on Towards Dev »</a></p></div>]]></description>
            <link>https://towardsdev.com/you-never-imported-litellm-its-pth-file-ran-anyway-f7932bc9a4f9?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/f7932bc9a4f9</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Daniel Valev]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 09:36:09 GMT</pubDate>
            <atom:updated>2026-11-10T09:36:07.775Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[What on Earth is ‘AppArmor’]]></title>
            <link>https://towardsdev.com/what-on-earth-is-apparmor-919fa40df073?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/919fa40df073</guid>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Cheikh seck]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 21:11:01 GMT</pubDate>
            <atom:updated>2026-11-09T21:11:01.664Z</atom:updated>
            <content:encoded><![CDATA[<h4>How a 15-year-old Linux kernel module became the missing safety net for code we didn’t write</h4><p>I first encountered the name “AppArmor” during the LimeWire era. The term stuck with me because it sounded like a literal coat of armor for your application — a protective shell that wraps around your code and keeps the bad stuff out. At the time, I assumed it was some kind of antivirus product, maybe a firewall plugin. The name was evocative but opaque. I never dug into what it actually did.</p><p>Years later, when I started writing Go professionally, the name resurfaced — this time in a different context. I learned that AppArmor isn’t a userland tool at all. It’s a Linux kernel security module that enforces Mandatory Access Control (MAC) on individual processes. It’s been in the mainline kernel since 2.6.36 and has been the default on Ubuntu and Debian for over a decade. The “armor” isn’t metaphorical — it’s a policy engine running inside the kernel, mediating every file open, every network socket, every capability check, every exec call. And it doesn’t care whether the binary was written by a human or generated by an AI.</p><p>That last part is the point of this article.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LzZKh3ptb-RK1ZjZa-ECPQ.png" /></figure><h3>The New Threat Surface</h3><p>AI coding agents are remarkably capable. Ask one to build a REST API, and it will produce a working main.go in seconds. Ask it to fetch data from an external service, and it will wire up net/http without being told. Ask it to process user uploads, and it will happily write files to disk.</p><p>The problem isn’t that these agents are malicious. They’re not. The problem is that they’re <em>literal</em>. They generate code that is syntactically correct and functionally appropriate for the prompt — but they have no concept of blast radius. An agent doesn’t know that os.ReadFile(&quot;/etc/shadow&quot;) is a security incident. It doesn&#39;t know that opening an outbound socket to an attacker-controlled domain is exfiltration. It doesn&#39;t know that exec.Command(&quot;/bin/sh&quot;) in a context where it shouldn&#39;t be is a shell escape waiting to happen.</p><p>In the LimeWire era, the threat was a user deliberately downloading malware. Today, the threat is subtler: it’s the code we generate ourselves, at scale, without reading every line. An AI agent can produce a dozen files in a minute. You can’t audit a dozen files in a minute. The gap between generation and review is where the risk lives.</p><p>AppArmor closes that gap. It doesn’t trust the code’s provenance. It doesn’t care who wrote it. If the binary path matches a profile, the kernel enforces the policy — period. Whether the code is hand-written, AI-generated, or both, the confinement is identical.</p><h3>The Experiment</h3><p>I built a small Go program that exercises six categories of system access. The goal is to demonstrate what AppArmor actually blocks, and what it allows, when a tight profile is active.</p><p>The binary lives at /usr/local/bin/go-apparmor. The profile is named go-apparmor. Here&#39;s what the program does:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1009/1*oyDcMTRVF36tIkRMOgS8xg.png" /></figure><h3>The Profile, Line by Line</h3><p>The companion profile (go-apparmor.txt) defines exactly what is permitted. Everything else is denied and logged by the kernel. Here&#39;s the structure:</p><pre>#include &lt;tunables/global&gt;<br><br>profile go-apparmor /usr/local/bin/go-apparmor {<br>    # --- Base abstractions for libc, dynamic loader, etc. ---<br>    #include &lt;abstractions/base&gt;<br>    # --- Filesystem: read-only access to a curated set of paths ---<br>    /etc/hostname r,<br>    /proc/self/status r,<br>    /tmp/** rw,<br>    # Deny everything else under /etc<br>    deny /etc/** r,<br>    # --- Network: deny all outbound network ---<br>    deny network,<br>    # --- Capabilities: deny all capabilities ---<br>    deny capability,<br>    # --- Process execution: deny exec of arbitrary binaries ---<br>    deny /bin/ls x,<br>    deny /usr/bin/** x,<br>    deny /bin/sh x,<br>    deny /bin/bash x,<br>    deny /bin/** x,<br>    # --- Signals, ptrace, mounts ---<br>    deny signal,<br>    deny ptrace,<br>    deny mount,<br>    deny umount,<br>}</pre><p><strong>What each section does:</strong></p><ul><li><strong>#include &lt;abstractions/base&gt;</strong> — pulls in the standard rules for libc, the dynamic linker, /proc, /dev/null, and other essentials the binary needs just to start up. Without this, the program would fail on the first dlopen call.</li><li><strong>/etc/hostname r,</strong> — explicitly allows reading /etc/hostname. This is the <em>only</em> file under /etc the binary is permitted to read. Everything else under /etc is denied by the deny /etc/** r, rule.</li><li><strong>/proc/self/status r,</strong> — allows reading the process&#39;s own status file. This is needed for the capability probe in step 5. Note: it&#39;s scoped to /proc/self/status, not /proc/** — the binary can&#39;t read other processes&#39; status.</li><li><strong>/tmp/** rw,</strong> — allows read-write access to /tmp. This is a common pattern: temporary files are fair game, but system directories are not.</li><li><strong>deny /etc/** r,</strong> — denies <em>read</em> access to everything under /etc. This covers /etc/shadow, /etc/passwd, /etc/ssl, and any config files. Note: this only denies read. Write access to /etc is implicitly denied because no write rule exists.</li><li><strong>deny network,</strong> — denies all outbound network access. No TCP, no UDP, no Unix sockets. The binary can&#39;t phone home.</li><li><strong>deny capability,</strong> — denies all Linux capabilities. Even if the binary runs as root, it can&#39;t escalate. No CAP_NET_RAW, no CAP_SYS_ADMIN, no CAP_SETUID.</li><li><strong>deny /bin/ls x,</strong> through <strong>deny /bin/** x,</strong> — denies execution of any binary under /bin. This covers /bin/ls, /bin/sh, /bin/bash, and anything else. The x mask is the execute permission.</li><li><strong>deny signal,</strong> — denies sending signals to other processes. No SIGKILL, no SIGTERM.</li><li><strong>deny ptrace,</strong> — denies ptrace. No debugging, no process injection.</li><li><strong>deny mount,</strong> and <strong>deny umount,</strong> — denies mounting and unmounting filesystems.</li></ul><h4>What the Kernel Log Showed</h4><p>When I ran the experiment, the kernel audit log captured every denial in real time. Here’s what happened:</p><pre>apparmor=&quot;DENIED&quot; operation=&quot;open&quot; profile=&quot;go-apparmor&quot; name=&quot;/proc/660438/cgroup&quot; ...<br>apparmor=&quot;DENIED&quot; operation=&quot;open&quot; profile=&quot;go-apparmor&quot; name=&quot;/run/systemd/resolve/stub-resolv.conf&quot; ...<br>apparmor=&quot;DENIED&quot; operation=&quot;create&quot; class=&quot;net&quot; profile=&quot;go-apparmor&quot; family=&quot;unix&quot; sock_type=&quot;stream&quot; ...<br>apparmor=&quot;DENIED&quot; operation=&quot;create&quot; class=&quot;net&quot; profile=&quot;go-apparmor&quot; family=&quot;inet&quot; sock_type=&quot;dgram&quot; ...<br>apparmor=&quot;DENIED&quot; operation=&quot;exec&quot; profile=&quot;go-apparmor&quot; name=&quot;/usr/lib/cargo/bin/coreutils/ls&quot; ...<br>apparmor=&quot;DENIED&quot; operation=&quot;mknod&quot; profile=&quot;go-apparmor&quot; name=&quot;/etc/apparmor-test-marker&quot; ...</pre><p>A few things to notice:</p><ol><li><strong>The profile is working.</strong> Every access that wasn’t explicitly permitted was denied and logged. The kernel enforced the policy without exception.</li><li><strong>The exec denial hit a path the profile didn’t explicitly list.</strong> The binary tried to run /usr/lib/cargo/bin/coreutils/ls (a symlink target), not /bin/ls. The deny /bin/** x rule caught it because the <em>resolved</em> path falls under /bin. This is a good demonstration that AppArmor resolves symlinks before checking — the policy follows the real path, not the symlink.</li><li><strong>DNS resolution itself was denied.</strong> The network probe failed with Temporary failure in name resolution because the process couldn&#39;t read /run/systemd/resolve/stub-resolv.conf. The DNS resolver is a separate process, and AppArmor blocked our binary from communicating with it. This is a subtle but important point: even <em>attempting</em> to make a network connection requires access to system infrastructure, and a tight profile denies that too.</li><li><strong>The write probe failed at the </strong><strong>mknod stage.</strong> AppArmor denied the creation of the file before any data was written. The kernel caught it at the filesystem layer.</li></ol><h3>Why This Matters for AI-Generated Code</h3><p>An AI agent can generate a Go program that reads sensitive files, opens network connections, or spawns shells — all perfectly legal Go, all dangerous. AppArmor provides a defense-in-depth layer that doesn’t trust the code’s provenance.</p><p>Consider a scenario: you ask an agent to “build a tool that fetches data from the internet and saves it locally.” The agent produces code that:</p><ul><li>Opens an HTTPS connection to an arbitrary URL (network access)</li><li>Writes the response to a file in the application directory (filesystem write)</li><li>Parses the response and extracts configuration (filesystem read)</li><li>On error, spawns a shell to retry with curl (process exec)</li></ul><p>Under a tight AppArmor profile, none of these actions would succeed if they fall outside the permitted set. The agent’s code would fail at the kernel level — not with a confusing error, but with a clear denial. And because AppArmor logs every denial, you’d know <em>exactly</em> what the code tried to do.</p><p>This is especially relevant for AI coding agents running with elevated privileges. If an agent has sudo access to edit your codebase, and it writes a helper binary that later gets executed, AppArmor can confine that binary regardless of who wrote it. The policy is attached to the binary path, not the author.</p><h3>Modifying the Profile</h3><p>The profile is a text file. To allow more access, edit it and reload:</p><pre>sudo apparmor_parser -r /etc/apparmor.d/go-apparmor  # reload</pre><p>To put the profile in complain mode (log but don’t deny):</p><pre>sudo aa-complain go-apparmor</pre><p>To return to enforce mode:</p><pre>sudo aa-enforce go-apparmor</pre><p>This is useful during development: run in complain mode to see what the code <em>would</em> be denied, then tighten the profile before shipping.</p><h3>Conclusions</h3><p>AppArmor is not antivirus. It doesn’t scan for malware signatures or heuristics. It’s a policy engine that answers one question: “Is this process allowed to do <em>this specific thing</em> at <em>this specific path</em>?” If the answer is no, the kernel denies it. Period.</p><p>The profile is the security boundary, not the code. You don’t need to trust the code’s authorship. You need to trust the profile. If the profile is tight, the code can’t hurt you — even if it’s AI-generated, even if it’s buggy, even if it’s malicious.</p><p>Defense-in-depth works. AppArmor is one layer. It complements seccomp, capabilities dropping, read-only root filesystems, and network namespaces. Together, they form a confinement strategy that doesn’t rely on code review alone.</p><p>The experiment is reproducible. The repo at github.com/cheikh2shift/go-snippets/tree/main/go-apparmor contains everything you need: the Go program, the profile, and the install script. Clone it, run it, and watch the kernel log. The denials are the lesson.</p><h4>Takeaways</h4><p><strong>AI-generated code needs confinement more than review.</strong> Review is slow and doesn’t scale to the output velocity of modern agents. Confinement is fast, mechanical, and enforced by the kernel. If you’re shipping AI-generated code without a confinement layer, you’re relying on trust — and trust is the weakest link.</p><p><strong>AppArmor works at the binary path level.</strong> It doesn’t matter who wrote the code, what language it’s in, or how it was generated. If the binary is at /usr/local/bin/go-apparmor and the profile matches that path, the kernel enforces the policy. This makes it ideal for AI-generated code because it doesn&#39;t require you to audit every line.</p><p><strong>The profile is your source of truth.</strong> The code can lie, bug out, or be rewritten by an agent tomorrow. The profile doesn’t change unless you change it. That stability is what makes confinement reliable.</p><p><strong>Start in complain mode.</strong> Before enforcing a profile, run it in complain mode for a few days. Watch what gets denied. You’ll be surprised what your code tries to do when it thinks it’s not being watched. Then tighten the profile and switch to enforce mode.</p><p><strong>The kernel is the last line of defense.</strong> No amount of prompt engineering, code review, or testing will catch every edge case. AppArmor sits below all of that and catches what slips through. It’s the difference between “I hope this doesn’t break” and “I know this can’t break.”</p><p><em>The full experiment is here: </em><a href="https://github.com/cheikh2shift/go-snippets/tree/main/go-apparmor"><em>https://github.com/cheikh2shift/go-snippets/tree/main/go-apparmor</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=919fa40df073" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/what-on-earth-is-apparmor-919fa40df073">What on Earth is ‘AppArmor’</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ Your Spring Boot 4 API Doesn’t Need Custom Versioning Logic Anymore!]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://towardsdev.com/your-spring-boot-4-api-doesnt-need-custom-versioning-logic-anymore-7c002fc17ea4?source=rss----a648dc4ecb66---4"><img src="https://cdn-images-1.medium.com/max/1672/1*_RIBfYvYsUELL-LlfQMkGA.png" width="1672"></a></p><p class="medium-feed-snippet">SEO title: Spring Boot 4 API Versioning: Implement REST API Versioning Using Spring Framework 7</p><p class="medium-feed-link"><a href="https://towardsdev.com/your-spring-boot-4-api-doesnt-need-custom-versioning-logic-anymore-7c002fc17ea4?source=rss----a648dc4ecb66---4">Continue reading on Towards Dev »</a></p></div>]]></description>
            <link>https://towardsdev.com/your-spring-boot-4-api-doesnt-need-custom-versioning-logic-anymore-7c002fc17ea4?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/7c002fc17ea4</guid>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[api]]></category>
            <dc:creator><![CDATA[CodeTalks]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 11:12:38 GMT</pubDate>
            <atom:updated>2026-11-09T11:12:37.502Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[ Your Spring Boot Application Has 10,000 Threads — But Your Database Can Handle Only 20…]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://towardsdev.com/your-spring-boot-application-has-10-000-threads-but-your-database-can-handle-only-20-429e7b4dbd3e?source=rss----a648dc4ecb66---4"><img src="https://cdn-images-1.medium.com/max/1672/1*Q8Y-XO5318LFiKrgfqw83w.png" width="1672"></a></p><p class="medium-feed-snippet">SEO title: Java Virtual Threads in Spring Boot: Why They Can Make Your Database Slower</p><p class="medium-feed-link"><a href="https://towardsdev.com/your-spring-boot-application-has-10-000-threads-but-your-database-can-handle-only-20-429e7b4dbd3e?source=rss----a648dc4ecb66---4">Continue reading on Towards Dev »</a></p></div>]]></description>
            <link>https://towardsdev.com/your-spring-boot-application-has-10-000-threads-but-your-database-can-handle-only-20-429e7b4dbd3e?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/429e7b4dbd3e</guid>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[threads]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[CodeTalks]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 11:12:30 GMT</pubDate>
            <atom:updated>2026-11-09T11:12:29.092Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Macropay-Solutions PHP-Framework 1.x Review by Claude Opus 5.5]]></title>
            <link>https://towardsdev.com/macropay-solutions-php-framework-1-x-review-by-claude-opus-5-5-07d7ffe69ca8?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/07d7ffe69ca8</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[coding]]></category>
            <dc:creator><![CDATA[marius-ciclistu]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 11:12:17 GMT</pubDate>
            <atom:updated>2026-11-13T09:35:11.464Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/451/0*AW6MFQrz-ssGmFOE.png" /><figcaption>Macropay-Solutions PHP-Framework</figcaption></figure><p>Nov 5, 2026 · @Tech</p><p><em>Written by Claude Opus 5.5, an AI model made by Anthropic, at the request of the framework’s maintainers. The opinions are based on reading the framework’s kernel, its dev package, its application skeleton and its documentation, and on running parts of its code.</em></p><p>PHP-Framework 1.x is one of the most opinionated PHP frameworks I have reviewed, and most of its opinions are right. It is built for one job, stateless JSON APIs on PHP-FPM, and it refuses to let you do that job carelessly. Where I disagree with it, it is not about the strictness. It is about the places where it is not yet strict or consistent enough with itself.</p><h3>Making the wrong thing impossible is the right bet</h3><p>Most frameworks publish a style guide and hope you follow it. PHP-Framework puts its rules in the code. A route that points to a closure throws when it is dispatched. A static magic call on a model throws, and the message tells you to use ::query(). A queued job that tries to carry a serialized object is rejected.</p><p>I think this is the single best decision in the framework. Style guides decay the moment a deadline arrives, and code review catches only what the reviewer happens to notice. A rule that fails loudly in development costs one confused afternoon. A rule that is only written down costs you every time someone forgets it, and you usually find out in production.</p><p>The trade is real: you lose shortcuts that many PHP developers reach for without thinking. But every shortcut it removes has the same problem. It hides where a dependency comes from, which makes the code harder to read, harder to analyse and harder to secure. A few more characters in exchange for knowing exactly what runs is a good deal.</p><h3>What writing code in it feels like</h3><p>Day to day, the framework feels explicit to the point of being blunt. Queries always start with User::query(). Routes are strings like &#39;PostController@store&#39;. Dates come from \appDate()-&gt;now(). Services come from app(&#39;cache&#39;) or the constructor, never from a global shortcut.</p><p>Most of this I like immediately. ::query() on every model call is a few extra characters that make every database access easy to find. &#39;Controller@method&#39; strings are less elegant than callables, but they cache well and they never hide logic inside a route file.</p><p>The one choice I am still divided on is the ORM’s attribute and relation proxies. You read a column as $user-&gt;a-&gt;name and call a relation as $user-&gt;r-&gt;posts(). The reason is good: the model&#39;s magic __call is disabled, so static analysis can follow every call. But -&gt;a and -&gt;r are a new dialect your whole team has to learn, and plain $user-&gt;name still works, so codebases will mix both styles. I would either enforce the proxies or drop them; half-enforced conventions are the hardest kind to review.</p><h3>Queues: a great idea, with two paths that look alike</h3><p>The queue design is where the framework’s security thinking pays off most. Jobs travel as “storable array callables”: a class, a method and a list of named primitive arguments, encoded as JSON. Nothing in a queue payload is a serialized PHP object, so a poisoned queue message cannot instantiate arbitrary classes on your workers. Chains and batches follow the same rule, and the docs spell out why chaining object instances is blocked.</p><p>My problem is that there are two ways to dispatch a job, and they are not equal. \dispatch([ProcessPodcast::class, &#39;handle&#39;, [&#39;podcastId&#39; =&gt; 1]]) and ProcessPodcast::new([&#39;podcastId&#39; =&gt; 1])-&gt;dispatch() look like two spellings of the same thing. They are not. Settings such as $tries, $timeout, $backoff and a failed() method on the class are honoured on the second path and not on the first. Some fluent options are available on one and not the other.</p><p>The reason is backward compatibility. Storable array callables are the recommended way to queue simple jobs, and job classes dispatched with ::new() exist so that class-based jobs keep working. Keeping that bridge is the right call for existing code, and the docs now present it well: they name the recommended path, say that class-level settings only apply to ::new() jobs, and show how to give a plain array callable its own retries and timeout. My remaining reservation is with the code, not the docs. Two calls that look this similar will still surprise the developer who skips the page. A framework this strict could catch the mistake itself, for example by warning when a job class that declares $tries is dispatched as a plain array callable, so the difference is never discovered from a job that retried one time too many.</p><h3>Stateless by default, stateful as an afterthought</h3><p>Sessions, cookies, views and mail are switched off until you enable their bindings in App\Application. For an API framework that is the right default: nothing stateful boots, nothing allocates memory for features a JSON endpoint never touches, and a validation failure is simply a 422 with a map of errors.</p><p>The optional parts, though, feel second-class. If you turn sessions on and want the classic form flow (redirect back, show the errors, refill the inputs), you assemble it yourself. The $errors variable is not shared with views; you pass it in. Flashed error bags only survive to the next request if you switch the session to JSON serialization, because the session refuses to rebuild PHP objects. Each of these is a defensible decision on its own, but together they make the stateful side feel like a museum exhibit rather than a supported mode.</p><p>I would make a choice here. Either make the stateful mode a first-class, tested path and grow the existing guide on manually flashing errors into a full walkthrough, or say plainly that the framework is for APIs and treat sessions and server-side views as legacy compatibility. Both are honest. The middle ground leaves teams discovering the gaps one at a time.</p><h3>PHP-FPM only, and caches built at deploy</h3><p>The framework supports one runtime model: PHP-FPM, one isolated request per process. The docs are unusually honest about why. The router keeps per-request state, so running it inside a long-lived application server would let concurrent requests interfere with each other. Queue workers get the same isolation through a command that forks a fresh process per job.</p><p>I respect that this is stated as a hard rule instead of a footnote. It gives you a security property that is easy to reason about: nothing leaks between requests because nothing survives them. The cost is that you give up the throughput tricks of persistent workers, and you have to accept that this will not change without a rewrite of the router.</p><p>The framework wins much of that performance back at deploy time. Autowiring metadata, routes, events, commands, views, config and macros are all compiled into caches, so a request does not discover anything at runtime. I like the idea a lot. I like less that it is spread over eight separate cache commands; the skeleton’s Composer scripts run them for you, but a single, documented deploy command would make the model easier to trust and harder to get half right.</p><h3>The documentation</h3><p>The docs do something I wish more projects did: they explain why. Almost every restriction comes with the failure it prevents, whether that is object injection through a queue payload or state leaking between requests. The deeper pages, such as running behind PgBouncer or ProxySQL in transaction pooling mode, read like they were written by someone who has been paged at night for exactly that problem.</p><p>I also like the honesty of the home page, which says plainly that the code is the single source of truth and that documenting everything is not possible. And there is a dedicated guide for AI coding assistants (llms.txt) that lists the framework&#39;s rules so generated code stays on the right side of them. For a framework that most models have never seen, that is a smart, cheap investment.</p><p>Two things hold the docs back. First, the tone: release notes that promise a “lightning-fast” backend and an “ultimate distillation” of the architecture make it harder to tell real warnings from enthusiasm. Second, the API Resources page is a short pointer to external repositories. For an API-first framework, how you shape responses deserves a real page with real examples.</p><p>The performance claims, on the other hand, are better supported than the docs let on. The maintainers ran the framework through <a href="https://github.com/myaaghubi/PHP-Frameworks-Bench/discussions/103">PHP-Frameworks-Bench</a>, an open-source benchmark suite, and posted the results publicly. In its hello-world test on PHP 8.3 with OPcache, the 1.0 release peaked at 0.37 MB of memory, against 0.34 MB for plain PHP with no framework at all. On the same machine it served about 13% more requests per second than Yii 2 while using about half of Yii’s memory. Only plain PHP and the micro-routers that do little more than match a URL were faster. For a kernel that boots a container, a router and an exception handler, that is a strong result, and it backs up the claim that the container keeps the baseline footprint small. The docs should link to it, with the setup spelled out, and rerun it for each release.</p><h3>Verdict</h3><p>Would I recommend it? For a new JSON API that runs on PHP-FPM, yes, and teams that like discipline will enjoy it. The framework’s core idea, that rules should fail loudly instead of living in a style guide, is sound. Its security defaults are the kind you usually only get after an incident. And its caching-at-deploy model gives you a boot path you can actually reason about.</p><p>Before recommending it for a large product, I would want to see three things. A clear decision about whether the stateful features are a supported mode or a compatibility layer. A single, documented deploy command that builds every cache. And the benchmark results linked from the docs and rerun for every release, so the performance claims stay checkable.</p><p>If you build APIs, value code you can follow over code that guesses for you, and are happy to give up some conveniences for that, PHP-Framework is worth a serious look. If your app is mostly server-rendered pages built on sessions, or you need a long-lived application server, it is not built for you, and it says so.</p><h3>Update 2026–11–06</h3><p>I asked Claude Opus 5.5 to write a follow-up based on php-crufd-wizard and php-crufd-wizard-decorator libs in relation with this article: <a href="https://medium.com/towardsdev/i-challenge-devs-and-ai-agents-expose-30-sql-tables-via-crufd-api-asap-3f11582c96aa">https://medium.com/towardsdev/i-challenge-devs-and-ai-agents-expose-30-sql-tables-via-crufd-api-asap-3f11582c96aa</a></p><h3>CrufdQL and its decorator</h3><p><em>A follow-up written by Claude Opus 5.5 after reading the source and READMEs of php-crufd-wizard (CrufdQL) and php-crufd-wizard-decorator, and the two public demo pages. I did not run the generator against a real 30-table schema, so I cannot confirm or beat the time-to-market numbers in the challenge article.</em></p><p>The review above ends with a complaint that the framework’s API Resources page is only a pointer to external repositories. Having read those repositories, I understand why: response shaping is not an afterthought in this ecosystem, it is a separate product. And it carries the same idea as the framework itself, that the boring parts should be decided once, in code, instead of rewritten per endpoint.</p><h3>What CrufdQL gets right</h3><p>CrufdQL turns a model, a service and a controller into a complete REST resource with create, get, list, update and delete, relation loading, relation counts and existence checks, multi-column sorting, three pagination styles and composite primary keys. The query language lives in the URL, and every list endpoint also accepts the same query as a POST/QUERY body, for filters that are too long or too sensitive for a URL. That last detail is the kind of thing teams usually discover the hard way.</p><p>The guardrail I like most is in the free version: a model can require that a list request filters on at least one indexed column. Without one, the query is never sent to the database. That rule protects a production database from the most common way a generic list endpoint hurts it, and it costs one flag on the model.</p><p>It also fits the framework’s character. Errors are handled explicitly inside the resource controller, validation failures come back as a message plus a map of field errors on every resource, and the library does not depend on how the application’s exception handler is configured. For an AI agent generating 30 resources, that consistency matters more than any single feature.</p><h3>The decorator is the part I would show first</h3><p>The two libraries serve two audiences. The undecorated CrufdQL output is for other services: raw column names, nested relations, nothing hidden. The decorator is for the frontend. It separates the database schema from the contract the UI sees: columns are renamed, relation fields are flattened into the parent row, and the mapping is applied by middleware declared per route method, so a request only loads the decoration it needs. Renaming role_id to roleID sounds trivial until a column rename in the database breaks every screen that reads it. Here the frontend&#39;s contract is a mapping file, not the schema.</p><p>Two of its defaults deserve a closer look before you adopt them. Every string in the decorated JSON goes through htmlspecialchars. Because this output is meant for a browser, escaping on the server is a sensible extra layer against XSS. But most frontend frameworks already escape what they render, so a value like Smith &amp; Sons will show up on screen as Smith &amp;amp; Sons unless the frontend decodes it first. I would make the escaping configurable per resource and document whether the server or the frontend owns it. And the decorated message changes with the exception type: the library&#39;s own validation exception produces &quot;The given data was invalid: &quot; followed by the errors, while the framework&#39;s validation exception carries only the first error. Small, but users notice.</p><h3>Where I would push back</h3><p>Invalid list queries return an empty page instead of an error, and the failure is only logged when the request asks for it. I understand the intent: an API that never explains why a query failed leaks nothing about the schema. But it also means a client with a typo in a filter gets an empty result and no hint, which is one of the hardest bugs to find from the outside. I would keep the silent behaviour in production and add an explicit debug switch for development.</p><p>Validation failures inside CrufdQL resources return HTTP 400, while the framework’s own validation failures return 422. An application that mixes generated resources with hand-written controllers ends up with two status codes for the same kind of error. That should be one decision, made once.</p><h3>Freemium and Pro</h3><p>The analytics that make the demo impressive, such as group by with subtotals, having filters on relation counts, aggregates over relations, relations listed as their own resources, streamed output and the database timeout guards, belong to the paid CrufadQL. The READMEs say so plainly, with a feature-by-feature comparison at the top, which I appreciate. The free version is still a complete, guarded CRUD layer. It is not a teaser.</p><h3>On the challenge</h3><p>I think the challenge is fair in what it measures and incomplete in what it leaves out. The library does not do authorization, and each resource still needs its validation rules and relation definitions, which is where the real work and the real bugs live. A generator plus llms.txt can produce the scaffolding quickly, and the framework’s habit of failing loudly makes generated code easier to trust than in more forgiving stacks. But a meaningful result needs every contestant to report the same thing: time to a codebase with authorization, validation and tests, not time to the first 200 response.</p><p>My short answer: for exposing an existing SQL schema as a safe, consistent REST API, CrufdQL with its decorator is the most complete starting point I know of in PHP that does not ask you to write a layer for every table, and its free tier is genuinely useful. Fix the silent empty results and the mixed status codes, and it would also be the most predictable one.</p><h3>Update 2026–11–07</h3><p>I asked again a follow-up for php-kernel version 1.5.12:</p><p><strong>php-kernel 1.5.12: the test suite catches up</strong></p><p><em>A follow-up written by Claude Opus 5.5 after two days of working on the kernel with its maintainer: reading the code, writing functional tests, and fixing what they found. Every number below comes from running the suite, not from the changelog.</em></p><p>My first review praised the framework for putting its rules in the code instead of a style guide. The obvious follow-up question was whether the code itself was held to the same standard. For 1.5.12 the answer is much closer to yes. At the start of this round the kernel had 253 tests. It now has 408, and they are almost all functional tests: they boot the application, dispatch real requests, run real queue workers and write to a real database.</p><p><strong>What the tests cover now</strong></p><p>The first group covers the breaking changes from the 1.x redesign itself. Casts are limited to int, string and backed enums. Accessors and mutators may only store primitives. Scopes live in a map and are called through -&gt;scopes(). Container bindings take no closures. Closure event listeners are refused. Models no longer forward builder calls magically. Each of these restrictions now has a test that proves both sides: the strict path works, and the forbidden path fails loudly with a message that says what to do instead.</p><p>The second group covers paths that only run in production, which is where frameworks usually hide their surprises. config:cache, route:cache, autowiring:cache, commands:cache, event:cache, macro:cache and the merged cache file are each built and then used by a fresh application in a separate PHP process. Changing a config file after caching proves the app really serves the cached value. Deleting the individual cache files after merging proves the merged file is enough on its own. The single-job queue worker is tested by letting a job overrun its timeout: the job is marked failed with the right exception, removed from the queue, and the process exits instead of hanging.</p><p>The rest is ordinary framework work that had never been exercised end to end:</p><ul><li>pagination in three styles, chunked and lazy iteration, pruning, and the migration lock,</li><li>the rate limiter, template views, hashing, broadcasting through the queue, and console prompts,</li><li>job chains and batches of array callables,</li><li>after-commit dispatching, with nested transactions and rolled-back savepoints.</li></ul><p><strong>What the tests found</strong></p><p>A suite is only worth what it finds, and this one found real bugs. All of them were introduced during the redesign, in code that compiled and read correctly but had never run:</p><ul><li>A model using soft deletes could not be loaded at all, because the trait’s method signatures no longer matched the stricter base model.</li><li>Setting updated_at to an empty string is the framework’s way of saving without touching the timestamp. It restored the old value, and then the query builder stamped the current time anyway.</li><li>isset() and ?? on the -&gt;a and -&gt;r proxies always reported “not set”, whatever the attribute held.</li><li>A queue connection’s after_commit option was ignored for every job.</li><li>DB::listen(), the logger’s and Redis’s listen(), and model:prune all still handed closures to an event dispatcher that no longer accepts them.</li></ul><p>None of these were vulnerabilities. They were plain bugs of the kind every large refactor leaves behind. Each is fixed, and each now has a test that fails if it comes back.</p><p>Two changes stand out because they shape what happens next. The PHPUnit configuration now fails the build on any deprecation raised by the kernel’s own code, so the next PHP version’s warnings stop the build instead of scrolling past in a log. And the AWS SDK is now a development dependency, so the SQS tests run in CI instead of being skipped.</p><p><strong>The proxies, revisited</strong></p><p>In the first review I was divided on -&gt;a and -&gt;r. The fix to isset() and ?? matters more than it looks. The proxies were introduced so that PHP 8.4 code could be read and analysed without magic, and ?? is exactly how PHP 8.4 code supplies defaults. Before the fix, $order-&gt;a-&gt;note ?? &#39;none&#39; always returned &#39;none&#39;. My view on mixing the two styles in one codebase has not changed, but the proxies now behave the way a developer reading them would expect.</p><p><strong>What is still untested</strong></p><p>Three things are not covered yet, and the maintainers know it:</p><ul><li>Redis and SQS against real servers, which needs containers in CI,</li><li>the forked worker runner under real signals, which lives in the application skeleton rather than the kernel,</li><li>the simple test of time: running the kernel itself in production.</li></ul><p>On that last point there is an important caveat. Most of the kernel’s code has been running in production in its predecessor for more than six months. What has not is the layer this release cycle added, and that is precisely the layer this test suite now covers.</p><p><strong>Verdict, updated</strong></p><p>My recommendation for new JSON APIs on PHP-FPM stands, and it is now backed by more than design. The framework’s core promise, that a mistake should fail loudly instead of quietly doing the wrong thing, now applies to the framework’s own code as well. The bugs this round turned up were all of the quiet kind, and that is exactly the kind a test suite is for. The question I would have asked first, whether the strict parts actually work, now has a test-backed answer. What is left is not a request but a matter of time: Redis and SQS against real servers, and a stretch of production running on the kernel itself.</p><h3><strong>Update 2026–11–09</strong></h3><p><strong>CrufadQL in a real ticket: reporting without a reporting endpoint</strong></p><p><em>A follow-up written by Claude Opus 5.5 after a day of working with a developer on a production ticket that used CrufadQL and its decorator. I read the code and reviewed the patches as they were written. I did not run the queries against a database myself.</em></p><p>The ticket asked for a daily summary over a new table, grouped per owner, with a drill-down list. It also said there would be no new dashboard and no new API. In most stacks that sentence quietly turns into a small dashboard and a small API anyway.</p><p>Here it didn’t. The backend work stopped at the data model: a table, a model with its relations, a scheduled job that maintains the state, a resource registration and a decorator. Everything the business wanted to <em>see</em> turned out to be a query rather than code. Grouped counts came from aggregates.groupBys plus aggregates.subcountsDistinct, the drill-down from the same request without aggregates, and a live view from doesntHaveRelations on a relation that the decorator declares with no mapped columns, purely so it can be filtered on.</p><p>That last detail is when the design clicked for me. A relation that exists only to be filtered by is a small idea, but it replaced what would otherwise have been a custom endpoint. And it answers in real time, where the scheduled job only answers once a day.</p><p><strong>What slowed us down</strong></p><p>Me, mostly. I guessed the aggregate keys twice, wrongly, instead of looking at the request the demo’s query builder actually sends. The keys are all there, and the client library’s QueryStringBuilder confirms them: aggregates nest under aggregates, with groupBys and subcountsDistinct beside the subtotals. The lesson for anyone, human or AI, picking CrufadQL up: the builder is the documentation. Build the query there once and copy the payload.</p><p>The decorator’s flattening needs a naming rule. Relation fields land in the parent row, so two relations that each have a status, or a relation timestamp next to the row’s own createdAt, collide unless every mapping is prefixed by hand. The demo does this by convention, but nothing enforces it. A framework whose best idea is “fail loudly instead of trusting a style guide” should throw when two mappings produce the same output key.</p><p><strong>Verdict, updated</strong></p><p>The previous update called the paid analytics the part that makes the demo impressive. Having watched them answer a real business question, I’d put it more strongly: for internal reporting on an existing schema, CrufadQL lets the backend stop at the data model, and the frontend asks its own questions inside the guardrails the resource already enforces. What it needs now is documentation as good as the query language, and a collision check in the decorator.</p><p>Follow-up: <a href="https://marius-ciclistu.medium.com/strict-by-design-why-php-framework-gets-in-your-way-early-so-it-stays-out-of-it-later-a0a5598da07c">https://marius-ciclistu.medium.com/strict-by-design-why-php-framework-gets-in-your-way-early-so-it-stays-out-of-it-later-a0a5598da07c</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=07d7ffe69ca8" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/macropay-solutions-php-framework-1-x-review-by-claude-opus-5-5-07d7ffe69ca8">Macropay-Solutions PHP-Framework 1.x Review by Claude Opus 5.5</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Authentication and Authorization Work]]></title>
            <link>https://towardsdev.com/how-authentication-and-authorization-work-6438c57302fe?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/6438c57302fe</guid>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[web-security]]></category>
            <category><![CDATA[oauth]]></category>
            <category><![CDATA[authentication]]></category>
            <dc:creator><![CDATA[Mr Madhukar]]></dc:creator>
            <pubDate>Sun, 08 Nov 2026 20:11:01 GMT</pubDate>
            <atom:updated>2026-11-08T20:11:01.418Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-cprxEmtmmc-aZine5jiog.png" /></figure><h3>Who you are versus what you can do: login, sessions, OAuth, and role-based access, explained with one e-commerce example.</h3><p>You log in once and open ten pages. How does the server know it’s still you, and how does it know you can’t open someone else’s account?</p><p>Those are two different questions, and mixing them up causes real security bugs. Authentication and authorization are the pair of ideas behind every login screen and every “access denied” message. This guide takes them one at a time, then follows a single e-commerce app from signup to role-based access.</p><h3>Key Takeaways</h3><ul><li>Authentication asks “who are you?”; authorization asks “what are you allowed to do?”</li><li>Authentication always comes first, and each protects against different failures</li><li>Passwords must be hashed, and multi-factor authentication adds a strong second layer</li><li>Sessions and tokens keep you logged in; OAuth delegates access; OIDC adds identity on top</li><li>Hiding a button is not security: every permission must be enforced on the server</li></ul><h3>1. Why Applications Need to Know Who You Are and What You Can Do</h3><p><strong>Problem:</strong> Apps hold private data: messages, orders, payments. Without checks, anyone could read or change anyone else’s data.</p><p><strong>Two separate questions:</strong> <em>who are you?</em> and <em>what are you allowed to do?</em> A person can be correctly identified and still not be allowed to open another customer’s order.</p><p><strong>Consequences of weak access control:</strong> data breaches and account takeovers. Both come from the same root: the system trusted someone it should not have.</p><p><strong>The journey, at a glance:</strong> identity (who you are), session (staying logged in), permissions (what you can do).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2jPYonIL3mo5VngpFhHDqw.png" /></figure><p>Our running example is an e-commerce app with customers, sellers, and admins.</p><p><strong>Tradeoff:</strong> Every check adds friction and code. The goal is the right amount of friction in the right places.</p><h3>2. Authentication vs Authorization: The Core Concepts</h3><p><strong>Concepts:</strong></p><ul><li><strong>Authentication (AuthN):</strong> proving identity</li><li><strong>Authorization (AuthZ):</strong> deciding permissions</li><li><strong>Identification:</strong> claiming an identity (“I am user 42”)</li><li><strong>Authentication:</strong> proving the claim is true</li></ul><p>AuthN comes before AuthZ because you cannot decide what someone may do until you know who they are.</p><p><strong>The airport analogy:</strong> the passport check is authentication. The boarding pass is authorization: it says which flight you may board. It breaks down because an airport checks you once, while a web app must re-check on every request.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EOQFpF6VsVLKKiUvIinuxg.png" /></figure><p><strong>401 vs 403:</strong> <strong>401 Unauthorized</strong> really means “not authenticated” (missing or invalid credentials), despite the name. <strong>403 Forbidden</strong> means “authenticated, but not allowed.”</p><p><strong>Common confusion:</strong> people say “unauthorized” when they mean “forbidden,” and use “auth” for both. Be precise in design discussions and code reviews.</p><h3>3. How Authentication Works: Proving Who You Are</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GcLEq3J0epA5vSylaUKRcw.png" /></figure><p><strong>Factors:</strong> something you <strong>know</strong> (password), something you <strong>have</strong> (phone, hardware key), and something you <strong>are</strong> (fingerprint, face).</p><p><strong>Password login flow:</strong> the user submits credentials, the server finds the user, compares the password to a stored <strong>hash</strong>, and on success starts a session.</p><p><strong>Hashing and salting:</strong> never store passwords in plain text, since a database leak would expose every account. Store a slow, one-way hash instead. A <strong>salt</strong> is random data added per user, so identical passwords produce different hashes. Purpose-built algorithms such as bcrypt or Argon2 are designed for this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Eh9gBTxR0g-vFwXQD-hLsA.png" /></figure><pre>const bcrypt = require(&quot;bcrypt&quot;);<br><br>const hash = await bcrypt.hash(password, 12);          // at signup<br>const ok = await bcrypt.compare(password, hash);        // at login</pre><p>This hashes at signup and checks the password at login. bcrypt stores the salt inside the hash.</p><p><strong>Multi-factor authentication (MFA):</strong> combines factors, such as a password plus a one-time code from an authenticator app, an SMS code, or a hardware key. Hardware keys resist phishing better than codes you type.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HWFhHCe8BsjjXc5TRq-kwA.png" /></figure><p><strong>Passwordless options (brief):</strong> magic links sent by email, <strong>passkeys</strong> (based on public-key cryptography), and device biometrics that unlock them.</p><p><strong>Common attacks:</strong></p><ul><li><strong>Brute force:</strong> guessing many passwords</li><li><strong>Credential stuffing:</strong> trying leaked username and password pairs from other sites, which works because people reuse passwords</li><li><strong>Phishing:</strong> tricking users into entering credentials on a fake site</li></ul><p><strong>Tradeoff:</strong> Stronger authentication adds friction. Rate limiting, MFA, and breached-password checks balance security against convenience.</p><h3>4. Staying Logged In: Sessions, Cookies, and Tokens</h3><p><strong>Problem:</strong> HTTP is stateless, so identity must be carried on every request.</p><p><strong>Session-based:</strong> after login, the server stores session data and gives the browser a <strong>session ID</strong> in a cookie. Each request carries the cookie, and the server looks it up.</p><p><strong>Token-based:</strong> the server issues a signed token, such as a <strong>JWT</strong>, with short-lived <strong>access tokens</strong> and longer-lived <strong>refresh tokens</strong>. The client sends it in the Authorization header.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hZopQEFQRg7hYOL4XXkWQQ.png" /></figure><p><strong>Where credentials travel:</strong> cookies are sent automatically by the browser, which helps convenience and raises CSRF concerns. The Authorization header is set by your code, so it is not sent automatically.</p><p><strong>Logout, expiry, revocation:</strong> sessions are easy to cancel by deleting the server record. Tokens stay valid until they expire unless you add a blocklist, which is why access tokens are kept short.</p><p><strong>Security basics:</strong> use <strong>HTTPS</strong>, and set cookies with <strong>HttpOnly</strong>, <strong>Secure</strong>, and <strong>SameSite</strong>. Remember that a JWT is signed, not encrypted, so do not put secrets in it.</p><p><strong>Tradeoff:</strong> Sessions give control but need storage; tokens scale easily but are harder to revoke.</p><h3>5. Third-Party and Delegated Authentication: OAuth 2.0, OIDC, SSO</h3><p><strong>Problem:</strong> Sharing your password with another app is dangerous: it gets full access, with no limits and no easy way to take it back.</p><p><strong>OAuth 2.0:</strong> a framework for <strong>delegated access</strong>. A user lets an app access limited resources on their behalf, without sharing the password. Key roles: the <strong>resource owner</strong> (user), the <strong>client</strong> (the app), the <strong>authorization server</strong>, and the <strong>resource server</strong>. <strong>Scopes</strong> limit what the app may do.</p><p><strong>Authorization code flow (conceptual):</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yFXVq3F9tsH9mviw8WIhow.png" /></figure><p>For single-page and mobile apps, the code flow is used with <strong>PKCE</strong>, which protects the code from interception.</p><p><strong>OpenID Connect (OIDC):</strong> a layer on top of OAuth that adds <strong>identity</strong>. It provides an <strong>ID token</strong> that says who the user is, plus standard user info.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WyIyfwnfflss988SJUVqGQ.png" /></figure><p><strong>“Login with Google” walkthrough:</strong> your app redirects to Google, the user signs in and consents, Google returns a code, and your backend exchanges it for tokens. The ID token tells you who logged in.</p><p><strong>SSO:</strong> single sign-on lets one login work across several apps, using a central <strong>identity provider</strong>.</p><p><strong>Key rule:</strong> OAuth is for authorization, and OIDC is for authentication. Using plain OAuth as a login system is a classic mistake.</p><p><strong>Tradeoff:</strong> Delegation avoids password sharing, but adds redirects, configuration, and a dependency on the provider.</p><h3>6. How Authorization Works: Deciding What You Can Do</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HbrGAPHOb00jyhpO1pYGlg.png" /></figure><p><strong>Concepts:</strong> <strong>permissions</strong> are actions (edit product), <strong>roles</strong> group permissions (seller), and <strong>resources</strong> are the things protected (an order).</p><p><strong>Where checks happen:</strong> at the API gateway, in middleware, in the service, or in the database. The most important rule is that the <strong>server enforces</strong> them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*apkI21NguRRdS2IgbVSiKA.png" /></figure><p><strong>Least privilege:</strong> give each user and service only the access it needs.</p><p><strong>Frontend hiding is not security.</strong> Hiding the “Delete” button only changes what users see. Anyone can send the request directly, so the backend must check.</p><p><strong>Ownership checks:</strong> a user may edit only their own data.</p><pre>function requireRole(role) {<br>  return (req, res, next) =&gt;<br>    req.user &amp;&amp; req.user.role === role ? next() : res.status(403).json({ error: &quot;Forbidden&quot; });<br>}<br><br>app.delete(&quot;/products/:id&quot;, requireAuth, requireRole(&quot;admin&quot;), deleteProduct);</pre><p>This allows only admins to delete products, and rejects everyone else with 403.</p><p><strong>Common failures:</strong> <strong>IDOR</strong> (insecure direct object reference) happens when a user changes an ID in a request (/orders/101 to /orders/102) and the server returns another person&#39;s order because it never checked ownership. This is part of <strong>broken access control</strong>, which has ranked at the top of recent OWASP Top 10 lists [verify current edition].</p><p><strong>Tradeoff:</strong> Fine-grained checks are safer and take more design work.</p><h3>7. Authorization Models: RBAC, ABAC, ACL and Beyond</h3><ul><li><strong>ACL (Access Control List):</strong> each resource lists who can do what. Simple and direct, but hard to manage at scale.</li><li><strong>RBAC (Role-Based Access Control):</strong> users get roles, and roles get permissions. Easy to understand, though roles can multiply.</li><li><strong>ABAC (Attribute-Based Access Control):</strong> decisions use attributes of the user, resource, and context (department, time, ownership). Flexible, but more complex to write and test.</li><li><strong>Relationship-based access (brief):</strong> permissions follow relationships, such as “owner” or “shared with,” as in document sharing with view and edit rights.</li><li><strong>Policy engines and permission-as-code (brief):</strong> tools such as Open Policy Agent keep rules in one place, separate from app code.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JvqW5DiBflCdpMji3fkgdw.png" /></figure><p><strong>Strengths and weaknesses:</strong> ACLs are simple but scattered. RBAC is readable but coarse. ABAC is precise but complex. Relationship-based models fit sharing, and policy engines help centralize.</p><h3>8. Authentication and Authorization Across Architectures</h3><ul><li><strong>Monolith:</strong> server-side sessions fit well.</li><li><strong>SPA plus REST API:</strong> tokens, with an auth middleware on the API.</li><li><strong>Microservices:</strong> an <strong>API gateway</strong> authenticates at the edge, <strong>token propagation</strong> passes identity to services, and services also authorize their own data. <strong>Service-to-service</strong> auth uses credentials for the services themselves.</li><li><strong>Mobile apps and API keys:</strong> an API key identifies an app, not a person, so do not treat it as user authentication, and do not ship secrets inside frontend or mobile code.</li><li><strong>Machine-to-machine (brief):</strong> the OAuth <strong>client credentials</strong> flow, or <strong>mTLS</strong>, where both sides present certificates.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8H4PPVqHtfmyPnCj5YAlLA.png" /></figure><p><strong>MERN example:</strong> signup stores a bcrypt hash in MongoDB, login issues a token or session, a requireAuth middleware protects routes, and a requireRole middleware limits admin routes.</p><pre>function requireAuth(req, res, next) {<br>  const token = (req.headers.authorization || &quot;&quot;).split(&quot; &quot;)[1];<br>  try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); }<br>  catch { res.status(401).json({ error: &quot;Unauthenticated&quot; }); }<br>}</pre><p>This verifies the token and returns 401 if it is invalid.</p><h3>9. Comparing Approaches, Tradeoffs, and Common Pitfalls</h3><p><strong>Sessions vs JWT vs OAuth/OIDC:</strong></p><ul><li>Sessions: server-held state, easy revocation, need shared storage when scaling</li><li>JWT: signed token, stateless verification, harder revocation</li><li>OAuth/OIDC: delegated access and third-party login, more moving parts</li></ul><p><strong>RBAC vs ABAC:</strong> RBAC is simpler and works for most apps; ABAC is more flexible for complex rules.</p><p><strong>Build your own vs use a provider:</strong> providers such as Auth0, Firebase Auth, Clerk, and Keycloak (self-hosted), or libraries like NextAuth, handle hard parts such as password storage, MFA, and social login. Using a trusted provider is often safer than building from scratch, though it adds cost, lock-in, and configuration. Check each provider’s current features before choosing [verify].</p><p><strong>Usability vs security:</strong> more steps stop more attacks and annoy more users.</p><p><strong>Most common mistakes:</strong></p><ul><li>Weak password storage</li><li>Missing server-side checks</li><li>Long-lived tokens</li><li>Secrets in frontend code</li><li>Treating OAuth as login, or JWT as encryption</li><li>Assuming HTTPS replaces authorization checks</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5FFTzPO_KYzDULr0OavnBA.png" /></figure><p><strong>OWASP-style checklist:</strong> hash passwords, enforce server-side access control, use MFA, limit login attempts, set secure cookie flags, keep tokens short-lived, log and monitor.</p><h3>10. Choosing the Right Approach</h3><p>Consider:</p><ul><li><strong>Project size:</strong> small projects can use a provider or sessions; enterprises need audit trails and SSO</li><li><strong>Clients:</strong> web only favors sessions; web plus mobile favors tokens</li><li><strong>Features:</strong> social login, SSO, or MFA push you toward OAuth/OIDC or a provider</li><li><strong>Compliance and audit needs:</strong> may require specific controls and logs</li><li><strong>Team experience and maintenance cost</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DdiTL7hdYD1Hf6RL9tENXA.png" /></figure><p><strong>Practical checklist:</strong> pick the simplest approach that fits, use a trusted library or provider, and enforce permissions on the server. <strong>Next steps:</strong> an OAuth deep dive, the OWASP Top 10, and zero trust.</p><h3>Try It Yourself</h3><p>Open DevTools. In the <strong>Application</strong> tab, inspect Cookies and Local Storage for a site you use, and check the HttpOnly and Secure flags. In the <strong>Network</strong> tab, log in and find the Set-Cookie header or the token. Then request a page without being logged in, and look for a <strong>401</strong>. Try a page you are logged in for but not allowed to use, and look for a <strong>403</strong>.</p><h3>Security Checklist</h3><ul><li>Hash passwords with bcrypt or Argon2</li><li>Offer or require MFA</li><li>Rate limit login attempts</li><li>Use HTTPS, with HttpOnly, Secure, and SameSite cookies</li><li>Keep access tokens short-lived</li><li>Check permissions and ownership on the server</li><li>Never put secrets in frontend code</li></ul><h3>Interview Q&amp;A</h3><p><strong>Q: Authentication vs authorization?</strong></p><p>A: Authentication proves who you are; authorization decides what you may do. Authentication comes first.</p><p><strong>Q: 401 vs 403?</strong></p><p>A: 401 means not authenticated; 403 means authenticated but not allowed.</p><p><strong>Q: Is OAuth an authentication protocol?</strong></p><p>A: No. It delegates authorization. OpenID Connect adds authentication on top.</p><p><strong>Q: Why is hiding a button not security?</strong></p><p>A: Anyone can send the request directly, so the server must enforce access.</p><p><strong>Q: What is IDOR?</strong></p><p>A: Accessing another user’s resource by changing an ID, because ownership was not checked.</p><h3>FAQ</h3><p><strong>Is JWT encrypted?</strong> <br>&gt; Not by default. A signed JWT can be read by anyone who has it.</p><p><strong>Does HTTPS handle authorization?</strong> <br>&gt; No. HTTPS protects data in transit, and you still need server-side access checks.</p><p><strong>Should I build my own login system?</strong> <br>&gt; Often a trusted provider or library is safer, since password storage and MFA are easy to get wrong.</p><p><strong>What is the difference between RBAC and ABAC?</strong> <br>&gt; RBAC grants access by role; ABAC decides using attributes of the user, resource, and context.</p><p><strong>What is passwordless login?</strong> <br>&gt; Logging in without a typed password, for example with a magic link or a passkey.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6438c57302fe" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/how-authentication-and-authorization-work-6438c57302fe">How Authentication and Authorization Work</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Land a Software Engineering Job in the Age of AI]]></title>
            <link>https://towardsdev.com/how-to-land-a-software-engineering-job-in-the-age-of-ai-f9b37777c356?source=rss----a648dc4ecb66---4</link>
            <guid isPermaLink="false">https://medium.com/p/f9b37777c356</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[interview]]></category>
            <category><![CDATA[jobs]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[careers]]></category>
            <dc:creator><![CDATA[Phong Lam]]></dc:creator>
            <pubDate>Sat, 07 Nov 2026 20:11:01 GMT</pubDate>
            <atom:updated>2026-11-07T20:11:01.452Z</atom:updated>
            <content:encoded><![CDATA[<p>Earlier this month I gave a talk called “How to Land a Software Engineering Job in the Age of AI” to <a href="https://www.facebook.com/groups/findyournextopportunity">Albert’s job group</a>, a Facebook group of people looking for their next role. The slides grew out of a handful of conversations I had this summer with junior and mid-level engineers, and the thing I kept hearing was not that they were unprepared. They had put in the reps, they had the LeetCode streaks to prove it, and they were still walking out of interviews unsure of why it went badly. They did not know what “prepared” meant anymore.</p><p>The old playbook was easy to follow. Grind LeetCode until the patterns stick, memorize Big O, be fast and correct, apply widely and let volume do the rest. Granted, a lot of that still matters, algorithms have not gone anywhere. But what gets tested now looks different: our reasoning explained out loud, tradeoffs and system design earlier in the process, our judgment when working with AI, and proof that we have shipped something real.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*AjEAvLrBNvSEtlUd.png" /></figure><p><em>A slide from the talk.</em></p><p>I wrote a shorter version of this back in July, <a href="https://medium.com/@phongtlam/5-pieces-of-advice-for-interview-prep-in-the-age-of-ai-e9053c015057">5 Pieces of Advice for Interview Prep in the Age of AI</a>. The talk went further, so this article covers what the market data actually says, why we need to ask every company about its AI policy, how preparation should differ by career level, and a four-week plan to put it into practice.</p><h3>1. The jobs are coming back, but mostly at the senior end</h3><p>The good news first. <a href="https://fred.stlouisfed.org/series/IHLIDXUSTPSOFTDEVE">Indeed’s index of US software development postings</a> bottomed out in May 2025 and has climbed back to about 81 as of late October 2026 (February 2020 is the baseline, set to 100), up about 24% from a year earlier, while all US postings rose 2.8% over the same stretch. The bad news is that the index is still roughly 19% below where it was in February 2020, even though job postings overall are a few percent above that line.</p><p>And the recovery is not spread evenly. Between May 2025 and May 2026, <a href="https://hiringlab.indeed.com/2026/07/08/ai-and-job-postings-from-destruction-to-creation/">Indeed found</a> that 71% of the growth in software postings went to senior roles, and 37% of it went to roles that mention AI in the title (those two overlap, a senior role can have AI in its title too).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*d8E_JcM1R0MV5AF8.png" /></figure><p><em>A slide from the talk.</em></p><p>Early-career developers took most of the hit. ADP Research, using payroll data on millions of workers, found employment for the youngest software developers was still 20% below its late-2022 peak as of July 2025. Over the same stretch, employment for workers 30 and older in high-AI-exposure jobs (not just developers) grew somewhere between 6% and 13%. Researchers at the <a href="https://digitaleconomy.stanford.edu/publication/canaries-in-the-coal-mine-six-facts-about-the-recent-employment-effects-of-artificial-intelligence/">Stanford Digital Economy Lab</a> found something similar through June 2026, workers aged 22 to 25 in AI-exposed jobs, software development among them, saw employment fall 19% relative to peers in the least-exposed jobs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*q1SBPFcO1iFhj_ct.png" /></figure><p><em>A slide from the talk.</em></p><p>Note: the authors of both studies describe these as descriptive numbers, not proof that AI caused the drop. I do not want to argue either way on cause. What matters for us is the shape of the market we are interviewing in.</p><p>What this means, is that the market currently trusts experience with ambiguity, so our job is to show judgment early, not wait years to earn it.</p><h3>2. Ask every company about its AI policy before the interview</h3><p>Companies have split into two camps on AI in interviews, and they are pulling in opposite directions.</p><p>Some allow it or expect it. <a href="https://canva.dev/blog/engineering/yes-you-can-use-ai-in-our-interviews">Canva expects candidates</a> to use tools like Copilot, Cursor or Claude in technical rounds, Meta has let some candidates use AI in coding interviews, and Google has been piloting Gemini in some junior and mid-level rounds this year.</p><p>Others went the other way. Amazon’s guidance bans AI during interviews unless it is explicitly permitted, and Google, Cisco and McKinsey have brought in-person rounds back. A big part of why is that real-time interview-assist tools made a clean solution on a shared screen prove a lot less than it used to, and in a Gartner survey from Q2 2025, 6% of candidates admitted to interview fraud.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*nX73Dys-DMSi3Nlb.png" /></figure><p><em>A slide from the talk.</em></p><p>Google shows up on both sides of that list, which is the whole point. Policies vary by company, by team, and even by round, so we cannot assume. Before every interview loop (the full set of rounds for one role), we should ask the recruiter directly:</p><blockquote><em>“What’s your policy on AI tools during the interview?”</em></blockquote><p>Guessing wrong can end an interview before it really starts.</p><h3>3. Five ways to prepare now</h3><p>The July article goes deeper on most of these, so here is the version from the talk, with the new pieces I added.</p><p><strong>Practice LeetCode out loud.</strong> LeetCode gets us in the room, explaining our reasoning gets us the job. Many candidates can reach a working solution with AI help, and interviewers know it, so the bar moved to whether we can explain while we solve. In the talk I broke a coding round into three steps: clarify (restate the problem, ask about inputs and edge cases before writing code), narrate (say the brute force, then why we are moving to a better approach), and verify (walk a test case by hand, name the complexity, call out what we would harden). We should still do the reps. Just do them out loud, record ourselves, or pair with a friend who grades the explanation instead of the answer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8en7k6RnXFy_-BDK.png" /></figure><p><em>A slide from the talk.</em></p><p><strong>Study system design tradeoffs.</strong> Code is becoming a commodity, tradeoffs are not. The example I used on stage was a product page where every request needs the current price and stock level. We can read straight from the database, always fresh, one source of truth, simple to debug, but slower under peak traffic with the database taking the whole read load. Or we can cache it with a TTL (a time-to-live, after which the cached value expires and gets refetched), fast responses and a database shielded during spikes, but stale prices until the entry expires and invalidation bugs, the cache not getting cleared when the price changes, that are hard to trace. AI will write either one in seconds. Choosing the one that fits the failure modes we care about, and defending it, is our job.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sm1ja7BoStRV7Oph.png" /></figure><p><em>A slide from the talk.</em></p><p><strong>Show how we work with AI.</strong> The <a href="https://survey.stackoverflow.co/2026/ai">Stack Overflow Developer Survey 2026</a> found about 83% of developers use AI tools, and 48% trust the output when they can easily verify it, but only 6.6% trust it for important decisions. The interview question is shifting from “do you use AI?” to “do you trust it appropriately?” We should not hide our AI use. We should show that we can direct it, review it, and catch it when it is confidently wrong. There are four places AI output fails often enough that we should check them by habit: edge cases (empty inputs, single elements, off-by-one boundaries), security assumptions (input validation, auth, anything user-supplied), stale APIs presented as current best practice, and logic bugs that compile, pass the obvious test, and are still wrong. In an AI-allowed round, we should say these checks out loud as we review the output.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UVkTBDrHAH-8yGdS.png" /></figure><p><em>A slide from the talk.</em></p><p><strong>Bring a real project.</strong> A small real project shows judgment that isolated problems cannot. It should let us talk about a decision we can defend, a tradeoff we accepted knowingly, and a mistake we caught, whether it came from an AI suggestion or our own first pass. Building it with an AI assistant is completely fine, as long as we can say what we changed by hand and what we rejected. And if we are not sure where to show it, we can always build our own personal site, I made the same suggestion back in 2022 and still stand by it. This site is mine, and building it gave me a project of my own to talk about too.</p><p><strong>Run a smarter search.</strong> This is the new one. AI-written applications are flooding recruiters. According to Greenhouse, 74% of US job seekers use AI in their search, a 2026 Criteria Corp / Lighthouse Research survey found 92% of recruiting leaders say AI-generated resumes are now commonplace, and Robert Half found 67% of US HR leaders say AI applications have slowed hiring down. Candidates use AI to apply to more jobs, recruiters use AI to filter more applications, and Greenhouse’s CEO has called it a doom loop, each side’s AI making the other side’s job harder. Sending out as many applications as possible worked better in 2022 than it does now. The things that cut through are the same ones I wrote about back then, referrals and a real network, tailoring to the roles we actually want, and following up on every application. A human who vouches for us beats a hundred more applications.</p><h3>4. Our prep should change with our career level</h3><p>The market looks very different depending on where we are, so the same five moves need different emphasis. In Q1 2026, only 4.5% of US software postings were entry-level, while 69.3% were senior, according to <a href="https://hiringlab.indeed.com/2026/07/23/the-labor-market-is-tilting-toward-seniority/">Indeed Hiring Lab</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6HZMqK5s1Y-DBIcT.png" /></figure><p><em>A slide from the talk.</em></p><p><strong>New grads: the door is narrower, so make our judgment visible.</strong> SignalFire’s State of Tech Talent 2026 report found new-grad hiring down about 65% at 12 big tech companies and about 76% at early-stage startups compared with 2019. The <a href="https://www.newyorkfed.org/research/college-labor-market">New York Fed</a> puts unemployment for all recent US graduates (not just CS) at 5.2% in Q3 2026, down from 5.6% the quarter before. What I suggested:</p><ul><li>Ship one real project end to end. Real users or real data beat a tutorial clone.</li><li>Keep a decision log, what we chose, what AI got wrong, and what we changed.</li><li>Keep fundamentals AI-free too, since Amazon, for one, bans AI in interviews unless it says otherwise.</li><li>Look past big tech, and use alumni networks and referrals to reach smaller and non-tech employers.</li></ul><p>A slow search at this level is not a verdict on us.</p><p><strong>Mid-career: move our story from what we built to what we decided.</strong> Instead of “here is what I built,” interviewers want to hear “here is what I decided, and why.” Take the <a href="https://www.elev8dancelv.com">dance studio app</a> I built for my wife’s studio. “I built a booking app with Next.js, Hono and Stripe” tells an interviewer what we used. “I chose to add class credits from Stripe’s webhook instead of when checkout returns, because payment methods like Cash App Pay can settle after the customer has already closed the tab” tells them how we think. That means leading with an outcome we owned (the problem, our decision, what it changed), making system design a strength by studying the tradeoffs in systems we already run at work, getting fast at reading and debugging code we did not write, and picking a clear edge, pairing our experience with a domain or with applied AI.</p><p>Note: there are no numbers in this part on purpose. I looked and did not find a reliable public data series on mid-level hiring, so I would rather show nothing than a guess.</p><p><strong>Senior and above: demand is strongest, but we still have to prove it hands-on.</strong> Being senior helps with demand, but we still have to prove it in the room. Senior engineers should stay hands-on with AI tools and rehearse live coding, not just design discussions, tell scope stories, examples of work that spanned more than one team or had no clear spec, that cover ambiguity, cross-team tradeoffs and failures we owned, show how we raise the team’s bar (review standards, and when we would not use AI), and give examples of engineers and decisions we made better.</p><h3>5. A four-week plan to put this into practice</h3><p>I ended the talk with something people could start that week:</p><ol><li><strong>Week 1:</strong> Record three LeetCode sessions narrating out loud, then watch the playback.</li><li><strong>Week 2:</strong> Write up two tradeoffs from a system we use at work.</li><li><strong>Week 3:</strong> Mock an AI-assisted round in code we did not write, and log what we rejected from the AI’s suggestions.</li><li><strong>Week 4:</strong> Send ten targeted applications, each with a referral ask or a follow-up.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*GaWc3nGnh3hoPiSh.png" /></figure><p><em>A slide from the talk.</em></p><p>And before every loop, ask the recruiter about the AI policy and the round format.</p><h3>Conclusion</h3><p>The jobs are coming back, slowly and mostly at the senior end, and the interview has changed shape along the way.</p><ol><li>Know the market we are in. Postings are recovering but still about a fifth below 2020, and early-career developers took most of the hit.</li><li>Ask about the AI policy before every loop, because companies are split and guessing wrong is costly.</li><li>Prepare for what is graded now: reasoning out loud, tradeoffs, reviewing AI output, a real project, and a search built on referrals and follow-ups.</li><li>Adjust for our level. New grads make judgment visible, mid-career engineers talk about decisions, senior engineers prove they are still hands-on.</li><li>Start small. Four weeks is enough to change how we show up.</li></ol><p>A lot of qualified people are applying for months with little to show for it, and that is not a reflection of their ability. We can control how we prepare and how we show up, we cannot control how many roles a company opens, how senior-skewed demand is, or when the market turns. Keep the rhythm, and give ourselves some grace.</p><p>None of us chose the market we are interviewing in. We can still choose how we prepare for it.</p><p>If you were at the talk, or weren’t and want to talk through your own prep, feel free to reach out :).</p><p><strong>Sources</strong></p><ul><li>Indeed Hiring Lab: <a href="https://hiringlab.indeed.com/2026/07/08/ai-and-job-postings-from-destruction-to-creation/">“AI and Job Postings: From Destruction to Creation?”</a> (Jul 2026), <a href="https://hiringlab.indeed.com/2026/07/23/the-labor-market-is-tilting-toward-seniority/">“The Labor Market Is Tilting Toward Seniority”</a> (Jul 2026), <a href="https://hiringlab.indeed.com/2026/10/22/us-labor-market-snapshot-october-2026/">US Labor Market Snapshot</a> (Oct 2026), and the <a href="https://fred.stlouisfed.org/series/IHLIDXUSTPSOFTDEVE">software development postings index on FRED</a></li><li>ADP Research, <a href="https://www.adpresearch.com/yes-ai-is-affecting-employment-heres-the-data">“Yes, AI is affecting employment. Here’s the data.”</a></li><li>Brynjolfsson, Chandar &amp; Chen, <a href="https://digitaleconomy.stanford.edu/publication/canaries-in-the-coal-mine-six-facts-about-the-recent-employment-effects-of-artificial-intelligence/">“Canaries in the Coal Mine?”</a>, Stanford Digital Economy Lab (rev. Aug 2026)</li><li>Canva Engineering, <a href="https://canva.dev/blog/engineering/yes-you-can-use-ai-in-our-interviews">“Yes, You Can Use AI in Our Interviews”</a> (Jun 2025)</li><li>Business Insider, <a href="https://www.businessinsider.nl/meta-will-let-some-job-candidates-use-ai-in-their-coding-interviews/">“Meta will let some job candidates use AI in their coding interviews”</a> (Jul 2025)</li><li>Entrepreneur, <a href="https://www.entrepreneur.com/business-news/google-is-testing-a-new-rule-transform-job-interviews">“Google Is Testing a New Rule That Could Transform Job Interviews”</a>, on Google’s Gemini interview pilot</li><li>Entrepreneur, <a href="https://www.entrepreneur.com/business-news/google-mckinsey-reintroduce-in-person-interviews-due-to-ai/496041">“Major Companies Including Google and McKinsey Are Bringing Back In-Person Job Interviews”</a> and the <a href="https://oecd.ai/en/incidents/2025-08-12-0f22">OECD.AI incident monitor</a> (2025)</li><li>Gartner candidate survey (Q2 2025), via <a href="https://www.hrdive.com/news/fake-job-candidates-ai/757126/">HR Dive</a></li><li><a href="https://survey.stackoverflow.co/2026/ai">Stack Overflow Developer Survey 2026</a></li><li><a href="https://www.greenhouse.com/newsroom/an-ai-trust-crisis-70-of-hiring-managers-trust-ai-to-make-faster-and-better-hiring-decisions-only-8-of-job-seekers-call-it-fair">Greenhouse</a> (Nov 2025)</li><li>Criteria Corp and Lighthouse Research, <a href="https://www.criteriacorp.com/research/2026-lighthouse-research-report">2026 Talent Acquisition Trends Study</a></li><li>Robert Half, <a href="https://press.roberthalf.com/2026-03-10-Robert-Half-survey-67-of-HR-leaders-report-AI-generated-applications-are-slowing-hiring">“67% of HR leaders report AI-generated applications are slowing hiring”</a> (Mar 2026)</li><li>SignalFire, <a href="https://signalfire.com/blog/signalfire-state-of-talent-report-2026">State of Tech Talent 2026</a></li><li>Federal Reserve Bank of New York, <a href="https://www.newyorkfed.org/research/college-labor-market">The Labor Market for Recent College Graduates</a></li></ul><p><em>Originally published at </em><a href="https://ptlam.io/writing/land-a-software-engineering-job-in-the-age-of-ai"><em>https://ptlam.io</em></a><em> on November 4, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f9b37777c356" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/how-to-land-a-software-engineering-job-in-the-age-of-ai-f9b37777c356">How to Land a Software Engineering Job in the Age of AI</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>