<?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: Antonio Ramirez Cobos</title>
    <description>The latest articles on DEV Community by Antonio Ramirez Cobos (@tonydspaniard).</description>
    <link>https://dev.to/tonydspaniard</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%2Fuser%2Fprofile_image%2F4010708%2F7061e6c5-8757-464a-a2d4-8717b66d5a31.jpg</url>
      <title>DEV Community: Antonio Ramirez Cobos</title>
      <link>https://dev.to/tonydspaniard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tonydspaniard"/>
    <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>[Boost]</title>
      <dc:creator>Antonio Ramirez Cobos</dc:creator>
      <pubDate>Wed, 05 Aug 2026 14:23:10 +0000</pubDate>
      <link>https://dev.to/tonydspaniard/-4k53</link>
      <guid>https://dev.to/tonydspaniard/-4k53</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://dev.to/2amtech/hai-how-to-get-your-team-to-work-best-with-ai-1231" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" 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%2Fd31r72xpnkrmavflcx15.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://dev.to/2amtech/hai-how-to-get-your-team-to-work-best-with-ai-1231" rel="noopener noreferrer" class="c-link"&gt;
            HAI: How to Get Your Team to Work Best with AI - DEV Community
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Your team is already using AI to write code. The problem is that everyone is doing it differently....
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png"&gt;
          dev.to
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
    </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>
