<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 2am.tech</title>
    <description>The latest articles on DEV Community by 2am.tech (2amtech).</description>
    <link>https://dev.to/2amtech</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F13756%2F40b6ef3d-c49e-4e9e-82ae-257a26c21412.png</url>
      <title>DEV Community: 2am.tech</title>
      <link>https://dev.to/2amtech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/2amtech"/>
    <language>en</language>
    <item>
      <title>Meet Polaris: A Complete PHP Identity Stack in One Line of Config (Part 1)</title>
      <dc:creator>Antonio Ramirez Cobos</dc:creator>
      <pubDate>Wed, 05 Aug 2026 14:25:37 +0000</pubDate>
      <link>https://dev.to/2amtech/meet-polaris-a-complete-php-identity-stack-in-one-line-of-config-part-1-lkc</link>
      <guid>https://dev.to/2amtech/meet-polaris-a-complete-php-identity-stack-in-one-line-of-config-part-1-lkc</guid>
      <description>&lt;p&gt;&lt;em&gt;This blog post series picks up where I left off in &lt;a href="https://dev.to/2amtech/univeros-how-ai-agents-revived-my-seven-year-old-php-framework-project-47c9"&gt;how AI agents revived my old PHP framework project&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In my previous post, I wrote about how a swarm of AI agents helped me revive a PHP framework I had abandoned years ago. That post was the origin story. This little series is the first real proof that the revival was worth it.&lt;/p&gt;

&lt;p&gt;Because a framework on its own is just plumbing. What I really wanted to know was simpler and scarier: could I build a serious, production-grade feature on top of it without hating my life? Not another to-do list demo. The thing every app needs, nobody enjoys writing, and that ruins your week when you get it wrong.&lt;/p&gt;

&lt;p&gt;Authentication.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://polaris.univeros.io/" rel="noopener noreferrer"&gt;Polaris&lt;/a&gt;. It is the official identity module for &lt;a href="https://univeros.io/" rel="noopener noreferrer"&gt;Univeros&lt;/a&gt;, and this post is the tour. The four how-tos that follow get hands-on with each piece. Here I just want to show you the shape of the thing and why it is built the way it is.&lt;/p&gt;

&lt;p&gt;I named it Polaris on purpose. Polaris is the fixed star sailors navigated by, and identity plays the same role in an application. Everything else eventually comes back to "Who is this, and what are they allowed to do?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The Thirty-Second Version
&lt;/h2&gt;

&lt;p&gt;Install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require univeros/polaris

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it. This is the "one line" I keep bragging about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/modules.php&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Univeros\Polaris\Module&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it the secrets it needs (from your environment or a secret manager, never from a file in the repo):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;APP_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;                                &lt;span class="c"&gt;# 32-byte base64, seeds the peppers + encrypter&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH_JWT_PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;private.pem&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  &lt;span class="c"&gt;# signs access tokens&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH_JWT_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;public.pem&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;    &lt;span class="c"&gt;# verification + the JWKS endpoint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the migrations and confirm it is alive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/altair db:migrate
bin/altair routes:list &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json | &lt;span class="nb"&gt;grep &lt;/span&gt;auth
bin/altair doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. That single &lt;code&gt;new Module()&lt;/code&gt; contributes every &lt;code&gt;/auth&lt;/code&gt;, &lt;code&gt;/users&lt;/code&gt;, and &lt;code&gt;/orgs&lt;/code&gt; route, the Cycle ORM entities, the migrations, the authentication and authorization middleware, and the container bindings. Fifty-two endpoints, wired and ready, with zero per-module bootstrapping on your side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Module, and Why “Drop-In” Is More than a Buzzword
&lt;/h2&gt;

&lt;p&gt;Here is something I learned the hard way over the years. Most auth code doesn’t die because the crypto is wrong, but because it is welded to the app that hosts it. You write a beautiful login flow for project A, then project B comes along and you spend two days surgically extracting it. By the time you’re done you’ve introduced three new bugs.&lt;/p&gt;

&lt;p&gt;Univeros has a module contract system, and Polaris leans on it completely. A module declares what it provides (routes, entities, migrations, middleware, container bindings) and the host wires it all in automatically. There is no "now go register these fifteen services" step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Module&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt;
    &lt;span class="nc"&gt;ModuleInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;RoutesProviderInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;MiddlewareProviderInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;EntityDirectoriesProviderInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;MigrationDirectoriesProviderInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'univeros/polaris'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Container&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$authConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AuthConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;authConfigArray&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nv"&gt;$secrets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Secrets&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AuthConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$authConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Secrets&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$secrets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IdentityBindings&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TokenBindings&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$authConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$secrets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SessionBindings&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpBindings&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MfaBindings&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$authConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$secrets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrganizationBindings&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;apply()&lt;/code&gt; builds the config and secrets eagerly. That is deliberate. Forget to set &lt;code&gt;AUTH_JWT_PRIVATE_KEY&lt;/code&gt;, and the app does not boot with a quietly insecure fallback and bite you in production three weeks later. It fails immediately, with a clear message. Fail loud. Fail early.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shape of Every Endpoint
&lt;/h2&gt;

&lt;p&gt;Before diving into the how-tos, there’s one pattern worth knowing. Once you see it, the rest of the module reads the same way. Univeros uses an Action, Input, Domain, Responder shape (it is basically Action-Domain-Responder with a typed input DTO in front). Every endpoint is the same quad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP edge   Action            thin route target: declares input, responder, domain, required permissions
            Input (readonly)  a typed request DTO with validation rules()
            Responder         turns a Payload into JSON or an RFC 9457 Problem Details body
Domain      *Service          the business logic: transactional, emits PSR-14 events
            Contracts/*       ports: SmsSender, OtpMailer, PasswordHasher, Clock, and friends
Persistence Entity/* (Cycle)  UUID-v7 entities mapped into the host ORM schema
Security    token machinery   implements the framework's Altair\Http auth contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The domains stay thin. They validate the edge, call a service, and translate outcomes into HTTP. All the real work (hashing, lockout, token minting, event dispatch) lives in the services. That separation keeps the codebase from rotting, and it is why each how-to in this series can show you a real, unedited domain class that still fits on a single screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Actually Get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt; Register, email verification, password login, /auth/me, logout / logout-all (Part 2, coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tokens:&lt;/strong&gt; Asymmetric JWT access tokens (RS256/EdDSA) plus opaque rotating refresh tokens with reuse detection; a JWKS endpoint (Part 2, coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sessions:&lt;/strong&gt; Per-device session list, individual and global revocation (Part 2, coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MFA / OTP:&lt;/strong&gt; TOTP (QR), SMS OTP, email OTP, recovery codes, the login-MFA gate, step-up (Part 3, coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Passwords:&lt;/strong&gt; Argon2id, policy enforcement, breached-password hook, reset and change (Part 2, coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-tenant RBAC:&lt;/strong&gt; Organizations, memberships, roles, permissions, invitations, org switching (Part 4, coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authorization:&lt;/strong&gt; Declarative permission guard middleware plus a programmatic Gate (Part 4, coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Providers &amp;amp; events:&lt;/strong&gt; Pluggable SMS/email/breach ports, PSR-14 domain events (Part 5, coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security Posture, Stated Plainly
&lt;/h2&gt;

&lt;p&gt;I am not going to wave my hands and say "secure by design." Here is what that phrase actually cashes out to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Passwords hashed with Argon2id, transparently rehashed when parameters change, with timing-equalized verification.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access tokens signed with asymmetric keys (RS256 or EdDSA) with kid-based rotation. Resource servers verify with the public key alone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refresh tokens, OTP codes, recovery codes, verification tokens, and reset tokens are never stored in plaintext. They are hashed or kept as keyed HMACs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rotating refresh tokens with family-based reuse detection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rate limiting on every sensitive endpoint, sliding-window account lockout, and anti-enumeration on register, resend, and forgot-password.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An audit log fed by the event stream.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The standards it follows are the boring, correct ones: JWT (RFC 7519), JWKS (RFC 7517), TOTP (RFC 6238), OAuth 2.0 refresh semantics and the Security BCP (RFC 9700), Problem Details (RFC 9457), and OWASP ASVS for password storage. The full threat model lives in the &lt;a href="https://polaris.univeros.io/docs/" rel="noopener noreferrer"&gt;reference docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, Did the Framework Hold Up?
&lt;/h2&gt;

&lt;p&gt;That was the whole point of this exercise. Part one of the Univeros series asked whether AI agents could resurrect a dead PHP framework. This series is the honest test: could I build something genuinely hard on top of it?&lt;/p&gt;

&lt;p&gt;I think the answer is yes, and the evidence is in the shape of the code. Polaris is fifty-two endpoints, full MFA, multi-tenant RBAC, rotating tokens with theft detection, and an audit trail, and yet every endpoint is the same readable quad. The framework's module system meant the whole thing installs in one line.&lt;/p&gt;

&lt;p&gt;The agents helped a lot with the grind: the RFC 6238 test vectors, functional tests for fifty-two endpoints, keeping the docs in sync with code. But the design decisions (authority comes from the database, not the token; last-owner protection; fail-open breach checks) came from years of getting auth wrong and remembering the scars. The agents are fast hands. The judgment is still mine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to Go Next
&lt;/h3&gt;

&lt;p&gt;Pick the piece you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Part 2:&lt;/strong&gt; Logins that do not leak covers register, email verification, password login, JWT access tokens, and rotating refresh tokens with reuse detection. (coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Part 3:&lt;/strong&gt; Real MFA in an afternoon covers TOTP/QR, SMS, email, recovery codes, the login gate, and step-up. (coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Part 4:&lt;/strong&gt; One user, many orgs covers organizations, roles, permissions, the Gate, and the tenant invariants. (coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Part 5:&lt;/strong&gt; Bring your own providers and events covers the SMS/email/breach ports and the PSR-14 event stream. (coming soon)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reference docs live at polaris.univeros.io. The framework is at univeros.io. The source is on &lt;a href="https://github.com/univeros/polaris" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require univeros/polaris
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. A whole identity stack. The fixed star your app navigates by. See you in part 2.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>identitymanagement</category>
      <category>php</category>
      <category>api</category>
    </item>
    <item>
      <title>HAI: How to Get Your Team to Work Best with AI</title>
      <dc:creator>Aleksandar Panic</dc:creator>
      <pubDate>Wed, 05 Aug 2026 14:17:05 +0000</pubDate>
      <link>https://dev.to/2amtech/hai-how-to-get-your-team-to-work-best-with-ai-1231</link>
      <guid>https://dev.to/2amtech/hai-how-to-get-your-team-to-work-best-with-ai-1231</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3crcfvbdxtagl5v753pt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3crcfvbdxtagl5v753pt.png" alt="HAI" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your team is already using AI to write code. The problem is that everyone is doing it differently. One developer is in Claude Code, another in Copilot, someone on the new team swears by Cursor, and there is always one person on Codex. Each of them has set up their assistant by hand, with their own prompts, their own half-remembered configuration, and no shared idea of how the AI should approach a ticket.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the result is inconsistent, the knowledge lives in people's heads, and none of the assistants can actually see the Jira ticket or the Confluence spec the work is based on. You end up pasting context into a chat window, over and over, for every assistant on the team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hai.2am.tech" rel="noopener noreferrer"&gt;Helper AI (HAI)&lt;/a&gt; is a free, open-source npm CLI that gives your whole team one shared AI workflow, whatever editor each member happens to use. It sets up Claude Code, Copilot, Cursor, and Codex the same way, with the same subagents and the same slash-commands, then pulls your Jira tickets and Confluence specs into plain Markdown the AI can actually read.&lt;/p&gt;

&lt;p&gt;Everyone keeps their editor. Everyone gets the same setup. And every assistant on the team can finally reach the tickets and specs the work depends on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Same Setup, Wherever Your Developers Work
&lt;/h2&gt;

&lt;p&gt;Four developers on four different editors should not mean four different AI workflows, four different sets of prompts, and four different results on the same kind of task.&lt;/p&gt;

&lt;p&gt;HAI installs into each environment where that environment expects to find things. Claude Code gets its config in &lt;code&gt;.mcp.json&lt;/code&gt;, Copilot in &lt;code&gt;.vscode/mcp.json&lt;/code&gt;, Cursor in &lt;code&gt;.cursor/mcp.json&lt;/code&gt;, and Codex in &lt;code&gt;.codex/config.toml&lt;/code&gt;. There is an "Any editor" mode too, which lays everything out under .ai/ for manual wiring. Whichever one a developer runs, they get the same MCP server, the same seven subagents, and the same six commands. The team matches without anyone agreeing on an editor first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Tickets and Specs, as Readable Markdown
&lt;/h2&gt;

&lt;p&gt;A lot of the context an AI needs to do a task well does not live in the codebase. The reasoning is in a ticket. The acceptance criteria are in a spec. The screenshot showing the bug is an authenticated attachment nobody can fetch without credentials.&lt;/p&gt;

&lt;p&gt;HAI closes that gap. Jira issues and Confluence pages land in a local .ai/ directory as plain Markdown, with comments, links, custom fields, and the page tree kept intact. Because it is Markdown on disk, your assistant reads it the same way it reads your source. It is all git-ignored by default, with no database and nothing proprietary, so switching ticket providers later does not change how anyone works. &lt;/p&gt;

&lt;p&gt;Since files are pulled directly from the developer's environment, AI doesn't need to use long and costly searches using standard MCP plugins, everything it needs is already there.&lt;/p&gt;

&lt;p&gt;Attachments get handled too. Since they sit behind auth, an AI cannot fetch them directly, so the &lt;code&gt;image_download&lt;/code&gt; tool pulls them with the right credentials and caches them locally. Your assistant just reads the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hand It a Ticket, Get Code Back
&lt;/h2&gt;

&lt;p&gt;Setup is a one-time thing. After hai init wires up an editor and runs the first sync, the day-to-day is a single command: hand your AI a Jira key with /implement JIRA-123 and it runs the whole job.&lt;/p&gt;

&lt;p&gt;It pulls the ticket over MCP, grabbing every issue that ticket links to in a single pass so the AI has the full picture. It researches the specs and the surrounding code. It drafts a plan. Then, once you approve, it implements the work, running the domain agents in parallel and checking each one.&lt;/p&gt;

&lt;p&gt;That last part leans on the seven subagents HAI installs, each with a clear job. A researcher and an architect that read and plan but never touch code. Backend, frontend, and refactoring agents that write it. A security reviewer and a verifier that checks it. The implement command splits work across them by domain and runs them at the same time rather than one after another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free, Open Source, and Two Commands to Set Up
&lt;/h2&gt;

&lt;p&gt;HAI is released under Apache 2.0 and runs on Node 16 or newer. It is one npm package, and each teammate sets themselves up with two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @2amtech/hai
hai init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;hai init walks through picking an AI provider, a ticket source, and a spec source, then runs the initial sync for you, so the .ai/ folder is ready right away. There is no separate pull to run on day one. Reach for hai pull later, mainly when specs change and you want the latest copy locally.&lt;/p&gt;

&lt;p&gt;On the source side you can point it at Atlassian for Jira and Confluence, at a Local provider that reads tickets and specs from folders on disk, or at None when you only want the subagents and commands with no ticket system attached.&lt;/p&gt;

&lt;p&gt;Everything stays in the repository and out of git. The tickets, the specs, and the credentials are all local. Nothing about your project leaves for anywhere it was not already going.&lt;/p&gt;

&lt;p&gt;Check it out at: &lt;a href="https://hai.2am.tech" rel="noopener noreferrer"&gt;https://hai.2am.tech&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cli</category>
    </item>
    <item>
      <title>2am.tech Releases SQL Migration Tool to Streamline Database Changes</title>
      <dc:creator>Aleksandar Panic</dc:creator>
      <pubDate>Tue, 07 Jul 2026 14:44:27 +0000</pubDate>
      <link>https://dev.to/2amtech/2amtech-releases-sql-migration-tool-to-streamline-database-changes-1hab</link>
      <guid>https://dev.to/2amtech/2amtech-releases-sql-migration-tool-to-streamline-database-changes-1hab</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Database migrations have a reputation for going wrong at the worst possible moment. Omitted rollback scripts, schema changes applied to production but not staging, credentials hardcoded in a config file that someone committed by accident… These are the kind of incidents that haunt database administrators and backend engineers, and they are far more common than most teams would like to admit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The SQL Migration Tool (SMT)&lt;/strong&gt; is a free desktop app and an open-source CLI utility that empowers backend developers and engineering teams with consistency, traceability, and control over database schema management. &lt;/p&gt;

&lt;h1&gt;
  
  
  Plain SQL By Design
&lt;/h1&gt;

&lt;p&gt;Most migration tools require developers to describe schema changes through a framework-specific syntax or an ORM layer. SMT takes the opposite approach. Migrations are standard .sql files. What gets executed is exactly what was written. The files can be run directly in any database client, independently of the tool, and they live in version control alongside the rest of the codebase. &lt;/p&gt;

&lt;p&gt;Scripts are supported which are also SQL files with dynamic replacements which can be run to perform common database maintenance tasks.&lt;/p&gt;

&lt;p&gt;SQL files do not have any special annotations so they can still be used in existing Database Administrator's tool of choice.&lt;/p&gt;

&lt;h1&gt;
  
  
  What It Does
&lt;/h1&gt;

&lt;p&gt;SMT supports MySQL/MariaDB, PostgreSQL, and Microsoft SQL Server. Teams can manage multiple environments from a single project configuration, with each environment tracking its own database changes, so rollouts can be granular. Rollback scripts are plain SQL files as well, with nothing inferred and nothing hidden.&lt;/p&gt;

&lt;p&gt;No need for hardcoded credentials. SMT can read credentials from different sources for each environment to connect to the database. For teams working in AWS environments, SMT integrates with AWS Secrets Manager out of the box. Local development setups can use .env files instead. For local or sandbox testing where security is not needed, directly entered credentials can still be provided.&lt;/p&gt;

&lt;p&gt;Environment specific database differences like different table prefixes, columns and schemas are not a problem. With Dynamic Replacement support, rather than maintaining separate SQL files for each environment, teams can define environment specific search and replace placeholders so that different environments run a SQL targeting the proper tables at runtime. This means the same migration file can target a different schema name in a test environment versus production one, use different table prefixes per tenant in a multi-tenant setup, or insert different seed data depending on where it is being deployed. One file, cleanly adapted to wherever it runs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Friendly for both Database Administrators and DevOps
&lt;/h1&gt;

&lt;p&gt;SMT is available both as a desktop application and a CLI package. Both are released under the MIT license and are free for personal and commercial use.&lt;/p&gt;

&lt;p&gt;Desktop application allows Database Administrators to add and manage the migrations across many projects with a simple and intuitive user interface. Available for all major operating systems: macOS, Windows, and Linux. Migrations can also be run directly from the GUI app. &lt;/p&gt;

&lt;p&gt;For DevOps CI/CD workflows, the CLI can be used to automatically apply these migrations during a deployment. It installs via npm in a single command and can run with zero user interaction needed. It integrates nicely with any environment which can run Node, Bun or Deno runtime.&lt;/p&gt;

&lt;p&gt;The SQL Migration Tool is available at: &lt;a href="http://smt.2am.tech" rel="noopener noreferrer"&gt;http://smt.2am.tech&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>migration</category>
      <category>database</category>
    </item>
    <item>
      <title>Univeros: How AI Agents Revived My Seven-Year-Old PHP Framework Project</title>
      <dc:creator>Antonio Ramirez Cobos</dc:creator>
      <pubDate>Wed, 01 Jul 2026 10:31:00 +0000</pubDate>
      <link>https://dev.to/2amtech/univeros-how-ai-agents-revived-my-seven-year-old-php-framework-project-47c9</link>
      <guid>https://dev.to/2amtech/univeros-how-ai-agents-revived-my-seven-year-old-php-framework-project-47c9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;On reviving Univeros, and why layering AI on top of a framework is the wrong approach.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There’s a project folder I’ve avoided revisiting for almost a decade.&lt;/p&gt;

&lt;p&gt;About seven years ago, I started working on a PHP framework that quickly became more than just a side project. It included a dependency injection container I was oddly proud of, a middleware pipeline, typed collections, and several unfinished packages with names I eventually couldn’t justify anymore. I poured countless nights and weekends into it, driven by both passion and curiosity.&lt;/p&gt;

&lt;p&gt;But then life happened. Clients demanded my focus, I moved to new places, and the next opportunity came along. Slowly, the project fell silent. The folder where all my work lived grew quiet and untouched for years. It became a sort of a small, specific kind of guilt. A reminder of what I once started but never quite finished. &lt;/p&gt;

&lt;p&gt;It's now clear to me that the tedious, repetitive tasks (aka the “plumbing”) killed the joy and momentum of the project, not the challenging parts, which I actually enjoyed. The endless cycle of writing actions, DTOs, responders, routes, migrations, repositories, tests, and updating docs was necessary but extremely unexciting.&lt;/p&gt;

&lt;p&gt;But last year's eureka moment was powerful. The very busywork that made me abandon the project is exactly what AI agents excel at today. But there's a catch. The framework has to be built in a way that allows me to actually trust what the agent is doing.&lt;/p&gt;

&lt;p&gt;So, I decided to revive the project as &lt;a href="https://univeros.io/" rel="noopener noreferrer"&gt;Univeros&lt;/a&gt;, an agent-native PHP 8.3+ framework. This is the story behind its creation and why it exists now. I hope you’ll find it exciting as I do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Honest Reasons Behind My Decision
&lt;/h2&gt;

&lt;p&gt;The first one is a bit petty, but I’ll admit it: I wanted all the hours I spent to mean something. There’s a unique sting in seeing your work abandoned. You know the effort was genuine, even if no one ever saw the results. Reviving Univeros was my way of refusing to let seven years of ideas and effort gather dust in a forgotten directory.&lt;/p&gt;

&lt;p&gt;The second reason is the real driving force. It’s a deep disagreement.&lt;/p&gt;

&lt;p&gt;Just look at how most frameworks are adding AI today: a chatbot on the docs, a “copilot” plugin that autocompletes your code. This AI sits on top, and actually guesses by reading your source files. That is the core problem. An AI agent working in a conventional framework will waste most of its context just trying to figure out basics like “what classes exist here?” or “did this code generation actually work?” It’s slow, expensive, and worst of all, non-deterministic. Run the same prompt twice, and you get two slightly different scaffold results. That noise ruins your diffs and messes up continuous integration (CI), which can’t tell a real change from a coin flip.&lt;/p&gt;

&lt;p&gt;I strongly believe this approach is backwards. The framework shouldn’t be something the AI reads and guesses about. It should be something the AI operates directly, deterministically, reversibly, with clear records after every action showing exactly what happened. You can’t just add this on later; you have to design it from the ground up, starting with how requests are handled.&lt;/p&gt;

&lt;p&gt;The third reason: I simply missed PHP. More on that later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “Agent-Native” Actually Means
&lt;/h2&gt;

&lt;p&gt;Univeros is built around a simple yet powerful idea, driven by specs. Instead of writing complex code for each part of an application, you create just one YAML file that describes an endpoint. With a single command, Univeros then generates the entire vertical slice of the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# api/users/create.yaml&lt;/span&gt;
&lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;createUser&lt;/span&gt;
&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/users&lt;/span&gt;
&lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Register a new user&lt;/span&gt;

&lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;minLength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;12&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;persistence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;User&lt;/span&gt;
  &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;users&lt;/span&gt;

&lt;span class="na"&gt;idempotency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;201"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;User&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;bin/altair spec:scaffold api/users/create.yaml&lt;/code&gt; and you get nine files: the Action, the Input DTO, the Responder, the domain stub, the entity, the repository, the migration, the PHPUnit test, the OpenAPI fragment, and the route entry. Ask for a typed TypeScript or Python client, and you’ll get it too. Yes, this means zero manual edits to wire it all together. The only code you write is the business logic inside the domain's &lt;code&gt;__invoke()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now the “plumbing” that made me abandon the original project is fully automated. But it’s not just about automation. The point is the properties that make it trustworthy for an agent:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic.&lt;/strong&gt; Every time you put in the same specs, you get the same output; without variations and without surprises. Consistent, byte-for-byte results no matter the machine or PHP minor version. This consistency means any changes are easy to spot, which makes continuous integration smoother and more reliable. The system isn’t guessing among many possible outcomes; it’s delivering the one true scaffold every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reversible.&lt;/strong&gt; Every scaffold is recorded in a rewind/replay journal. A bad generation is a &lt;code&gt;journal:rewind&lt;/code&gt; away from never having happened. Agents iterate fast and sometimes they can iterate in a wrong way. But these failed attempts are recoverable and reversible. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotent and webhook-ready by default.&lt;/strong&gt; Stripe-style &lt;code&gt;Idempotency-Key&lt;/code&gt; middleware and signed inbound/outbound webhooks (HMAC, Ed25519, timestamp windows, event-id dedupe, retry, dead-letter) are first-class primitives, driven by the same spec and round-tripping cleanly through OpenAPI 3.1. Instead of distributed-systems plumbing everyone gets wrong, there is a line in a YAML file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent-operable.&lt;/strong&gt; This one deserves its own section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Toolbelt I Wish I Had from the Start
&lt;/h2&gt;

&lt;p&gt;Instead of reading the source to understand the framework, an agent working in Univeros reads &lt;em&gt;manifests&lt;/em&gt;. The &lt;code&gt;AgentSpec&lt;/code&gt; package compiles everything into a deterministic Markdown packet under &lt;code&gt;.agent/&lt;/code&gt;. Thus, "what does univeros/http expose?" costs one file read instead of a hundred tokens of spelunking.&lt;/p&gt;

&lt;p&gt;When the agent needs to act rather than know, there's a first-party &lt;strong&gt;MCP server&lt;/strong&gt; exposing the framework as 42 callable tools over stdio or HTTP. And every generated project ships an &lt;strong&gt;Altair agent skill&lt;/strong&gt; at &lt;code&gt;.claude/skills/altair/SKILL.md&lt;/code&gt;, teaching shell-capable agents like Claude Code to drive the project through &lt;code&gt;bin/altair&lt;/code&gt; directly instead of dragging every tool schema into context.&lt;/p&gt;

&lt;p&gt;From there it's a real toolbelt, and every command emits structured JSON, so the agent never parses prose to learn what happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Introspection:&lt;/strong&gt; a read-only X-ray of a booted app. Bindings, routes, listeners, middleware, specs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Doctor:&lt;/strong&gt; a health-check runner with CS, PHPStan, and test gates, plus container and DB probes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Suggest:&lt;/strong&gt; a refactor adviser that flags dead bindings, fat constructors, and routes without specs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Symbol index:&lt;/strong&gt; find-usages, callers-of, and refactor-impact answers in milliseconds, from the AST plus the specs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Eval:&lt;/strong&gt; the agent's "let me check" primitive. Run a PHP snippet in a sandboxed subprocess to get a structured result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Events:&lt;/strong&gt; an append-only &lt;code&gt;.altair/events.jsonl&lt;/code&gt; mutation log, making "what just changed?" survive across sessions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test Reporter&lt;/strong&gt; and &lt;strong&gt;Examples:&lt;/strong&gt; an AI-native PHPUnit report that maps failures to source, and a test-linked library of canonical patterns so agents copy the idiomatic way instead of inventing one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, I wrote a benchmark for the thesis: &lt;a href="https://univeros.io/docs/benchmarks/tokens-to-ship" rel="noopener noreferrer"&gt;"Tokens to Ship"&lt;/a&gt;. It measures how cheaply an agent goes from a cold prompt to a passing acceptance suite, comparing Univeros and a conventional baseline. I published the methodology with its weaknesses listed first, because a benchmark that doesn't survive scrutiny is worse than no benchmark. The claim is deliberately narrow: deterministic, scannable output costs fewer tokens than re-reading the source every time. That's the foundation of the approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Home to PHP
&lt;/h2&gt;

&lt;p&gt;I imagined that the sense of nostalgia would be a bittersweet one, much like reading a journal entry from when you were in your twenties.&lt;/p&gt;

&lt;p&gt;But this wasn’t the case since PHP has grown up in the meantime.&lt;/p&gt;

&lt;p&gt;There’s almost nothing left of the language I knew before 8.3; it has evolved significantly since then. There are now real typed properties: &lt;code&gt;readonly&lt;/code&gt; classes, enums, constructor promotion. First-class attributes now turn the “magic” used by the framework into something the engine itself understands. An agent can read this with reflection instead of guessing at. It’s clear that the ecosystem has become a “grown up.”&lt;/p&gt;

&lt;p&gt;The Univeros framework was always intended to be free of magic and fully typed. I just didn't have a language mature enough to make it pleasant, or a reason compelling enough to finish. Now I have both. &lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the Box
&lt;/h2&gt;

&lt;p&gt;Univeros consists of 40 independently usable packages under one bundle. Install everything with &lt;code&gt;composer require univeros/framework&lt;/code&gt;, or pick pieces: &lt;code&gt;univeros/cache&lt;/code&gt;, &lt;code&gt;univeros/http&lt;/code&gt;, &lt;code&gt;univeros/container&lt;/code&gt;. It follows &lt;a href="https://www.php-fig.org/psr/" rel="noopener noreferrer"&gt;PSR-7/15/14/6/16 standards&lt;/a&gt; where applicable. The HTTP stack is a PSR-15 middleware pipeline with an Action / Domain / Input / Responder lifecycle, FastRoute, and JWT/basic/digest auth. Persistence is a thin Repository / UnitOfWork contract over Cycle ORM v2. Messaging bridges Symfony Messenger with attribute-driven handler discovery. There's a runtime auto-wiring DI container, a PSR-14 event dispatcher, a command bus, sixteen input filters, eighteen validators, typed data structures in pure PHP, caching across four backends, as well as cryptographic primitives done carefully.&lt;/p&gt;

&lt;p&gt;It's fully modular by design, too. A pluggable module registers one line in &lt;code&gt;config/modules.php&lt;/code&gt; and self-wires its routes, entities, and migrations into the host. The core is named &lt;em&gt;Altair&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give It a Try
&lt;/h2&gt;

&lt;p&gt;If you've ever watched an agent waste half its context window just to figure out whether its last edit worked, do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project univeros/univeros my-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single command gives you a complete, running, tested API: a working /ping, a passing test, and the whole spec-driven toolchain wired. From there, write a spec and ship your first real endpoint in minutes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://univeros.io/docs/" rel="noopener noreferrer"&gt;univeros.io/docs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/univeros/framework" rel="noopener noreferrer"&gt;github.com/univeros/framework&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The benchmark:&lt;/strong&gt; Tokens to Ship&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seven years is a long time to leave something in a drawer. But I couldn’t have built the version I actually wanted until now. It was imagined as the thing that would eventually sit next to me and handle the plumbing.&lt;/p&gt;

&lt;p&gt;That thing finally arrived. So I finished the framework for it. &lt;/p&gt;

&lt;p&gt;In the second part of this blog series, I’ll cover the features and capabilities of &lt;a href="https://polaris.univeros.io/" rel="noopener noreferrer"&gt;Polaris&lt;/a&gt;, Univeros’ official identity module that provides a complete authentication and authorization system for applications built on the framework.&lt;/p&gt;

&lt;p&gt;Univeros is an MIT licensed project, built at &lt;strong&gt;2am.tech&lt;/strong&gt;. &lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>php</category>
      <category>agentskills</category>
    </item>
  </channel>
</rss>
