<?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: Anthony Garces</title>
    <description>The latest articles on DEV Community by Anthony Garces (@anthonygarces).</description>
    <link>https://dev.to/anthonygarces</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%2F4077859%2F606ef6b9-dec7-4434-b4cb-87ad820c0744.png</url>
      <title>DEV Community: Anthony Garces</title>
      <link>https://dev.to/anthonygarces</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anthonygarces"/>
    <language>en</language>
    <item>
      <title>Six Roads to a False PASS (All One Root Cause)</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 10:14:25 +0000</pubDate>
      <link>https://dev.to/anthonygarces/six-roads-to-a-false-pass-all-one-root-cause-23ah</link>
      <guid>https://dev.to/anthonygarces/six-roads-to-a-false-pass-all-one-root-cause-23ah</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Signed, subject-bound evidence can still prove the wrong action; bind each claim to the exact command before execution. &lt;a href="https://ranex.dev/blog/building-ranex-slice-log" rel="noopener noreferrer"&gt;The parent slice log&lt;/a&gt; asks what the credential was actually about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You have a credential check. It is signed, valid, and attached to the right commit. Before you trust that green light, what was the credential actually about?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id="in-this-note"&gt;In this note&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A valid credential can prove the wrong thing&lt;/li&gt;
&lt;li&gt;The binding held. The boundary around it did not.&lt;/li&gt;
&lt;li&gt;Shapes to hunt for before your next PASS&lt;/li&gt;
&lt;li&gt;Freeze bypasses instead of filing them away&lt;/li&gt;
&lt;li&gt;Make your next credential earn its label&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where a lot of pipelines quietly fail. They validate the badge and never validate the job the badge claims to represent.&lt;/p&gt;

&lt;p&gt;I hit that in Ranex with a record for &lt;code&gt;true&lt;/code&gt;. The record could be correctly signed, bound to the subject tree, and accepted as proof that tests had executed. Nothing in that sentence should make you comfortable.&lt;/p&gt;

&lt;h2 id="a-valid-credential-can-prove-the-wrong-thing"&gt;A valid credential can prove the wrong thing&lt;/h2&gt;

&lt;p&gt;Validating a credential is not validating what the credential is about. A signature can prove who recorded a command and which subject it named; it cannot turn &lt;code&gt;true&lt;/code&gt; into a test suite.&lt;/p&gt;

&lt;p&gt;Before SLICE-003, evidence satisfaction came down to a claim name, a subject digest, and exit code zero. The evaluator never looked at what ran. A worker could produce a signed, subject-bound record under the claim &lt;code&gt;tests-executed&lt;/code&gt; while running &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is a clean record of an empty action.&lt;/p&gt;

&lt;p&gt;Your equivalent might not be &lt;code&gt;true&lt;/code&gt;. It might be a smoke check treated as a release test. A scanner that reports success when it scanned no files. A deployment receipt that names a revision but never proves the artifact reached production. The shape is the same: the system checks that somebody presented a credential, then assumes the credential carries the meaning it wanted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not let a label do the work of evidence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ranex changed the claim shape in the committed gate catalog. A claim declares its required argv as a list, not a shell string. The kernel computes a digest of canonical argv bytes and compares it with the signed command digest in evidence. The readable command remains signed too, so a matching digest cannot be paired with a swapped human-readable field.&lt;/p&gt;

&lt;p&gt;The result is narrow and useful: a signed record of &lt;code&gt;true&lt;/code&gt; no longer satisfies &lt;code&gt;tests-executed&lt;/code&gt;. The catalog declares the command that can satisfy the claim.&lt;/p&gt;

&lt;p&gt;That is &lt;a href="https://ranex.dev/blog/how-the-kernel-works" rel="noopener noreferrer"&gt;a kernel decision&lt;/a&gt;, not a convention in a build script. The check does not need to trust the worker summary.&lt;/p&gt;

&lt;h2 id="the-binding-held-the-boundary-around-it-did-not"&gt;The binding held. The boundary around it did not.&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/anthonykewl20/ranex/blob/main/docs/slices/done/SLICE-003-claim-command-binding.md" rel="noopener noreferrer"&gt;Six independent audits failed to break the claim-command binding itself. The same audits found six ways to get a false PASS around it&lt;/a&gt; because observation still happened inside state the observed party controlled.&lt;/p&gt;

&lt;p&gt;This is the uncomfortable part. A strong control can be real and still be surrounded by weak seams. The right response is to name precisely what it does, then follow the attacker to the next boundary.&lt;/p&gt;

&lt;p&gt;The slice also re-proved criteria one through nine by mutation. Each safeguard was deleted from &lt;code&gt;src/&lt;/code&gt; in turn, and its covering test had to go red. That work established that the binding was enforced where it claimed to be enforced.&lt;/p&gt;

&lt;p&gt;Then the audit moved outward.&lt;/p&gt;

&lt;p&gt;One bypass inherited an environment that retargeted the bound binary. A &lt;code&gt;PYTHONPATH&lt;/code&gt; setting and a &lt;code&gt;sitecustomize.py&lt;/code&gt; outside the repository could make the bound command exit zero without running. The argv, digest, absolute interpreter, tree, and signature remained truthful.&lt;/p&gt;

&lt;p&gt;Another let a file Git was told to exclude decide the outcome. Another used a clean filter that made a modified tracked file hash as if it were the committed blob. Others targeted the trust-root blob, the &lt;code&gt;git&lt;/code&gt; executable selected from an editable path, or an untracked empty directory that Git cannot represent.&lt;/p&gt;

&lt;p&gt;Six roads. One root cause: &lt;strong&gt;the thing being measured still controlled the place and tools used to measure it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The slice did not pretend those paths were closed. They were reproduced, recorded, and assigned to the next slice. That is a better status than a clean report that has never tried to drive around the guardrail.&lt;/p&gt;

&lt;h2 id="shapes-to-hunt-for-before-your-next-pass"&gt;Shapes to hunt for before your next PASS&lt;/h2&gt;

&lt;p&gt;Look for the seam between a declared command and the process that actually runs. Attackers do not need to defeat a digest when they can influence the interpreter, environment, filesystem, or tool that feeds it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A claim name that has no pinned argv, test-plan digest, or other executable definition.&lt;/li&gt;
&lt;li&gt;A command comparison performed as a shell string rather than a structured argument vector.&lt;/li&gt;
&lt;li&gt;A signed digest that does not cover the human-readable command or executable path.&lt;/li&gt;
&lt;li&gt;Environment variables inherited by the measured command.&lt;/li&gt;
&lt;li&gt;Tools resolved from ambient &lt;code&gt;PATH&lt;/code&gt; or from a directory the observed party can write.&lt;/li&gt;
&lt;li&gt;Ignored, excluded, or untracked files that can affect the measured result.&lt;/li&gt;
&lt;li&gt;Git configuration, filters, replacement refs, or other local state that can change what “committed” appears to mean.&lt;/li&gt;
&lt;li&gt;A passing test that removes a safeguard only in a mock instead of from the running path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each shape, write down the proposition you need to prove. “The record is signed” is not enough. “The catalog-bound argv ran against the subject in a measurement environment chosen outside the observed party” is a proposition you can test.&lt;/p&gt;

&lt;p&gt;That sentence is longer because the boundary is larger. Good. Short security claims often hide missing nouns.&lt;/p&gt;

&lt;h2 id="freeze-bypasses-instead-of-filing-them-away"&gt;Freeze bypasses instead of filing them away&lt;/h2&gt;

&lt;p&gt;When you reproduce a bypass, freeze it as a strict expected-failure test with a green control beside it. That keeps the hole visible without pretending it is fixed.&lt;/p&gt;

&lt;p&gt;SLICE-003 did exactly that for the false-PASS paths around the binding. Each one is a strict &lt;code&gt;xfail&lt;/code&gt;. Each has a control that proves the test can pass when the vulnerable condition is absent. The expected failure will fail loudly on the day the path is actually closed.&lt;/p&gt;

&lt;p&gt;This matters because a plain issue can go stale, lose its reproduction, or be mistaken for a theoretical concern. A strict expected failure runs in the suite. It carries the attack shape forward with the code.&lt;/p&gt;

&lt;p&gt;There is a trap here: an expected failure without a green control can pass vacuously. The test might not run the relevant code at all. You have traded one invisible gap for another. Put the control beside the bypass. Make both explain themselves.&lt;/p&gt;

&lt;p&gt;Also state the boundary plainly. SLICE-003 did not claim output digests, authentic approver identity, or key containment. A command matching the bound digest can still be trusted to have done what its name suggests. That limitation was recorded rather than decorated with an unchecked field.&lt;/p&gt;

&lt;p&gt;Ranex is pre-release. The claim-command binding is built; the surrounding observation hardening was next-slice work in this record. “Designed, not built” is a useful sentence when a control stops at a real boundary.&lt;/p&gt;

&lt;h2 id="questions-people-actually-ask"&gt;Questions people actually ask&lt;/h2&gt;

&lt;p&gt;These questions help you bind a CI claim to the command that produced it.&lt;/p&gt;

&lt;h3&gt;How do you bind a CI claim to the command that produced it?&lt;/h3&gt;

&lt;p&gt;The committed catalog declares the argv that satisfies a claim, and the kernel compares a digest of that argv with the signed evidence.&lt;/p&gt;

&lt;h3&gt;Why does a signed record of true not prove tests executed?&lt;/h3&gt;

&lt;p&gt;A signature and subject digest establish who recorded an observation and which tree it names. They do not establish that the command was a test run.&lt;/p&gt;

&lt;h3&gt;What should happen to bypasses found around a control?&lt;/h3&gt;

&lt;p&gt;A team should reproduce bypasses, keep a green control beside each one, and freeze them as strict expected-failure tests until the next slice closes the root cause.&lt;/p&gt;

&lt;h2 id="make-your-next-credential-earn-its-label"&gt;Make your next credential earn its label&lt;/h2&gt;

&lt;p&gt;Pick one important claim in your pipeline: tests executed, artifact scanned, release approved, migration applied. Find the credential that satisfies it. Now write the exact action that credential is supposed to represent.&lt;/p&gt;

&lt;p&gt;Bind that action before the worker runs. Verify the binding after it returns. Then try to go around it through the environment, the executable path, the working tree, and every ignored input you can name.&lt;/p&gt;

&lt;p&gt;The reproductions live in &lt;a href="https://github.com/anthonykewl20/ranex" rel="noopener noreferrer"&gt;the Ranex repository&lt;/a&gt;, under &lt;code&gt;docs/slices/done/&lt;/code&gt;. Take the shape, not the marketing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it. Break it. Tell me what broke.&lt;/strong&gt; If this gave you a useful test, star the repository and send an honest critique. A false PASS found before release is a gift.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this post was drafted with AI assistance. Every factual claim traces to the repository’s README or slice records, the same fact gate the product enforces on code. It ships only after Anthony’s own review.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agentsafety</category>
      <category>evidence</category>
      <category>testdesign</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>An Environment Variable Can Choose the Repo You Judge</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:09:02 +0000</pubDate>
      <link>https://dev.to/anthonygarces/an-environment-variable-can-choose-the-repo-you-judge-58ld</link>
      <guid>https://dev.to/anthonygarces/an-environment-variable-can-choose-the-repo-you-judge-58ld</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; A git safety flag cannot protect you if inherited environment state chooses a different repository first; build the child environment deliberately. The &lt;a href="https://ranex.dev/blog/building-ranex-slice-log" rel="noopener noreferrer"&gt;Ranex slice log&lt;/a&gt; shows why the observed artifact must be the artifact you intended.&lt;/p&gt;

&lt;p&gt;You can write “refuse” in a threat table and still leave the door open. That happens when a control answers one question while an unexamined input answers the more important one.&lt;/p&gt;

&lt;p&gt;For a tool that judges a repository, the first question is painfully basic: which repository did it actually read?&lt;/p&gt;

&lt;h2 id="in-this-note"&gt;In this note&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The flag did not choose the repository&lt;/li&gt;
&lt;li&gt;Ambient state is an input&lt;/li&gt;
&lt;li&gt;The fix builds the child environment&lt;/li&gt;
&lt;li&gt;What the boundary still misses&lt;/li&gt;
&lt;li&gt;An audit you can run&lt;/li&gt;
&lt;li&gt;Where Ranex stands&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="the-flag-did-not-choose-the-repository"&gt;The flag did not choose the repository&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;--no-replace-objects&lt;/code&gt; stops git object substitution inside the repository it is querying. It does not choose that repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/anthonykewl20/ranex/blob/main/docs/adr/ADR-004-environment-boundary-for-git-queries.md" rel="noopener noreferrer"&gt;ADR-004&lt;/a&gt; records how Ranex learned that distinction. An earlier ADR said every git query passed &lt;code&gt;--no-replace-objects&lt;/code&gt;, so a &lt;code&gt;git replace&lt;/code&gt; substitution would refuse. The statement was only half a boundary.&lt;/p&gt;

&lt;p&gt;At nine of ten call sites, Ranex’s shared &lt;code&gt;git()&lt;/code&gt; helper passed &lt;code&gt;env=None&lt;/code&gt;. That inherited the caller’s &lt;code&gt;GIT_*&lt;/code&gt; variables. A relative &lt;code&gt;GIT_DIR&lt;/code&gt; could therefore direct subject-digest reads, trust-root reads, and scratch-index status at a shadow repository the observed party controlled.&lt;/p&gt;

&lt;p&gt;The attack was reproduced end to end without forging anything. Genuine signed evidence produced in the shadow tree passed the governed repository’s gate even though the governed repository’s own committed check exited 1. The evidence was signed correctly. The measuring instrument was aimed at the wrong thing.&lt;/p&gt;

&lt;h2 id="ambient-state-is-an-input"&gt;Ambient state is an input&lt;/h2&gt;

&lt;p&gt;Inherited environment variables are inputs, even when no function parameter mentions them. If the observed party can set them, they can influence what your tool observes.&lt;/p&gt;

&lt;p&gt;This was not a git-only lesson. &lt;code&gt;PATH&lt;/code&gt; can choose the binary behind a command name. &lt;code&gt;HOME&lt;/code&gt; can choose global configuration. A process that silently inherits either has accepted a decision from outside its explicit interface.&lt;/p&gt;

&lt;p&gt;That does not mean one fix closes every environment risk. ADR-004 scopes its change to the environment of git queries Ranex itself makes. It says plainly that &lt;code&gt;HOME&lt;/code&gt; still selects &lt;code&gt;~/.gitconfig&lt;/code&gt;, &lt;code&gt;PATH&lt;/code&gt; can still select a git shim, and a repository’s own &lt;code&gt;.git/config&lt;/code&gt; remains in scope for git. Naming the limit is part of the control.&lt;/p&gt;

&lt;p&gt;This same shape sits behind several false-pass routes: a toolchain or import path selected by the party being measured can make a check run somewhere other than the code you thought you checked. &lt;a href="https://ranex.dev/blog/six-roads-to-a-false-pass" rel="noopener noreferrer"&gt;Six roads to a false pass&lt;/a&gt; covers the related &lt;code&gt;PYTHONPATH&lt;/code&gt; and &lt;code&gt;sitecustomize&lt;/code&gt; bypasses.&lt;/p&gt;

&lt;h2 id="the-fix-builds-the-child-environment"&gt;The fix builds the child environment&lt;/h2&gt;

&lt;p&gt;Ranex now removes every &lt;code&gt;GIT_*&lt;/code&gt; key from &lt;code&gt;os.environ&lt;/code&gt; before launching git, then applies only explicit call-site overrides. Ambient &lt;code&gt;GIT_*&lt;/code&gt; values no longer choose the repository.&lt;/p&gt;

&lt;p&gt;The helper’s parameter changed from &lt;code&gt;env&lt;/code&gt; to &lt;code&gt;overrides&lt;/code&gt;, because the old name suggested a complete environment when the intended meaning was a small deliberate addition. One call site needs that addition: &lt;code&gt;uncommitted_paths&lt;/code&gt; passes the scratch index it computed as &lt;code&gt;GIT_INDEX_FILE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The choice is broader than a denylist. A future variable beginning &lt;code&gt;GIT_&lt;/code&gt; is excluded as soon as it exists. Git’s own environment helper was not copied: it intentionally lets configuration variables through so &lt;code&gt;-c&lt;/code&gt; works across submodules. ADR-004 records that a copied allowlist would have preserved that gap.&lt;/p&gt;

&lt;p&gt;The backing test does not settle for “the poisoned run did not pass.” It asserts that the poisoned evaluation returns the same verdict as the clean evaluation. A crash could satisfy the weaker assertion and still hide a broken boundary. Restoring inherited environment makes the reproduction red again while the clean controls stay green.&lt;/p&gt;

&lt;h2 id="what-the-boundary-still-misses"&gt;What the boundary still misses&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;GIT_*&lt;/code&gt; boundary closes the reproduced redirect, not every input git honors. Repository-local config can still inject a filter; ADR-004 leaves that as a strict expected failure. &lt;code&gt;HOME&lt;/code&gt;, &lt;code&gt;PATH&lt;/code&gt;, and future non-&lt;code&gt;GIT_*&lt;/code&gt; variables are also outside this specific control.&lt;/p&gt;

&lt;p&gt;That disclosure is not an apology for the fix. It is what keeps a narrow control from becoming a broad claim. The earlier defect was not merely an unhandled edge case; it was a sad-path row that implied it covered more than it did.&lt;/p&gt;

&lt;p&gt;If your own tool shells out, inspect the process boundary as closely as the command-line flags. Ask which inputs select the target, which inputs select the executable, which inputs select configuration, and which of those inputs came from the caller by accident.&lt;/p&gt;

&lt;h2 id="an-audit-you-can-run"&gt;An audit you can run&lt;/h2&gt;

&lt;p&gt;You can find this bug shape before an incident. Start with the boundary that decides what your tool reads, not only the boundary that decides what it may do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Locate every subprocess wrapper.&lt;/strong&gt; A single shared helper is useful only if every relevant query uses it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List ambient selectors.&lt;/strong&gt; Check environment, current directory, config discovery, executable lookup, and inherited file descriptors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate target from action.&lt;/strong&gt; A flag can constrain an action while saying nothing about the target.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Construct the child environment.&lt;/strong&gt; Start from an intentional set and admit only values the program calculated or explicitly owns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poison the environment in a test.&lt;/strong&gt; Assert the clean and poisoned runs produce the same result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down exclusions.&lt;/strong&gt; A boundary that cannot see an input must not claim to control it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="where-ranex-stands"&gt;Where Ranex stands&lt;/h2&gt;

&lt;p&gt;Ranex is pre-release. The README describes a kernel with a working verdict path and a larger designed surface that is not yet built. ADR-004 records a closed control in that kernel boundary; it does not turn the project into a finished product or erase the separate risks listed above.&lt;/p&gt;

&lt;p&gt;Your next review should begin one layer earlier than the flag. Confirm the process is asking the right repository before you celebrate the answer it received. Try it. Break it. Tell me what broke.&lt;/p&gt;

&lt;h2 id="questions-people-actually-ask"&gt;Questions people actually ask&lt;/h2&gt;

&lt;h3&gt;Can GIT_DIR make a tool judge the wrong repository?&lt;/h3&gt;

&lt;p&gt;ADR-004 reproduced a relative GIT_DIR redirect that made Ranex read the subject digest, trust roots, and scratch-index status from a shadow repository controlled by the observed party.&lt;/p&gt;

&lt;h3&gt;Does &lt;code&gt;--no-replace-objects&lt;/code&gt; choose the repository git reads?&lt;/h3&gt;

&lt;p&gt;No: ADR-004 says &lt;code&gt;--no-replace-objects&lt;/code&gt; constrains object substitution inside a repository, while inherited environment variables had separately chosen which repository Ranex queried.&lt;/p&gt;

&lt;h3&gt;How did Ranex stop ambient GIT_* variables?&lt;/h3&gt;

&lt;p&gt;Ranex changed git() to build a child environment from os.environ minus every GIT_* key and then apply only explicit call-site overrides, including the computed GIT_INDEX_FILE override.&lt;/p&gt;

&lt;h3&gt;What does the GIT_* fix still not cover?&lt;/h3&gt;

&lt;p&gt;ADR-004 leaves repository-local configuration, HOME-selected global gitconfig, PATH-selected git binaries, and non-GIT_* variables outside this boundary.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this post was drafted with AI assistance. Every factual claim traces to the repository’s README or slice records, the same fact gate the product enforces on code. It ships only after Anthony’s own review.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>evidence</category>
      <category>supplychain</category>
      <category>kernel</category>
    </item>
    <item>
      <title>Open Source Signal, Week 1: What Held Up, What We Corrected, What We Left Out</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:07:28 +0000</pubDate>
      <link>https://dev.to/anthonygarces/open-source-signal-week-1-what-held-up-what-we-corrected-what-we-left-out-42la</link>
      <guid>https://dev.to/anthonygarces/open-source-signal-week-1-what-held-up-what-we-corrected-what-we-left-out-42la</guid>
      <description>&lt;h2&gt;The week the license fine print became the headline&lt;/h2&gt;

&lt;p&gt;You spent part of this week clicking "merge" on dependency bot PRs before coffee, the way you do every week. Nothing unusual there. What was unusual was how much of the actual conversation in your feeds had nothing to do with releases and everything to do with license text: AppFlowy-Cloud getting archived, AFFiNE's split-license repo raised questions, Outline's BSL status coming up once someone read the LICENSE file.&lt;/p&gt;

&lt;p&gt;And then there was OpenMAIC, a repository that landed in the team's adoption discussion by midweek. Here's the honest answer about it: GitHub's own file detection classifies OpenMAIC's LICENSE file as MIT. That's the whole claim. If you saw someone describe a license swap with specific dates attached, ask them for a dated source.&lt;/p&gt;

&lt;p&gt;None of this is dramatic on its own. But a week where three separate projects raised license questions, in three different ways, is worth noticing as a pattern rather than three coincidences.&lt;/p&gt;

&lt;h2&gt;A signal system is judged by its corrections, not its hits&lt;/h2&gt;

&lt;p&gt;Anyone can publish a list of releases. Copy the changelog, add a sentence, done. That's not a signal system, that's a mirror. What makes a roundup worth trusting isn't how many items it names: it's whether it tells you when it was wrong, what it left out, and why.&lt;/p&gt;

&lt;p&gt;So the corrections log below isn't a footnote you can skip. It's the part of this post doing the most work. If AppFlowy-Cloud shows up on last month's shortlist you saved, that shortlist is stale as of September 1. If someone told you AFFiNE is "just MIT," that's incomplete, and the incompleteness matters if you're planning a production deployment. Reports come from the worker. Verdicts come from a judge. The correction is where the judging shows up in public.&lt;/p&gt;

&lt;h2&gt;How items earned inclusion&lt;/h2&gt;

&lt;p&gt;Nothing here is new research. Everything in this roundup was checked during Days 1 through 6 of the week, then re-verified against primary sources before publication, because facts about licenses, security advisories, and release notes move, sometimes within days.&lt;/p&gt;

&lt;p&gt;Three filters decided what made the cut. First, primary sources checked the same morning as the claim: a GitHub release page, a raw LICENSE file, a security advisory, not a summary of one. Second, a claim gate: every sentence in this post that could change a decision you make has to trace back to something in the verification packet, or it doesn't get written. Third, no item made it in for recency alone. A release with no listed changes doesn't count as a highlight just because it's this week's.&lt;/p&gt;

&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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-weekly-roundup-2026-09-06-v2.svg" 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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-weekly-roundup-2026-09-06-v2.svg" alt="Animated weekly roundup: a thread travels an arc of seven panes, marking the verified ones and bypassing the excluded" width="1000" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Proof: what held up&lt;/h2&gt;

&lt;p&gt;Re-verified against primary sources on September 2, 2026; volatile facts re-checked again before Sunday publication.&lt;/p&gt;

&lt;p&gt;A note on scope: the links below point to posts published earlier this week, and each of those carries its own checked-at date. Trust that date for its facts, not this one.&lt;/p&gt;

&lt;h3&gt;Verified highlights&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AppFlowy-Cloud was archived September 1, 2026.&lt;/strong&gt; Its README now calls it legacy and unmaintained, and states that production self-hosting has moved to a closed-source commercial fork. The shortlists that still name AppFlowy Cloud are stale as of September 1. &lt;a href="https://github.com/AppFlowy-IO/AppFlowy-Cloud" rel="noopener noreferrer"&gt;AppFlowy-Cloud repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AFFiNE's license is split, not uniform.&lt;/strong&gt; The repository tree describes itself as MIT for the community edition, but &lt;code&gt;packages/backend&lt;/code&gt; and &lt;code&gt;packages/common/native&lt;/code&gt; sit under a separate license whose server file requires a valid AFFiNE Enterprise Edition subscription for production use. &lt;a href="https://raw.githubusercontent.com/toeverything/AFFiNE/canary/LICENSE" rel="noopener noreferrer"&gt;Root LICENSE&lt;/a&gt; · &lt;a href="https://raw.githubusercontent.com/toeverything/AFFiNE/canary/packages/backend/server/LICENSE" rel="noopener noreferrer"&gt;Backend server LICENSE&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SiYuan v3.8.2 (August 30) fixed a critical stored-XSS advisory.&lt;/strong&gt; GHSA-7h8j-qw37-w46g, CVSS 9.0, affects 3.8.1 and earlier. The advisory has no CVE ID assigned yet and may be revised. Treat the severity number as current, not final. &lt;a href="https://github.com/siyuan-note/siyuan/security/advisories/GHSA-7h8j-qw37-w46g" rel="noopener noreferrer"&gt;Security advisory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gitea v1.27.3 (August 29) shipped a SECURITY-led release.&lt;/strong&gt; Package token scopes, attachment path enforcement, Actions artifact signatures, fork-PR trust boundaries, and hook permissions all changed. If you're self-hosting below 1.27.3, this is the release you're behind on. &lt;a href="https://github.com/go-gitea/gitea/releases/tag/v1.27.3" rel="noopener noreferrer"&gt;Release notes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keycloak 26.7.3 (August 31) closed a CVE batch.&lt;/strong&gt; Three worth naming are CVE-2026-35563 (LDAP hostname verification), CVE-2026-16093 (signed-JWT assertion policy bypass), and CVE-2026-16089 (authorization code retargeting). &lt;a href="https://github.com/keycloak/keycloak/releases/tag/26.7.3" rel="noopener noreferrer"&gt;Release notes&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Corrections log&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Shortlists still calling AppFlowy Cloud "the open-source Notion answer" are stale as of September 1: it's archived.&lt;/li&gt;
&lt;li&gt;Outline v1.10.0 is Business Source License 1.1. Its own LICENSE file states plainly that it "is not an Open Source license," with a Change Date of 2030-09-01 before it converts to Apache-2.0. &lt;a href="https://raw.githubusercontent.com/outline/outline/main/LICENSE" rel="noopener noreferrer"&gt;Outline LICENSE&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;"AFFiNE is just MIT" is incomplete: the Enterprise Edition server file requires a paid subscription for production use.&lt;/li&gt;
&lt;li&gt;NocoDB's Sustainable Use License is not OSI-approved, by NocoDB's own documentation, and the LICENSE file itself never claims to be open source. &lt;a href="https://docs.nocodb.com/getting-started/self-hosted/license" rel="noopener noreferrer"&gt;NocoDB license docs&lt;/a&gt; · &lt;a href="https://raw.githubusercontent.com/nocodb/nocodb/develop/LICENSE.md" rel="noopener noreferrer"&gt;LICENSE.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The Day 2 worked example's repository numbers (commits, issues, pull requests) were re-checked and updated before this publication because they'd already moved within days. A snapshot without a date stamp is a snapshot that rots.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;Watchlist, and what we left out&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 1.37 missed our UTC release window by hours: published 2026-08-26T16:29Z, already August 27 in Manila. It stays a watchlist note, not a highlight; we're not treating it as material-impact this week. &lt;a href="https://github.com/kubernetes/kubernetes/releases/tag/v1.37.0" rel="noopener noreferrer"&gt;Release notes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wiki.js 3.x is beta, marked "Not for production use," while v2.5.314 remains the production Latest release.&lt;/li&gt;
&lt;li&gt;
&lt;a href="mailto:n8n@2.37.7"&gt;n8n@2.37.7&lt;/a&gt; published with no listed features, fixes, or security notes attached. Recency without documented impact, so it doesn't earn a highlight slot. &lt;a href="https://github.com/n8n-io/n8n/releases/tag/n8n%402.37.7" rel="noopener noreferrer"&gt;Release&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Three items carry date-bound caveats worth repeating: the SiYuan advisory has no CVE ID yet and advisories get revised; the Day 6 GitHub CLI responsiveness numbers were a September 2 hand sample of the nine newest issues, bot median around 3.5 minutes, human maintainers visible on two of nine; Uptime Kuma's v2 removal of JSON backup was checked on Day 5 only. None of these are permanent facts. They're facts as of a specific day.&lt;/li&gt;
&lt;li&gt;Wiki.js install docs never loaded for verification because the page is JavaScript-rendered, so they stay unverified rather than assumed.&lt;/li&gt;
&lt;li&gt;Excluded outright: Outline (BSL, not open source), AppFlowy-Cloud (archived), and anything that wasn't verified during Days 1 through 6.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This week's watchlist is shorter than some weeks. That's not an oversight: a quiet section stays short, and length here isn't padded to look thorough.&lt;/p&gt;

&lt;h2&gt;Which check will you adopt next week?&lt;/h2&gt;

&lt;p&gt;You don't need to run every check in this series at once. Pick the one that matches whatever you're actually doing right now (evaluating a new dependency, self-hosting something, or just trying to figure out if a tool is still maintained) and run that one this week.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're pasting AI answers into decisions, start with &lt;a href="/blog/oss-signal-prove-ai-source"&gt;proving which source your AI cited&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Before adopting anything new, run &lt;a href="/blog/oss-signal-assess-new-github-repo"&gt;six checks before adopting a new repository&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you're choosing between knowledge-workspace tools, read &lt;a href="/blog/oss-signal-open-source-knowledge-workspace-alternative"&gt;comparing open-source knowledge-workspace alternatives&lt;/a&gt;. It's the piece that surfaces the AppFlowy and AFFiNE license details in full.&lt;/li&gt;
&lt;li&gt;For a rundown of &lt;a href="/blog/oss-signal-release-changes-2026-09-03"&gt;the week's release changes&lt;/a&gt;, that post has the day-by-day detail this roundup compresses.&lt;/li&gt;
&lt;li&gt;Planning to self-host anything from this list, work through &lt;a href="/blog/oss-signal-self-hosted-evaluation-checklist"&gt;the five-part self-hosting checklist&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;And if you're looking to give back rather than just consume, &lt;a href="/blog/oss-signal-find-active-project-to-contribute"&gt;finding a project worth your first contribution&lt;/a&gt; is the practical starting point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All six live under the &lt;a href="/open-source"&gt;open-source field notes&lt;/a&gt; hub if you want the full week in order.&lt;/p&gt;

&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;Will you do this every week?&lt;/h3&gt;

&lt;p&gt;That's the intent: a Sunday roundup, built from what got verified Monday through Saturday, with corrections logged in the open. Whether it continues depends on whether it stays useful to you and whether the verification process holds up under a weekly cadence without cutting corners. If it starts feeling padded or rushed, that's a sign to slow down or stop, not to keep shipping anyway.&lt;/p&gt;

&lt;h3&gt;What does corrections mean here?&lt;/h3&gt;

&lt;p&gt;It means a specific, dated log of claims that changed, were previously incomplete, or turned out wrong once checked against a primary source, not a vague "we try to be accurate" disclaimer. This week's log has five entries above. Some weeks will have more, some fewer. The number isn't the point; the willingness to publish it is.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosures: ranex.dev is Anthony Garces's site, and Leitir (the tool used in Day 1's provenance walkthrough) is his tool. That post is a methodology walkthrough, not a ranking or an independent endorsement. Research and drafting for this series were AI-assisted; human review is required before anything here gets published.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>licensing</category>
      <category>opensource</category>
      <category>security</category>
      <category>weeklyroundup</category>
    </item>
    <item>
      <title>Three OSS Release Changes This Week That Actually Change Something</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:06:39 +0000</pubDate>
      <link>https://dev.to/anthonygarces/three-oss-release-changes-this-week-that-actually-change-something-eon</link>
      <guid>https://dev.to/anthonygarces/three-oss-release-changes-this-week-that-actually-change-something-eon</guid>
      <description>&lt;h2&gt;Fourteen pull requests before coffee&lt;/h2&gt;

&lt;p&gt;Your dependency bot opened 14 pull requests overnight. Version bumps everywhere: a patch here, a minor there, one thing that looks like it might be a rewrite. Sprint planning is in twenty minutes.&lt;/p&gt;

&lt;p&gt;You cannot read fourteen changelogs before standup. You can barely read one. So you do what most people do: you skim the titles, you merge the ones that look boring, and you leave a tab open for the one that doesn't. That tab usually stays open for a week.&lt;/p&gt;

&lt;p&gt;The problem isn't that you're lazy. It's that the list is sorted by date, and date tells you nothing about whether a release changed something you need to react to.&lt;/p&gt;

&lt;h2&gt;Recency is not impact&lt;/h2&gt;

&lt;p&gt;A changelog firehose treats every release the same: newest first, no ranking by consequence. A patch that renames an internal function sits next to a patch that closes a critical vulnerability, and they look identical in a notification feed.&lt;/p&gt;

&lt;p&gt;So here's the bar this post uses. A release counts as material if it clears at least one of four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A breaking change: something stops working the way it did.&lt;/li&gt;
&lt;li&gt;A license change: the terms you're operating under shifted.&lt;/li&gt;
&lt;li&gt;A security-relevant default or behavior change: access control, authentication, or a vulnerability fix.&lt;/li&gt;
&lt;li&gt;A deprecation or a major new capability: something is going away, or something new just became load-bearing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A release that clears none of these is routine maintenance. It might still be worth merging. It's just not a decision.&lt;/p&gt;

&lt;h2&gt;Four questions that separate signal from noise&lt;/h2&gt;

&lt;p&gt;Before you decide a release deserves your attention, ask:&lt;/p&gt;

&lt;p&gt;Does the changelog name an actual behavior change, or is it just a version bump with no notes attached?&lt;/p&gt;

&lt;p&gt;Who is actually in the affected range: is this you, or a configuration you don't run?&lt;/p&gt;

&lt;p&gt;Is there a primary source (a release page, a security advisory, a migration guide), or just a tag and a badge?&lt;/p&gt;

&lt;p&gt;What happens if you do nothing? Sometimes the honest answer is "nothing," and that's useful to know too.&lt;/p&gt;

&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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-release-changes-2026-09-03-v2.svg" 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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-release-changes-2026-09-03-v2.svg" alt="Animated release timeline: three bright seams mark material changes cutting through quiet strata" width="1000" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;What actually changed this week&lt;/h2&gt;

&lt;p&gt;Checked against primary sources on September 3, 2026. Release tags and "Latest" labels can change overnight, and advisories get revised after publication, so recheck before you act on any of this. What counts as "material" here is this post's judgment call, not an official ranking. A release missing from this list isn't a comment on its quality.&lt;/p&gt;

&lt;h3&gt;Gitea v1.27.3&lt;/h3&gt;

&lt;p&gt;Published 2026-08-29T17:42:17Z, not a prerelease. The release notes lead with a SECURITY section, not ENHANCEMENTS: package token-scope handling, attachment path enforcement, same-repo issue access in markup, Actions artifact signatures, hiding limited users, fork-PR trust boundaries, hook permissions, and repo-creation token authorization all get fixes. No CVE is assigned on the page, and there's no CVSS score attached.&lt;/p&gt;

&lt;p&gt;If you self-host Gitea, this one is yours to read. The release page doesn't enumerate which older versions each fix covers, so open the SECURITY list and check it against what your instance actually exposes. Gitea Cloud instances auto-upgrade in a maintenance window, so that's not your call to make. The license is unchanged: MIT at the v1.27.3 tag.&lt;/p&gt;

&lt;p&gt;Primary source: &lt;a href="https://github.com/go-gitea/gitea/releases/tag/v1.27.3" rel="noopener noreferrer"&gt;Gitea v1.27.3 release notes&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;SiYuan v3.8.2&lt;/h3&gt;

&lt;p&gt;Published 2026-08-30T04:36:15Z. The changelog lists one bugfix line ("Some security vulnerabilities") linked to issue #18837, which indexes several GHSAs closed into this release. The one worth naming is GHSA-7h8j-qw37-w46g: Critical, CVSS 9.0, an incomplete asset blocklist that let a previously-fixed stored-XSS issue come back. Affected versions are &amp;lt;= 3.8.1; 3.8.2 patches it. The advisory was published 2026-08-19 and carries no CVE ID.&lt;/p&gt;

&lt;p&gt;The advisory notes that desktop builds bundling pandoc carry higher impact than the official Docker container, which ships without pandoc at all. That distinction matters if you're choosing between install methods, and not only whether to upgrade. License is unchanged: AGPL v3.&lt;/p&gt;

&lt;p&gt;Primary source: &lt;a href="https://github.com/siyuan-note/siyuan/security/advisories/GHSA-7h8j-qw37-w46g" rel="noopener noreferrer"&gt;GHSA-7h8j-qw37-w46g advisory&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Keycloak 26.7.3&lt;/h3&gt;

&lt;p&gt;Published 2026-08-31T09:29:37Z, marked Latest, not a prerelease. This is a CVE batch, not a single fix. CVE-2026-35563 covers an LDAP client that doesn't verify the server certificate hostname. CVE-2026-16093 is a signed-JWT assertion policy bypass through unsigned headers. CVE-2026-16089 lets authorization codes get retargeted to a different client session. The same notes list token-exchange tenant and hosted-domain bypasses (CVE-2026-18215 and CVE-2026-18214), plus bugs in FGAP v2 authorization.&lt;/p&gt;

&lt;p&gt;The release page doesn't say which earlier patch levels each CVE reaches. What it does name is components: the security-fix rows carry labels for OIDC, LDAP, organizations, token-exchange, and FGAP v2. If your Keycloak leans on any of those, this is your release. Keycloak links a migration guide alongside the notes. I didn't re-fetch the individual GHSAs for this one; the release page was the primary source I checked.&lt;/p&gt;

&lt;p&gt;Primary source: &lt;a href="https://github.com/keycloak/keycloak/releases/tag/26.7.3" rel="noopener noreferrer"&gt;Keycloak 26.7.3 release notes&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;The rest of the firehose&lt;/h3&gt;

&lt;p&gt;Most of what showed up this week didn't clear the bar. Wiki.js 3.0.0-beta.537 published a body that's one line long: "3.x Beta Release – Not for production use." n8n@2.37.7 shows a compare header and a badge, with no listed features, fixes, or security notes. There may be a patch buried in the commit history, but the release page doesn't document one. Recency without documented impact is most of what a dependency bot hands you.&lt;/p&gt;

&lt;p&gt;A few releases were checked and deliberately left off this list. Outline v1.10.0 is excluded because its own LICENSE file says BSL 1.1 is not an Open Source license. That's not a technicality, it's the license text itself. AppFlowy-Cloud was archived on 2026-09-01, which is a deprecation announcement, not a versioned release. Kubernetes v1.37.0 published at 2026-08-26T16:29:12Z UTC, which lands just before this week's UTC window opens, even though it landed on 2026-08-27 in Manila time, I excluded it to keep the window boundary consistent. And Paperless-ngx v3.1.2 shipped a real security patch for GHSA-2jhj-xqrq-rmrq, a path traversal issue, and the maintainers recommend it for all users. It didn't make the three because the advisory rates it Low, CVSS 3.1, and the setting it depends on defaults to false. That's a call about relative impact this week, not a knock on the patch.&lt;/p&gt;

&lt;h2&gt;Which one is yours?&lt;/h2&gt;

&lt;p&gt;You don't need to read all fourteen changelogs. Could you check the one item that touches your stack today? Self-hosted Gitea: that one is yours to check. A SiYuan install you haven't touched since last month: what version is it on? Keycloak on 26.7.x: is the migration guide open anywhere yet?&lt;/p&gt;

&lt;p&gt;For everything else in the queue (the repos you've never evaluated before, beyond the ones bumping versions), see the method at &lt;a href="/blog/oss-signal-assess-new-github-repo"&gt;/blog/oss-signal-assess-new-github-repo&lt;/a&gt;. And for more releases like this, &lt;a href="/open-source"&gt;/open-source&lt;/a&gt; is where this series lives.&lt;/p&gt;

&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;Is Gitea 1.27.3 a security update?&lt;/h3&gt;

&lt;p&gt;Yes, in the sense that the release notes are organized under a SECURITY heading covering access-control fixes across packages, attachments, issues, Actions, and hooks. There's no CVE or CVSS score attached to the page, so treat it as a security-relevant patch batch rather than a scored vulnerability disclosure.&lt;/p&gt;

&lt;h3&gt;What is GHSA-7h8j-qw37-w46g?&lt;/h3&gt;

&lt;p&gt;It's a Critical, CVSS 9.0 advisory for SiYuan affecting versions &amp;lt;= 3.8.1, fixed in 3.8.2. It describes an incomplete asset blocklist that reopened a previously-patched stored-XSS issue. Desktop installs bundling pandoc carry higher impact than the official Docker image, which doesn't include pandoc.&lt;/p&gt;

&lt;h3&gt;Why isn't Kubernetes 1.37 on this list?&lt;/h3&gt;

&lt;p&gt;It published at 2026-08-26T16:29:12Z UTC, just before this week's UTC window starts. It landed on 2026-08-27 in Manila time, but I kept the window boundary in UTC for consistency and left it out. That's a judgment call, not a claim that the release doesn't matter.&lt;/p&gt;

&lt;h3&gt;How do I judge next week's releases?&lt;/h3&gt;

&lt;p&gt;Run them against the same four criteria: a breaking change, a license change, a security-relevant default or behavior change, or a deprecation or major capability shift. If a release clears none of those, it's routine maintenance you can merge without ceremony.&lt;/p&gt;





&lt;p&gt;&lt;strong&gt;Disclosures:&lt;/strong&gt; This post is part of ranex.dev's open-source-signal series, written by Anthony Garces, who runs ranex.dev. Read it as a first-party source, not a neutral third party. Research and drafting for this post used AI assistance; all facts were checked against primary sources on the date stated above. Human review is required before publication.&lt;/p&gt;

</description>
      <category>gitea</category>
      <category>keycloak</category>
      <category>opensource</category>
      <category>releasenotes</category>
    </item>
    <item>
      <title>Before You Adopt a New Repository: Six Checks Beyond Stars</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:05:51 +0000</pubDate>
      <link>https://dev.to/anthonygarces/before-you-adopt-a-new-repository-six-checks-beyond-stars-41i0</link>
      <guid>https://dev.to/anthonygarces/before-you-adopt-a-new-repository-six-checks-beyond-stars-41i0</guid>
      <description>&lt;p&gt;A repository link lands in your team chat the evening before sprint planning. A teammate asks whether the team should depend on it. The only signal anyone has mentioned is its star count.&lt;/p&gt;

&lt;p&gt;You have no inside knowledge and no time for a full audit. What can you bring to the meeting that is more useful than another opinion?&lt;/p&gt;

&lt;p&gt;GitHub's own records can give you a compact evidence sheet. The point is not to manufacture a score. It is to replace one attention number with six observations whose limits are visible.&lt;/p&gt;

&lt;h2&gt;Attention is not an adoption decision&lt;/h2&gt;

&lt;p&gt;A star count cannot tell you what depending on a repository will be like. Neither can any single replacement metric. Commit activity can be shallow or imported. Issue totals can hide very different conversations. A license label can describe one detected file without settling the status of every file in the tree.&lt;/p&gt;

&lt;p&gt;So keep the evidence separate from the decision. Reports come from the worker. Verdicts come from a judge.&lt;/p&gt;

&lt;p&gt;In this case, your team is the judge. Your constraints (where the dependency would sit, how costly replacement would be, what licensing review you require, and how much uncertainty you can absorb) determine the verdict.&lt;/p&gt;

&lt;h2&gt;The six checks&lt;/h2&gt;

&lt;h3&gt;1. History depth&lt;/h3&gt;

&lt;p&gt;When does the visible history begin, and what shape does it have? Start with the repository creation date. Then inspect the newest and oldest entries in the default branch's commit listing.&lt;/p&gt;

&lt;p&gt;If you request one commit per page, the pagination header can show how many pages GitHub enumerated at that moment. Treat that number as a snapshot, not a permanent total. History can change, and a reachable branch does not prove that no other roots or orphaned history exist.&lt;/p&gt;

&lt;h3&gt;2. Issue and pull request evidence&lt;/h3&gt;

&lt;p&gt;Issue and pull request totals are inventory. They do not explain what the discussions contain, how reviews work, or what a typical response looks like.&lt;/p&gt;

&lt;p&gt;Could you open at least one merged pull request and read it end to end? Note who submitted it, the number of commits, the files touched, the recorded review comments, and the open-to-merge timestamps. One example still cannot establish a pattern, but it gives the totals concrete context.&lt;/p&gt;

&lt;h3&gt;3. Release record&lt;/h3&gt;

&lt;p&gt;List the releases and record exactly what GitHub returns: versions, dates, and draft or prerelease status. Resist the urge to turn the list into a claim about stability or discipline. A release record proves that releases are recorded. Compatibility and support require different evidence.&lt;/p&gt;

&lt;h3&gt;4. License classification and scope&lt;/h3&gt;

&lt;p&gt;GitHub's license endpoint can return an SPDX classification, a path, and a blob SHA for the detected license file. That is useful: it identifies the file your team needs to inspect and gives you a specific object to recheck.&lt;/p&gt;

&lt;p&gt;It does not prove that every file in the repository is covered by the same terms. It is not legal advice. If licensing matters to your use, this check defines the next question instead of pretending to answer it.&lt;/p&gt;

&lt;h3&gt;5. Contributor-attribution concentration&lt;/h3&gt;

&lt;p&gt;The contributors endpoint attributes contributions to accounts. Add the returned contributions, then calculate the share held by the top account and top few accounts.&lt;/p&gt;

&lt;p&gt;Write the result without turning it into a resilience claim. Accounts are not necessarily unique people, anonymous work may be missing, and an attribution snapshot cannot tell you who can maintain the project tomorrow.&lt;/p&gt;

&lt;h3&gt;6. Initial-commit provenance clues&lt;/h3&gt;

&lt;p&gt;Open the oldest listed commit and read the object itself. How many parents does it have? How large are the recorded additions and deletions? Does GitHub mark its signature as verified? Does the commit message contain trailers?&lt;/p&gt;

&lt;p&gt;These are provenance clues. Record them precisely. On their own, they do not prove authorship, misconduct, or code quality.&lt;/p&gt;

&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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-assess-new-github-repo-v2.svg" 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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-assess-new-github-repo-v2.svg" alt="Animated diagram of a repository health check: a commit graph is scanned while six verification checks tick into place" width="1000" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Worked example: THU-MAIC/OpenMAIC&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Checked at: September 2, 2026.&lt;/strong&gt; The following is a point-in-time reading of public GitHub sources, not an adoption recommendation.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://api.github.com/repos/THU-MAIC/OpenMAIC" rel="noopener noreferrer"&gt;repository metadata&lt;/a&gt; reported that OpenMAIC was created on 2026-03-11, uses &lt;code&gt;main&lt;/code&gt; as its default branch, was not archived, was not a fork, and had a &lt;code&gt;pushed_at&lt;/code&gt; date of 2026-09-02. Its &lt;a href="https://github.com/THU-MAIC/OpenMAIC/blob/main/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt; describes an immersive multi-agent learning and course-creation platform and includes a v1.0.0 section. That is the project's documentation describing itself; it does not verify what the software can do.&lt;/p&gt;

&lt;p&gt;For history depth, the &lt;a href="https://api.github.com/repos/THU-MAIC/OpenMAIC/commits?per_page=1" rel="noopener noreferrer"&gt;commit listing at one item per page&lt;/a&gt; linked to page 503 as its last page and returned &lt;code&gt;f760f58&lt;/code&gt; as the newest commit, dated 2026-09-02, with message &lt;code&gt;feat(render-service): machine-readable admission state (#1351)&lt;/code&gt;. The &lt;a href="https://api.github.com/repos/THU-MAIC/OpenMAIC/commits?per_page=1&amp;amp;page=503" rel="noopener noreferrer"&gt;last linked page&lt;/a&gt; returned &lt;code&gt;0d20abf&lt;/code&gt;, dated 2026-03-12, as the oldest listed commit in that snapshot. This is evidence of the visible history GitHub enumerated then, not a forever-exact commit count.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://api.github.com/search/issues?q=repo%3ATHU-MAIC%2FOpenMAIC%20is%3Aissue" rel="noopener noreferrer"&gt;issue search&lt;/a&gt; returned 537 issues, while the &lt;a href="https://api.github.com/search/issues?q=repo%3ATHU-MAIC%2FOpenMAIC%20is%3Apr" rel="noopener noreferrer"&gt;pull request search&lt;/a&gt; returned 763 pull requests. Those volatile totals say how much inventory GitHub search reported; they do not say what the inventory means.&lt;/p&gt;

&lt;p&gt;To ground that inventory, &lt;a href="https://api.github.com/repos/THU-MAIC/OpenMAIC/pulls/1273" rel="noopener noreferrer"&gt;pull request 1273&lt;/a&gt; was contributor-authored and recorded 13 commits, 21 changed files, eight review comments, and two comments. It was opened on August 28 and merged on August 31. That is one sampled merged pull request. It cannot establish the repository's typical response time or review process.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://api.github.com/repos/THU-MAIC/OpenMAIC/releases/latest" rel="noopener noreferrer"&gt;latest-release endpoint&lt;/a&gt; returned v1.0.0, published on 2026-08-27, with both draft and prerelease set to false. The &lt;a href="https://api.github.com/repos/THU-MAIC/OpenMAIC/releases" rel="noopener noreferrer"&gt;returned releases list&lt;/a&gt; contained nine non-draft, non-prerelease releases, from v0.1.0 on 2026-03-26 through v1.0.0. That is the release record visible at the check time, nothing more.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://api.github.com/repos/THU-MAIC/OpenMAIC/license" rel="noopener noreferrer"&gt;license endpoint&lt;/a&gt; classified the detected &lt;code&gt;LICENSE&lt;/code&gt; file as MIT and returned its blob SHA. The classification does not establish license coverage for every file, and this article does not provide legal advice.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://api.github.com/repos/THU-MAIC/OpenMAIC/contributors?per_page=100" rel="noopener noreferrer"&gt;contributors endpoint&lt;/a&gt; returned 62 accounts and 481 attributed contributions. The top account held 50.9% of that returned total; the top three held 72.3%. Those percentages describe concentration in that API snapshot. They do not count unique humans or establish future maintainability.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;a href="https://api.github.com/repos/THU-MAIC/OpenMAIC/commits/0d20abfed9602db14323225482ae0cd505311604" rel="noopener noreferrer"&gt;oldest listed commit object&lt;/a&gt; showed zero parents, 125,393 additions, zero deletions, a signature status of &lt;code&gt;verified: false&lt;/code&gt;, and a message trailer naming Claude Opus 4.6 as a co-author. Those facts describe the object GitHub returned. They do not tell you who wrote which code, whether anything improper happened, or whether the code fits your needs.&lt;/p&gt;

&lt;p&gt;Ranex has no disclosed affiliation with OpenMAIC.&lt;/p&gt;

&lt;h2&gt;What do you take into sprint planning?&lt;/h2&gt;

&lt;p&gt;You now have six bounded observations instead of a single attention signal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the visible history and its dates;&lt;/li&gt;
&lt;li&gt;issue and pull request inventory plus one inspected example;&lt;/li&gt;
&lt;li&gt;the release record as listed;&lt;/li&gt;
&lt;li&gt;the detected license classification and its limits;&lt;/li&gt;
&lt;li&gt;contributor-attribution concentration at one point in time; and&lt;/li&gt;
&lt;li&gt;initial-commit provenance clues.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Could you run those checks before the sprint commitment, then decide against your own constraints? If an AI assistant supplied any repository facts along the way, close that loop too: here is &lt;a href="/blog/oss-signal-prove-ai-source"&gt;how to verify which source your AI cited&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;How do I assess a new GitHub repository before adopting it?&lt;/h3&gt;

&lt;p&gt;Replace the star count with six recorded observations: history depth, issue and pull request evidence, the release record, license classification and scope, contributor-attribution concentration, and root-commit provenance clues.&lt;/p&gt;

&lt;h3&gt;Why aren't issue and PR counts enough on their own?&lt;/h3&gt;

&lt;p&gt;Counts are inventory, not evidence. Open at least one merged pull request and inspect its commits, changed files, review comments, and timeline. One sample cannot establish a pattern, but it gives the totals concrete context.&lt;/p&gt;

&lt;h3&gt;What does the license check actually tell you?&lt;/h3&gt;

&lt;p&gt;GitHub's license endpoint classifies a detected license file and returns its path and blob SHA. It does not confirm that every file is covered, and it is not legal advice.&lt;/p&gt;

&lt;h3&gt;What are initial-commit provenance clues?&lt;/h3&gt;

&lt;p&gt;They are facts read from the root commit, such as its parents, additions and deletions, signature status, and message trailers. Alone, they support no conclusion about authorship, misconduct, or code quality.&lt;/p&gt;

&lt;p&gt;Research and drafting were AI-assisted. Human review is required before publication.&lt;/p&gt;

</description>
      <category>github</category>
      <category>opensource</category>
      <category>repositoryassessment</category>
      <category>softwareduediligence</category>
    </item>
    <item>
      <title>An Open-Source Alternative to Notion: Three Candidates Compared</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:05:01 +0000</pubDate>
      <link>https://dev.to/anthonygarces/an-open-source-alternative-to-notion-three-candidates-compared-jic</link>
      <guid>https://dev.to/anthonygarces/an-open-source-alternative-to-notion-three-candidates-compared-jic</guid>
      <description>&lt;p&gt;The renewal notice arrives on a Tuesday. You are the team lead, so you open the pricing page and do the multiplication one more time. &lt;a href="https://www.notion.so/pricing" rel="noopener noreferrer"&gt;Notion lists Plus at $10 per member per month and Business at $20&lt;/a&gt;. Twelve people, a few contractors, one workspace. The number you get is not catastrophic. It is just a number that grows every time someone joins.&lt;/p&gt;

&lt;p&gt;You check the guest option first. &lt;a href="https://www.notion.so/pricing" rel="noopener noreferrer"&gt;Guests are free, but they can only access individual pages&lt;/a&gt;, so the contractors who live in three spaces still need seats. That is the moment the search usually starts: "notion open source alternative". This post is for that moment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR.&lt;/strong&gt;&lt;/p&gt;


&lt;p&gt;Three self-hostable candidates checked against primary sources on September 2, 2026: AFFiNE, Docmost, and SiYuan. No winner is declared. You get a five-point checklist, a comparison table where every cell traces to a repo or official doc, and one next action to take this week.&lt;/p&gt;

&lt;h2&gt;What "alternative" actually has to match&lt;/h2&gt;

&lt;p&gt;An alternative does not have to match Notion's feature page. It has to match your workflows: the meeting notes, the wiki, the database your team actually opens every day. Before you compare candidates, it is worth knowing exactly how portable your current content is, from Notion's own documentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.notion.com/help/export-your-content" rel="noopener noreferrer"&gt;Workspace export covers HTML, Markdown, and CSV for databases&lt;/a&gt;, plus your uploaded files. That is the good news. The fine print matters more. Notion states plainly that &lt;a href="https://www.notion.com/help/export-your-content" rel="noopener noreferrer"&gt;you can't instantly recreate your workspace by reuploading exported content&lt;/a&gt;. &lt;a href="https://www.notion.com/help/export-your-content" rel="noopener noreferrer"&gt;Download links expire after 7 days&lt;/a&gt;, and exports can take up to 30 hours to process. On Enterprise plans, &lt;a href="https://www.notion.com/help/export-your-content" rel="noopener noreferrer"&gt;workspace or teamspace owners can disable member exporting entirely&lt;/a&gt;. And &lt;a href="https://www.notion.com/help/back-up-your-data" rel="noopener noreferrer"&gt;workspace PDF export is being removed, with rollout through 31 August 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;None of this is a complaint. It is documented behavior, and it sets the bar: any candidate you evaluate should export your data in a form you can re-import elsewhere, on your schedule.&lt;/p&gt;

&lt;p&gt;Facts below checked against primary sources on September 2, 2026.&lt;/p&gt;

&lt;h2&gt;A checklist that works for any candidate&lt;/h2&gt;

&lt;p&gt;Run these five checks before you fall in love with a demo. They apply to any self-hosted tool, the three below included.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;License.&lt;/strong&gt; Read the LICENSE file, not the badge. Monorepos sometimes split licenses by directory, and "core" licenses can hide enterprise-gated features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment model.&lt;/strong&gt; Find the official install path. Docker Compose, Kubernetes, single binary. Note every dependency it drags in: databases, caches, proxies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data export.&lt;/strong&gt; What formats come out, and can a restore get your content back? A backup that only restores one component is not a full exit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project activity.&lt;/strong&gt; Check the releases page and issue tracker. Look at dates, not stars. Recent, steady releases beat a big number that stopped moving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance burden.&lt;/strong&gt; Who upgrades it, backs it up, and watches it at 2 a.m.? If the answer is "nobody yet", that is your answer.&lt;/li&gt;
&lt;/ol&gt;

&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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-open-source-knowledge-workspace-alternative-v2.svg" 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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-open-source-knowledge-workspace-alternative-v2.svg" alt="Animated comparison of three open-source candidates: feature bars fill across three equal cards under a settling balance beam" width="1000" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;The proof: three candidates, same five checks&lt;/h2&gt;

&lt;p&gt;I applied the checklist to three candidates on September 2, 2026. Sources: the &lt;a href="https://github.com/toeverything/AFFiNE" rel="noopener noreferrer"&gt;AFFiNE repository&lt;/a&gt; and &lt;a href="https://docs.affine.pro/self-host-affine/" rel="noopener noreferrer"&gt;self-host docs&lt;/a&gt;, the &lt;a href="https://github.com/docmost/docmost" rel="noopener noreferrer"&gt;Docmost repository&lt;/a&gt; and &lt;a href="https://docmost.com/docs/installation" rel="noopener noreferrer"&gt;installation docs&lt;/a&gt;, and the &lt;a href="https://github.com/siyuan-note/siyuan" rel="noopener noreferrer"&gt;SiYuan repository&lt;/a&gt; with its &lt;a href="https://github.com/siyuan-note/siyuan/releases" rel="noopener noreferrer"&gt;releases&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AFFiNE&lt;/th&gt;
&lt;th&gt;Docmost&lt;/th&gt;
&lt;th&gt;SiYuan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CE described as MIT, but &lt;a href="https://raw.githubusercontent.com/toeverything/AFFiNE/canary/LICENSE" rel="noopener noreferrer"&gt;split LICENSE&lt;/a&gt;: backend carries a separate license; &lt;a href="https://raw.githubusercontent.com/toeverything/AFFiNE/canary/packages/backend/server/LICENSE" rel="noopener noreferrer"&gt;EE needs a subscription for production&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/docmost/docmost" rel="noopener noreferrer"&gt;AGPL-3.0 core&lt;/a&gt;; enterprise features in ee directories under an enterprise license&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://raw.githubusercontent.com/siyuan-note/siyuan/master/LICENSE" rel="noopener noreferrer"&gt;AGPL v3&lt;/a&gt;; most features free for commercial use, some paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deploy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.affine.pro/self-host-affine/" rel="noopener noreferrer"&gt;Docker Compose recommended&lt;/a&gt;; Postgres only, Redis required; &lt;a href="https://docs.affine.pro/self-host-affine/install/requirements" rel="noopener noreferrer"&gt;4 cores, 2 GB RAM minimum&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docmost.com/docs/installation" rel="noopener noreferrer"&gt;Docker Compose recommended&lt;/a&gt;; Postgres 18, Redis 8, APP_SECRET of 32+ characters; claims air-gapped operation&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/siyuan-note/siyuan" rel="noopener noreferrer"&gt;Official Docker image b3log/siyuan on port 6806&lt;/a&gt;, workspace bind mount, accessAuthCode required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data export&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.affine.pro/self-host-affine/administer/backup-and-restore" rel="noopener noreferrer"&gt;Official backup is pg_dump plus blobs and config; database-only restore loses uploaded files&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docmost.com/docs/user-guide/import-export" rel="noopener noreferrer"&gt;Markdown or HTML ZIP export; Notion ZIP import; Confluence, PDF, DOCX import are Enterprise&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Markdown with assets, PDF, Word, HTML; .sy JSON on disk. But &lt;a href="https://github.com/siyuan-note/siyuan" rel="noopener noreferrer"&gt;Docker hosting cannot export PDF, HTML, or Word&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latest release&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/toeverything/AFFiNE/releases" rel="noopener noreferrer"&gt;v0.27.4, 18 Aug 2026&lt;/a&gt;; 72.1k stars, 614 issues&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/docmost/docmost/releases" rel="noopener noreferrer"&gt;v0.95.0, 3 Jul 2026&lt;/a&gt;, security fixes; 21.6k stars, 223 issues&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/siyuan-note/siyuan/releases" rel="noopener noreferrer"&gt;v3.8.2, 30 Aug 2026&lt;/a&gt;; 46.1k stars, 79 open issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-host burden&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Postgres, Redis, volumes, config; &lt;a href="https://docs.affine.pro/self-host-affine/install/configuration" rel="noopener noreferrer"&gt;0.27.4 changed Compose paths and dropped .env&lt;/a&gt;, so guides rot fast&lt;/td&gt;
&lt;td&gt;Postgres, Redis, &lt;a href="https://docmost.com/docs/" rel="noopener noreferrer"&gt;WebSockets at the proxy for the realtime editor&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Lightest of the three; but auth is a lock-screen access code and it is &lt;a href="https://github.com/siyuan-note/siyuan" rel="noopener noreferrer"&gt;positioned as personal-first&lt;/a&gt;, not a multi-tenant workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notion stays as the context row in your head: whatever you pick, the migration path out of it is the export behavior described above.&lt;/p&gt;

&lt;h3&gt;Why the usual suspects are missing&lt;/h3&gt;

&lt;p&gt;You will notice some famous names absent. That is the checklist working, not an oversight.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AppFlowy-IO/AppFlowy-Cloud" rel="noopener noreferrer"&gt;AppFlowy-Cloud was archived on 1 September 2026&lt;/a&gt;. The README calls it legacy and unmaintained, and says production self-hosting is a closed-source commercial fork with a free tier of one user seat. &lt;a href="https://raw.githubusercontent.com/outline/outline/main/LICENSE" rel="noopener noreferrer"&gt;Outline's LICENSE is Business Source License 1.1&lt;/a&gt;, which states in its own text that it is not an Open Source license (its change date to Apache-2.0 is 2030-09-01). &lt;a href="https://github.com/requarks/wiki/releases" rel="noopener noreferrer"&gt;Wiki.js 3.0.0-beta.537 is explicitly marked not for production use&lt;/a&gt;; v2.5.314 is the production latest.&lt;/p&gt;

&lt;h2&gt;One action you can take today&lt;/h2&gt;

&lt;p&gt;Do not migrate anything yet. Pick one real workflow, something small and loud, like the weekly meeting-notes database. Pilot one candidate against exactly that for one week. You will learn more about export paths, auth, and upgrade pain in seven days of real use than in any feature table.&lt;/p&gt;

&lt;p&gt;When you are done, take the checklist back to your shortlist. More field notes live at &lt;a href="/open-source"&gt;/open-source&lt;/a&gt;, and the rest of the series is on the &lt;a href="/blog"&gt;blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Reports come from the worker. Verdicts come from a judge. This post is the worker's report: every cell above links to a primary source you can open yourself.&lt;/p&gt;

&lt;h2&gt;Caveats&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Feature tables rot fast. The checked-at date of September 2, 2026 bounds every claim here, including versions and star counts. Re-verify before you rely on any of them.&lt;/li&gt;
&lt;li&gt;License nuance: Docmost and SiYuan are AGPL-3.0, which carries network-copyleft implications a company should run past counsel. This is not legal advice. AFFiNE CE is described as MIT, but the backend carries a separate license.&lt;/li&gt;
&lt;li&gt;No candidate here is "the winner". Fit depends on your constraints: team size, compliance, who is on call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Disclosures&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;First-party interest: ranex.dev is my site, and this series covers the open-source ecosystem I work in.&lt;/li&gt;
&lt;li&gt;AI assistance was used in the research and drafting of this post.&lt;/li&gt;
&lt;li&gt;Human review is required. This post is scheduled for review before publication.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;Is AFFiNE really MIT licensed?&lt;/h3&gt;

&lt;p&gt;Mostly, for the Community Edition. AFFiNE CE is described as MIT and free for self-hosting, but the repository LICENSE is split: packages/backend and packages/common/native carry a separate license, and the EE server code requires a valid Enterprise Edition subscription for production use. Read the LICENSE files yourself before committing.&lt;/p&gt;

&lt;h3&gt;Which candidate is closest to Notion?&lt;/h3&gt;

&lt;p&gt;None is a full replacement. Docmost is the most team-oriented of the three, but its Bases feature (Table and Kanban) is Enterprise-only as of v0.95.0. AFFiNE's official backup procedure is a Postgres pg_dump plus blobs rather than a document export. SiYuan describes itself as a privacy-first personal knowledge system, and its official Docker hosting cannot export PDF, HTML, or Word.&lt;/p&gt;

&lt;h3&gt;What about AppFlowy?&lt;/h3&gt;

&lt;p&gt;The AppFlowy-Cloud repository was archived on 1 September 2026. Its README calls it legacy and unmaintained, and says production self-hosting is now a closed-source commercial fork with a free tier of one user seat. That is why it is not in the table.&lt;/p&gt;

&lt;h3&gt;Will my Notion content migrate cleanly?&lt;/h3&gt;

&lt;p&gt;Partly. Non-database pages export as Markdown and full-page databases as CSV, with callouts becoming HTML. But you cannot export all database views at once, Form views cannot be exported, and Notion states you cannot instantly recreate a workspace by re-uploading exported content. Databases, views, and permissions will not round-trip.&lt;/p&gt;

</description>
      <category>affine</category>
      <category>docmost</category>
      <category>notion</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Cause Is Structure, Not Prose</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:04:13 +0000</pubDate>
      <link>https://dev.to/anthonygarces/cause-is-structure-not-prose-5b1m</link>
      <guid>https://dev.to/anthonygarces/cause-is-structure-not-prose-5b1m</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; A failure message should explain a cause that already exists as data, not force the next system or person to guess the cause from a sentence. The &lt;a href="https://ranex.dev/blog/building-ranex-slice-log" rel="noopener noreferrer"&gt;Ranex slice log&lt;/a&gt; keeps the evidence behind those distinctions.&lt;/p&gt;

&lt;p&gt;You open a failed check and get one sentence: “this claim is not satisfied.” You still have to ask the question that matters. Was the work never done, was the evidence for another revision, was it forged, or did the checker refuse to admit it?&lt;/p&gt;

&lt;p&gt;A sentence can be useful to read. It is a poor place to hide the only category that tells you what to do next.&lt;/p&gt;

&lt;h2 id="in-this-note"&gt;In this note&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A sentence cannot be the interface&lt;/li&gt;
&lt;li&gt;The cause must survive the boundary&lt;/li&gt;
&lt;li&gt;Unknown is not the nearest known cause&lt;/li&gt;
&lt;li&gt;What to keep structured&lt;/li&gt;
&lt;li&gt;Why ranking does not fix it&lt;/li&gt;
&lt;li&gt;Where Ranex stands&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="a-sentence-cannot-be-the-interface"&gt;A sentence cannot be the interface&lt;/h2&gt;

&lt;p&gt;A cause must survive as structured data because different failures demand different responses. “Absent” is not another spelling of “forged,” and neither is another spelling of “stale.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/anthonykewl20/ranex/blob/main/docs/adr/ADR-020-cause-is-structure-not-prose.md" rel="noopener noreferrer"&gt;ADR-020&lt;/a&gt; records the specific Ranex failure. The kernel’s &lt;code&gt;_diagnosis()&lt;/code&gt; already partitioned unsatisfied claims into five kinds: contradicted, failed, mismatched, stale, and absent. The admission layer added refused and unattributable. Seven events could reach a caller as the same broad outcome: a claim was not satisfied.&lt;/p&gt;

&lt;p&gt;Then the old path discarded the distinction. &lt;code&gt;_diagnosis()&lt;/code&gt; joined the kernel buckets into English, &lt;code&gt;Evaluation&lt;/code&gt; exposed claim IDs without their cause, and the CLI recomputed then discarded the admission results. A later renderer had two bad choices: parse prose or invent a category it had not received.&lt;/p&gt;

&lt;p&gt;This is not a copywriting complaint. ADR-020 ties prose parsing to a defect that reopened SLICE-002: a forgery could be reported using wording reserved for honest absence. If the words are the interface, an attacker who changes a field can influence the story the operator sees.&lt;/p&gt;

&lt;h2 id="the-cause-must-survive-the-boundary"&gt;The cause must survive the boundary&lt;/h2&gt;

&lt;p&gt;Ranex’s decision is to compute one partition, return it as data, and render the human sentence from that same partition. The structure and the prose then have one source.&lt;/p&gt;

&lt;p&gt;The five kernel claim causes remain at the evaluation boundary. The two admission causes do not enter &lt;code&gt;Evaluation&lt;/code&gt;, because pure &lt;code&gt;evaluate()&lt;/code&gt; cannot see admission rejections; the projection composes them once. Self-approval is also separate: it is an evaluation-level refusal marker, not a claim cause, and it must render even when &lt;code&gt;missing_claims&lt;/code&gt; is empty.&lt;/p&gt;

&lt;p&gt;That placement protects the kernel’s purity. &lt;code&gt;evaluate()&lt;/code&gt; remains a pure function of gate, evidence, subject, and approver. It does not learn about UI wording or admission state merely because a screen needs to explain a result.&lt;/p&gt;

&lt;p&gt;The design also preserves compatibility where it earns it. &lt;code&gt;reason&lt;/code&gt; must remain byte-identical for existing inputs, because people read it and the journal records it. The structured field is additive, but adding it changes the evaluation record digest for new evaluations. Old journal rows keep their own digests; comparing digests across that declared boundary is not valid.&lt;/p&gt;

&lt;h2 id="unknown-is-not-the-nearest-known-cause"&gt;Unknown is not the nearest known cause&lt;/h2&gt;

&lt;p&gt;An unknown cause must block and render as unclassified. It must not be rounded into the closest familiar category.&lt;/p&gt;

&lt;p&gt;ADR-020 treats the causes as unordered. There is no severity ranking that lets a renderer keep one cause and erase the others. A claim that is contradicted and missing is named once under contradiction; suite detail belongs on a failed cause, not in a new category; and a nullable admission &lt;code&gt;claim_id&lt;/code&gt; stays null rather than being coerced into honest absence.&lt;/p&gt;

&lt;p&gt;That discipline also gives the reader an honest response to future change. The wire accepts an unknown tag, the reader shows &lt;code&gt;unclassified&lt;/code&gt;, and the result still blocks. The renderer does not guess. It shows the operator that the system has encountered something it cannot yet name safely.&lt;/p&gt;

&lt;p&gt;One exit code cannot offer that honesty. A successful process can conceal skipped, missing, or unexamined work; a failed process can represent many distinct causes. &lt;a href="https://ranex.dev/blog/a-skip-is-not-a-pass" rel="noopener noreferrer"&gt;A skip is not a pass&lt;/a&gt; follows the related move from exit codes to structured test outcomes.&lt;/p&gt;

&lt;h2 id="what-to-keep-structured"&gt;What to keep structured&lt;/h2&gt;

&lt;p&gt;You do not need a governance kernel to apply this rule. Any boundary that turns a machine result into a person-facing explanation needs a stable category before it needs polished wording.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Define the closed causes.&lt;/strong&gt; Name the states your consumer has to handle rather than relying on message fragments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach the cause where it is discovered.&lt;/strong&gt; Do not calculate it again in an API, CLI, dashboard, or report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render prose from the data.&lt;/strong&gt; Keep human language useful without making it the protocol.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the mapping total.&lt;/strong&gt; Test every known cause and reject a default arm that silently swallows a new one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserve unknowns.&lt;/strong&gt; Block, disclose, and investigate them instead of assigning the nearest familiar label.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep distinct layers distinct.&lt;/strong&gt; A claim failure, an admission rejection, and self-approval can be related without becoming one bucket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test old wording deliberately.&lt;/strong&gt; If compatibility matters, assert the existing sentence rather than trusting an incidental refactor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="why-ranking-does-not-fix-it"&gt;Why ranking does not fix it&lt;/h2&gt;

&lt;p&gt;Ranking causes does not preserve them. It chooses a winner and throws information away.&lt;/p&gt;

&lt;p&gt;ADR-020 rejects a severity rank over the seven causes. It notes Knative’s approach of ranking many causes into one and discarding what falls below the selected severity. That can be a useful presentation choice in another system, but it is the wrong data model when each cause tells an operator a different next action.&lt;/p&gt;

&lt;p&gt;A renderer also needs exhaustive handling. ADR-020 cites typed string states that downstream code handled inconsistently because a switch’s default arm did not report an unfamiliar value. The corrective move is not to add more prose. It is to validate the closed set at deserialization so no renderer receives a known value it cannot handle.&lt;/p&gt;

&lt;h2 id="where-ranex-stands"&gt;Where Ranex stands&lt;/h2&gt;

&lt;p&gt;Ranex is pre-release, and the status must be read carefully. ADR-020 is accepted and describes the kernel decision. The README says SLICE-020 closed structured five-kind evaluation causes and self-approval, and its projection composes refused and unattributable rejections.&lt;/p&gt;

&lt;p&gt;That does not make every interface or future board feature complete. The ADR leaves presentation, colour, glyphs, layout, and new causes out of scope. What it establishes is narrower and more useful: the cause should reach the consumer as structure, while the sentence remains a readable rendering of that structure.&lt;/p&gt;

&lt;h2 id="questions-people-actually-ask"&gt;Questions people actually ask&lt;/h2&gt;

&lt;h3&gt;What does cause is structure, not prose mean?&lt;/h3&gt;

&lt;p&gt;ADR-020 requires Ranex to compute a per-claim cause as structured data and render reason from that same partition, rather than asking a later consumer to recover the cause by parsing English.&lt;/p&gt;

&lt;h3&gt;What failure causes does Ranex distinguish?&lt;/h3&gt;

&lt;p&gt;ADR-020 identifies five kernel claim causes (contradicted, failed, mismatched, stale, and absent) and two admission-layer causes, refused and unattributable; self-approval remains a separate evaluation-level marker.&lt;/p&gt;

&lt;h3&gt;Why is parsing a failure message unsafe?&lt;/h3&gt;

&lt;p&gt;ADR-020 forbids parsing reason because changed wording can mislabel a forgery as honest absence; the machine-readable category must survive to the renderer.&lt;/p&gt;

&lt;h3&gt;Does Ranex ship structured causes today?&lt;/h3&gt;

&lt;p&gt;The Ranex README says SLICE-020 closed structured five-kind evaluation causes and self-approval, while its projection composes refused and unattributable rejections; Ranex remains pre-release.&lt;/p&gt;

&lt;p&gt;The next time a failure arrives as one tidy sentence, ask what structured state produced it and whether every consumer can handle that state. Try it. Break it. Tell me what broke.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this post was drafted with AI assistance. Every factual claim traces to the repository’s README or slice records, the same fact gate the product enforces on code. It ships only after Anthony’s own review.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>evidence</category>
      <category>gateverdicts</category>
      <category>kernel</category>
    </item>
    <item>
      <title>A Skipped Test Is Not a Passed Test</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:03:24 +0000</pubDate>
      <link>https://dev.to/anthonygarces/a-skipped-test-is-not-a-passed-test-5h3d</link>
      <guid>https://dev.to/anthonygarces/a-skipped-test-is-not-a-passed-test-5h3d</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Exit code zero does not prove required tests ran; compare structured outcomes against a frozen manifest and fail on missing or undeclared results. The &lt;a href="https://ranex.dev/blog/building-ranex-slice-log" rel="noopener noreferrer"&gt;parent slice log&lt;/a&gt; documents why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your test command exited zero. Which required tests actually ran?&lt;/strong&gt; If your answer is “the job was green,” you have a hole in the gate. Exit-code satisfaction allowed a skipped test, or a vanished one, to read as success.&lt;/p&gt;

&lt;h2 id="in-this-note"&gt;In this note&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Zero exited cleanly while the suite got smaller&lt;/li&gt;
&lt;li&gt;Judge outcomes against a frozen manifest&lt;/li&gt;
&lt;li&gt;Go hunt for disappearance in your pipeline&lt;/li&gt;
&lt;li&gt;What the proof covers and where it stops&lt;/li&gt;
&lt;li&gt;Make your next green run account for every test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The measured failure was not theoretical. &lt;a href="https://github.com/anthonykewl20/ranex/blob/main/docs/slices/done/SLICE-009-a-skip-is-absence.md" rel="noopener noreferrer"&gt;Twenty-seven tests were destroyed while the remainder stayed green.&lt;/a&gt; The gate had no way to distinguish that partial absence from a healthy run.&lt;/p&gt;

&lt;p&gt;A passing exit code is a promise about process completion. It is not a complete account of the test outcomes you needed.&lt;/p&gt;

&lt;h2 id="zero-exited-cleanly-while-the-suite-got-smaller"&gt;Zero exited cleanly while the suite got smaller&lt;/h2&gt;

&lt;p&gt;Before SLICE-009, Ranex accepted &lt;code&gt;exit_code == 0&lt;/code&gt; and nothing else for its &lt;code&gt;tests-executed&lt;/code&gt; claim. A test that asserted &lt;code&gt;False&lt;/code&gt;, then received a skip marker, reported one skipped test, exited zero, and satisfied the claim.&lt;/p&gt;

&lt;p&gt;Pytest refuses a suite with total absence through exit code 5. Partial absence is different. A suite can skip itself into silence or shrink without changing its exit code.&lt;/p&gt;

&lt;p&gt;That exact shape had already happened: two agents in one worktree destroyed 27 tests, and the remainder stayed green. The problem was not that pytest lied. It returned the process status it was designed to return. The gate asked that status to prove more than it could prove.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An exit code is a promise about nothing your gate has defined.&lt;/strong&gt; Until you define the required outcomes, zero only tells you that the command chose zero.&lt;/p&gt;

&lt;p&gt;This is broader than agents. A changed test selection, a conditional skip, an environment difference, or a deleted file can all turn your familiar green badge into a smaller test run. A test that runs and sometimes fails is a different disease; &lt;a href="https://ranex.dev/blog/flaky-suite-approved-twice" rel="noopener noreferrer"&gt;that one is worth a read too&lt;/a&gt;. If the only evidence you retain is a status code, you cannot compare what ran with what should have run.&lt;/p&gt;

&lt;h2 id="judge-outcomes-against-a-frozen-manifest"&gt;Judge outcomes against a frozen manifest&lt;/h2&gt;

&lt;p&gt;The fix is to bind structured test outcomes into the evidence and compare them against a manifest frozen from the suite. SLICE-009 binds &lt;code&gt;junitxml&lt;/code&gt; into the digest-bound command argv, then signs structured outcomes as evidence v3.&lt;/p&gt;

&lt;p&gt;The signed summary includes outcome counts, sorted non-passed IDs with their kinds, and a full-outcome digest. The artifact is read from the hermetic sample before teardown. An older evidence v2 row is refused loudly rather than treated as a skip.&lt;/p&gt;

&lt;p&gt;The manifest is generated by a freeze ceremony from a hermetic run’s junitxml and committed beside the gate catalog. It is outcome-blind: it records the expected test IDs, not a convenient claim that every test passed. Expected skips are declared by ID with a reason.&lt;/p&gt;

&lt;p&gt;Then the manifest diff becomes the rule.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every manifest ID must appear as passed.&lt;/li&gt;
&lt;li&gt;A declared expected-skip may skip or pass.&lt;/li&gt;
&lt;li&gt;An undeclared skip blocks.&lt;/li&gt;
&lt;li&gt;An xfail, xpass, error, or missing ID blocks.&lt;/li&gt;
&lt;li&gt;An absent, unparseable, oversized, or duplicate-ID artifact blocks.&lt;/li&gt;
&lt;li&gt;Extra IDs neither satisfy nor block the claim.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a parser trick. The slice requires a real pytest run to produce the artifact, then freezes and judges that same artifact through the same path. A handwritten XML string can exercise a parser while proving nothing about the pipeline that produced it.&lt;/p&gt;

&lt;p&gt;The manifest also comes from the dispatch-time base tree during delegated judging, never the candidate. A candidate that can edit the measuring stick cannot be expected to judge itself strictly.&lt;/p&gt;

&lt;h2 id="go-hunt-for-disappearance-in-your-pipeline"&gt;Go hunt for disappearance in your pipeline&lt;/h2&gt;

&lt;p&gt;You do not need to build a new kernel to test this idea. Start by asking whether your gate can name every required test and say what happened to each one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run a required test with a skip marker and see whether your release gate still approves the result.&lt;/li&gt;
&lt;li&gt;Delete a required test file in a disposable branch. Confirm the gate fails for the missing test, not only when the suite crashes.&lt;/li&gt;
&lt;li&gt;Add a selection rule that deselects a required test. Check whether the gate notices the missing ID.&lt;/li&gt;
&lt;li&gt;Collect a structured result artifact such as junitxml from the exact command your gate judges.&lt;/li&gt;
&lt;li&gt;Freeze the expected test IDs before the change being judged. Keep declared skips explicit and reasoned.&lt;/li&gt;
&lt;li&gt;Compare outcomes to that frozen list, including skips, xfails, xpasses, errors, and duplicate IDs.&lt;/li&gt;
&lt;li&gt;Keep the manifest outside the candidate’s authority when a delegated worker is being judged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A test that cannot be found is not a test that passed. That sounds obvious until a zero exit code waves it through.&lt;/p&gt;

&lt;h2 id="what-the-proof-covers-and-where-it-stops"&gt;What the proof covers and where it stops&lt;/h2&gt;

&lt;p&gt;SLICE-009 proves that an undeclared skip blocks, declared expected skips are explicit permission rather than an obligation, non-passing outcome kinds block, and a missing ID blocks. It also proves this against a deleted test file, the same class of disappearance as the 27-test incident.&lt;/p&gt;

&lt;p&gt;Ranex now gates its own repository using this rule. The README says the current gate judges signed structured outcomes against a manifest diff rather than exit code alone. It PASSes only against that manifest and flips to FAIL when a frozen test file is deleted.&lt;/p&gt;

&lt;p&gt;The close-out record gives the implementation’s then-current measurement: a 736-ID manifest, 67 declared expected skips, and 669 passed in the sealed sample — the manifest has grown since; &lt;code&gt;governance/suite_manifest.json&lt;/code&gt; currently carries 943 IDs and 113 expected skips. The source also states why the skipped tests exist: harness-fork, cold-start by design, dependency and provisioning conditions, one OpenRouter credential, and one mount namespace. Those skips were declared at freeze time with reasons. They were not silently accepted.&lt;/p&gt;

&lt;p&gt;There is a boundary here. A hostile tree can fabricate an all-pass artifact. The slice has a passing test that states this forgery boundary rather than hiding it. Structured outcomes improve what the exit code could establish; they do not make an untrusted producer truthful.&lt;/p&gt;

&lt;p&gt;That is why this belongs with &lt;a href="https://ranex.dev/blog/how-the-kernel-works" rel="noopener noreferrer"&gt;how the kernel works&lt;/a&gt;: a verdict is only as strong as the evidence and authority around it. Ranex is pre-release, and its README lists a working verdict path alongside gaps that remain designed rather than built. The source records are available in &lt;a href="https://github.com/anthonykewl20/ranex" rel="noopener noreferrer"&gt;the Ranex repository&lt;/a&gt; under &lt;code&gt;docs/slices/done/&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id="questions-people-actually-ask"&gt;Questions people actually ask&lt;/h2&gt;

&lt;p&gt;These questions help you inspect skipped and missing tests before a green status approves them.&lt;/p&gt;

&lt;h3&gt;Why is exit code zero not enough for a test gate?&lt;/h3&gt;

&lt;p&gt;A skipped or vanished test can leave pytest exiting zero, so exit-code satisfaction alone cannot show that required tests ran.&lt;/p&gt;

&lt;h3&gt;How can a test gate detect skipped or missing tests?&lt;/h3&gt;

&lt;p&gt;A gate can compare signed structured junitxml outcomes with an outcome-blind manifest frozen from the suite and block missing IDs.&lt;/p&gt;

&lt;h3&gt;What happens when a frozen test file is deleted?&lt;/h3&gt;

&lt;p&gt;Ranex’s own gate flips from PASS to FAIL and names the missing test ID.&lt;/p&gt;

&lt;h2 id="make-your-next-green-run-account-for-every-test"&gt;Make your next green run account for every test&lt;/h2&gt;

&lt;p&gt;Take one required test file and delete it in a disposable branch. If your gate stays green, stop treating that gate as proof that the suite ran. Bind a structured outcome artifact to the command, freeze the required IDs, and compare the result against that list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it. Break it. Tell me what broke.&lt;/strong&gt; Star &lt;a href="https://github.com/anthonykewl20/ranex" rel="noopener noreferrer"&gt;Ranex on GitHub&lt;/a&gt; if this gives you a sharper test gate, and send the honest critique. The missing test your pipeline catches next is the point.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this post was drafted with AI assistance. Every factual claim traces to the repository’s README or slice records, the same fact gate the product enforces on code. It ships only after Anthony’s own review.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>evidence</category>
      <category>gateverdicts</category>
      <category>testdesign</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Finding an Open-Source Project Worth Your First Contribution</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:02:20 +0000</pubDate>
      <link>https://dev.to/anthonygarces/finding-an-open-source-project-worth-your-first-contribution-14nn</link>
      <guid>https://dev.to/anthonygarces/finding-an-open-source-project-worth-your-first-contribution-14nn</guid>
      <description>&lt;p&gt;You find a repository with a "good first issue" label on something that looks approachable. You read the issue, read the linked file, write a careful patch, and open the PR. Then you wait.&lt;/p&gt;

&lt;p&gt;A week passes. You check the tab once a day, then once every few days, then you stop checking as often because checking and finding nothing feels worse than not checking at all. Four months later you notice the tab is still open. No comment, no review, no closure: just a PR sitting in a queue you can't see the size of. You close the tab yourself, quietly, and move on to something else.&lt;/p&gt;

&lt;p&gt;Nothing about that story is unusual. It's also nothing you could have known from the label, the star count, or the README. The label said "good first issue." It said nothing about who reads issues, how often, or whether an external PR has ever actually been merged there.&lt;/p&gt;

&lt;p&gt;So before you spend an evening on a patch: what could you have checked beforehand (in ten minutes, by hand, on the repo's own issue and PR history) that would have told you something closer to the truth?&lt;/p&gt;

&lt;h2&gt;Lesson&lt;/h2&gt;

&lt;p&gt;A label is a claim a project makes about itself. A merged PR is a thing that actually happened. Those are different kinds of evidence, and only one of them tells you what happens after you hit submit.&lt;/p&gt;

&lt;p&gt;"Good first issue" is metadata someone attached, possibly months ago, possibly by a maintainer who has since moved to other work. It doesn't tell you the median time between opening a PR and getting a human response. It doesn't tell you whether the last five PRs tagged that way were merged, ignored, or auto-closed by a stale-bot. In the worked example later in this post, the open "good first issue" search on a genuinely active project returned zero results at the moment I checked it, while real external contributions, unrelated to that label, were being reviewed and merged in under three weeks.&lt;/p&gt;

&lt;p&gt;That gap is the point. Labels are marketing, even when nobody intends them that way. They're a snapshot of intent, not a running log of behavior. Issue threads and merged pull requests are closer to a running log: timestamps, who commented, what they said, how long it took. Reading five of those by hand takes less time than writing the code for your first PR, and it tells you more about what happens after you submit it than any badge on the repository.&lt;/p&gt;

&lt;h2&gt;Checklist: five signals worth checking by hand&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Time-to-first-response, but separate bot from human.&lt;/strong&gt; Open the repo's most recent issues and note who leaves the first comment and how fast. Many projects run triage bots that reply within minutes with a template: label suggestions, a request for more info, a note about backlog policy. That speed is real, but it isn't review. Scroll past the bot comment and find the first comment from an actual maintainer account. That gap (sometimes minutes, sometimes days) is closer to what you'll experience once your PR is up. Check the commenter's role badge (MEMBER, CONTRIBUTOR, or nothing) if the platform shows one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. External-PR merge behavior.&lt;/strong&gt; Search merged pull requests filtered to something like an "external" label, or scan recent merged PRs and check whether the author is listed as a core team member. For a handful of them, note the opened date and the merged date. A consistent multi-week gap tells you more than a single fast example. A single fast example tells you almost nothing on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Maintainer activity and release cadence.&lt;/strong&gt; Look at the commit history on the main branch and the release/tag list. Are there commits in the last week? Do releases land on something like a monthly rhythm, or is the last tag from a year ago? Cadence is a proxy for whether anyone is actively steering the project right now, rather than whether it once was.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. CONTRIBUTING.md promises versus automation.&lt;/strong&gt; Read the actual contributing guide. It's often not at the repo root. Note what it asks for (an issue first? explicit maintainer sign-off before you write code?) and whether a bot enforces that ask, or whether it's just a suggestion. Then look for a case where a maintainer overrode the stated rule for something small, like a typo fix. That tells you whether the written policy is rigid or whether maintainers use judgment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Issue-triage patterns.&lt;/strong&gt; Look at labels on the newest issues. Do they get sorted into categories (bug, enhancement, priority) shortly after opening, or do they sit unlabeled? Systematic triage is a weak but real signal that someone is actively managing the queue, rather than occasionally glancing at it.&lt;/p&gt;

&lt;p&gt;None of these, alone, tells you what will happen to your specific PR. Together, they tell you more than a badge does.&lt;/p&gt;

&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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-find-active-project-to-contribute-v2.svg" 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%2Fcms.ranex.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Ffigure-oss-signal-find-active-project-to-contribute-v2.svg" alt="Animated contribution path: a change travels from a fork through review into a merged braid while an unmerged path fades" width="1000" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Proof&lt;/h2&gt;

&lt;p&gt;Checked at: September 2, 2026. A nine-issue and six-PR hand sample, drawn from the newest open items, so it is right-censored where responses had not arrived at fetch. This is a point-in-time reading, not an endorsement of any project named below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worked example: &lt;a href="https://github.com/cli/cli" rel="noopener noreferrer"&gt;cli/cli&lt;/a&gt;&lt;/strong&gt;, GitHub's official CLI ("gh is GitHub on the command line," per its README).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Bot vs. human.&lt;/em&gt; &lt;a href="https://github.com/cli/cli/issues/14317" rel="noopener noreferrer"&gt;Issue #14317&lt;/a&gt; was opened 2026-09-02 at 06:01 UTC; a bot commented at 06:05 (~4 minutes) suggesting labels. &lt;a href="https://github.com/cli/cli/issues/14308" rel="noopener noreferrer"&gt;Issue #14308&lt;/a&gt;, opened September 1, got a bot reply in about 3 minutes, and its backlog template states plainly that the team is not committing to implement the request and that external contributions aren't being sought without a "help wanted" tag. On the human side: &lt;a href="https://github.com/cli/cli/issues/14253" rel="noopener noreferrer"&gt;#14253&lt;/a&gt;, opened August 24, got its first maintainer (MEMBER) comment on August 28, about 3.9 days later, asking for a screenshot. &lt;a href="https://github.com/cli/cli/issues/14223" rel="noopener noreferrer"&gt;#14223&lt;/a&gt;, opened August 21, got its first MEMBER comment on August 28, about 6.7 days later. Across the &lt;a href="https://github.com/cli/cli/issues" rel="noopener noreferrer"&gt;nine newest open issues&lt;/a&gt; at the time of the check, bot-comment median latency was roughly 3.5 minutes; human maintainer comments appeared on only 2 of the 9.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;External merges.&lt;/em&gt; The "external" label marks PRs from outside the core CLI team; &lt;a href="https://github.com/cli/cli/pulls?q=is%3Apr+is%3Amerged+label%3Aexternal" rel="noopener noreferrer"&gt;920 merged PRs&lt;/a&gt; carry it. &lt;a href="https://github.com/cli/cli/pull/14154" rel="noopener noreferrer"&gt;#14154&lt;/a&gt; (opened by scarletkc) went from open on August 15 to merged September 1, about 17 days, credited as a first contribution in the &lt;a href="https://github.com/cli/cli/releases/tag/v2.99.0" rel="noopener noreferrer"&gt;v2.99.0&lt;/a&gt; release notes. &lt;a href="https://github.com/cli/cli/pull/13787" rel="noopener noreferrer"&gt;#13787&lt;/a&gt; (kofuk) ran roughly July 3 to July 16, about 13 days, with a review comment reading simply "Thanks LGTM." &lt;a href="https://github.com/cli/cli/pull/13886" rel="noopener noreferrer"&gt;#13886&lt;/a&gt; (kobihikri) ran July 15 to July 21, about 6 days.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Activity.&lt;/em&gt; v2.99.0 released 2026-09-01, following a roughly monthly cadence: v2.98.0 on August 20, v2.97.0 on July 31, v2.96.0 on July 2 (full &lt;a href="https://github.com/cli/cli/releases" rel="noopener noreferrer"&gt;release history&lt;/a&gt;). &lt;a href="https://github.com/cli/cli/commits/trunk" rel="noopener noreferrer"&gt;Trunk commits&lt;/a&gt; landed September 2, September 1, August 31, August 28, August 27, and August 26.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written policy vs. exceptions.&lt;/em&gt; The &lt;a href="https://github.com/cli/cli/blob/trunk/.github/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING guide&lt;/a&gt; says the team accepts PRs for issues tagged "help wanted," and asks contributors not to open PRs without that tag or explicit acceptance criteria. External PRs against core issues won't be accepted otherwise. Yet &lt;a href="https://github.com/cli/cli/pull/13940" rel="noopener noreferrer"&gt;#13940&lt;/a&gt;, a typo fix, got a bot warning about the missing help-wanted issue and a four-day auto-close notice, and maintainer williammartin approved and merged it the same day, July 22. The project's own &lt;a href="https://raw.githubusercontent.com/cli/cli/trunk/docs/triage.md" rel="noopener noreferrer"&gt;triage doc&lt;/a&gt; says tiny, obviously-mergeable fixes like typos mean review, test, and merge. Automation sets the default rule; a human made the exception.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Triage.&lt;/em&gt; The newest open issues carry labels like needs-triage, enhancement, bug, and priority-3 (#14317, #14308, #14297): a pattern, not a promise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contrast, no verdict.&lt;/strong&gt; &lt;a href="https://github.com/jqlang/jq/pulls?q=is%3Apr+is%3Aopen+sort%3Acreated-asc" rel="noopener noreferrer"&gt;jqlang/jq&lt;/a&gt; shows 117 open PRs, the oldest sorted by creation dating to #458 from 2014 and #1103 from February 2016. That is a dated fact, not a judgment. Years-open PRs can reflect real design disagreements rather than neglect, and this article draws no conclusion about jq either way. Its absence from the checklist above means nothing about the project.&lt;/p&gt;

&lt;p&gt;A few things worth saying plainly: the names here are illustrations, not recommendations. Past responsiveness is not a prediction of what will happen to your PR. cli/cli is staffed by GitHub employees, so its latencies are not representative of a volunteer-run project. Treat that difference as real, not a footnote. No project here is called friendly or unfriendly. Maintainers are people with variable time and energy, and the numbers above describe a queue, not a character trait.&lt;/p&gt;

&lt;h2&gt;Try it tonight&lt;/h2&gt;

&lt;p&gt;Before you write a line of code for a project you're considering, spend twenty minutes on its issue tracker and PR history instead. Read five recent issues and note who responds first (bot or human) and how long it takes. Find four merged external PRs and note the gap between opened and merged. Time the first &lt;em&gt;human&lt;/em&gt; response, not the first automated one. That's the whole exercise, and it's cheaper than a four-month wait.&lt;/p&gt;

&lt;p&gt;If you're trying to decide whether to depend on a project rather than contribute to it, the evidence you'd gather is nearly the same, aimed at a different question. See &lt;a href="/blog/oss-signal-assess-new-github-repo"&gt;oss-signal-assess-new-github-repo&lt;/a&gt; for that version, or browse the rest of the &lt;a href="/open-source"&gt;open-source signal series&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;How long should a first PR take to get reviewed?&lt;/h3&gt;

&lt;p&gt;There is no fixed answer. In the sample above, external merges landed in roughly six to seventeen days, but a hand sample is not a distribution. Judge each repo from its own recent threads, not from a number in this article.&lt;/p&gt;

&lt;h3&gt;Are good first issue labels useful?&lt;/h3&gt;

&lt;p&gt;In cli/cli, the open good-first-issue query returned zero results at the time of this check, while real external PRs were being merged. The label is a claim a project makes about itself; issue threads and merged PRs are things that actually happened. Some projects do maintain these labels carefully. Verify per repo.&lt;/p&gt;

&lt;h3&gt;Is cli/cli a good first project for me?&lt;/h3&gt;

&lt;p&gt;That framing does not have a yes-or-no answer here. cli/cli is used as an illustration, not an endorsement, and it is staffed by GitHub employees, so its response times are not typical of volunteer-run projects. Your constraints (time, skill, interest) decide, not this article.&lt;/p&gt;





&lt;p&gt;&lt;em&gt;Disclosure: this is a first-party piece from the ranex.dev open-source signal series. Ranex has no affiliation with cli/cli, GitHub, or the jq project. Research and drafting were AI-assisted; human review is required before publication.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>contribution</category>
      <category>github</category>
      <category>opensource</category>
      <category>opensourcecontribution</category>
    </item>
    <item>
      <title>Why a Verdict Has to Be a Pure Function</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:02:14 +0000</pubDate>
      <link>https://dev.to/anthonygarces/why-a-verdict-has-to-be-a-pure-function-50na</link>
      <guid>https://dev.to/anthonygarces/why-a-verdict-has-to-be-a-pure-function-50na</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If your green result depends on the clock, a network call, or a model’s opinion, you cannot replay it; Ranex makes the verdict a function of gate, evidence, subject, and approver. &lt;a href="https://ranex.dev/blog/how-the-kernel-works" rel="noopener noreferrer"&gt;Read how the kernel works.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You inherit a green check, then a release breaks. Now you have a hard question. Did the check prove anything, or did the conditions happen to be friendly when it ran?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A verdict must not have a mood.&lt;/strong&gt; The kernel’s working evaluator takes a gate, evidence, a subject, and an approver. Give it those same inputs and it returns the same verdict, always. That is a narrow promise. It is also the foundation beneath a result you can inspect later.&lt;/p&gt;

&lt;h2 id="in-this-note"&gt;In this note&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A verdict must not have a mood&lt;/li&gt;
&lt;li&gt;Purity draws a hard boundary&lt;/li&gt;
&lt;li&gt;Check your own verdict path&lt;/li&gt;
&lt;li&gt;Proof is a record you can replay&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="a-verdict-must-not-have-a-mood"&gt;A verdict must not have a mood&lt;/h2&gt;

&lt;p&gt;A pure verdict answers from its inputs alone. It does not ask what time it is, call a service, read a model response, or change its answer because a different operator ran it.&lt;/p&gt;

&lt;p&gt;That sounds strict because it is. A verdict that reaches outside its inputs is partly a report about the moment it ran. The code could be unchanged while the result moves because a service was down, a credential was present, or a model phrased an answer differently. You are left arguing about atmosphere instead of examining proof.&lt;/p&gt;

&lt;p&gt;Ranex states the rule in executable terms. &lt;code&gt;evaluate()&lt;/code&gt; is a pure function of &lt;code&gt;(gate, evidence, subject, approver)&lt;/code&gt;. The repository also names a useful test for the boundary: removing every model credential from the machine must not change a verdict. If credentials can move the outcome, a model is inside the judging path. That is not an assistant helping explain a result. That is an unrecorded decider.&lt;/p&gt;

&lt;p&gt;The topology matters here. The model port can propose, criticise, or translate text. The worker port returns a diff from its isolated worktree. The check port produces the outputs that count. None of those model roles can pass a gate. The kernel reads the evidence and applies the rule. Your agent can be inventive where invention belongs; it cannot make the ruler bend.&lt;/p&gt;

&lt;p&gt;This does not make a verdict friendly. It makes it legible. When it says FAIL, you can ask which input failed to satisfy the gate. When it says PASS, you can preserve the inputs and ask the same question again later. There is no confidence score to interpret and no model memory to guess at.&lt;/p&gt;

&lt;h2 id="purity-draws-a-hard-boundary"&gt;Purity draws a hard boundary&lt;/h2&gt;

&lt;p&gt;Purity does not mean every part of building software is predictable. It means nondeterministic work has a boundary, and the judgment on the other side does not inherit its chaos.&lt;/p&gt;

&lt;p&gt;An agent can write different code from the same request. It can take different amounts of time. It can fail before it succeeds. Ranex does not claim otherwise. The determinism ledger separates those facts from the mechanical chain: graph to covering paths, paths to scenario text, test plus code to result, results plus rules to verdict, and journal to full replay. Each left-hand transformation is intended to be a pure function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generated code is not the proof.&lt;/strong&gt; Evidence tied to a subject and rules applied to that evidence are the proof. The approved flow graph is the root of trust in the designed larger loop; graph compilation and the surrounding product flow remain designed rather than built. The current kernel has a working verdict path, not the whole picture around it.&lt;/p&gt;

&lt;p&gt;That boundary stops a familiar trick. A worker can say that the diff is correct. A model can say the tests look convincing. Neither statement is a verdict. The kernel needs evidence that satisfies the gate for the precise subject being judged, and it applies the same evaluation rather than trusting a self-report.&lt;/p&gt;

&lt;p&gt;It also keeps a hard distinction between what a passing build proves and what it does not. A passing build supports the claim that approved graph behavior has executable tests, those tests ran and passed, and the evidence is pinned to an exact code digest. It does not prove the graph was right, behavior outside that graph, or properties such as performance, accessibility, or security unless separate gates check them. Saying less is not a weakness. It is how the verdict keeps its meaning.&lt;/p&gt;

&lt;h2 id="check-your-own-verdict-path"&gt;Check your own verdict path&lt;/h2&gt;

&lt;p&gt;You can inspect whether your own pipeline has this boundary before replacing anything. Start with the green result you least trust. Ask what could change it without changing the commit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the verdict read the clock, a network response, or a model opinion?&lt;/li&gt;
&lt;li&gt;Can you name the gate, the evidence, the exact subject, and the approver it used?&lt;/li&gt;
&lt;li&gt;Would the same recorded inputs return the same answer on another machine?&lt;/li&gt;
&lt;li&gt;Does evidence from an older subject stop counting after the tree changes?&lt;/li&gt;
&lt;li&gt;Can the person who produced evidence also approve it?&lt;/li&gt;
&lt;li&gt;Can you remove model credentials and get the same verdict?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That list is not a purity contest. It is an incident drill. If one answer is unclear, your next disagreement over a release will be harder because the system did not retain a stable question to ask.&lt;/p&gt;

&lt;p&gt;Notice the link between this rule and &lt;a href="https://ranex.dev/blog/absence-blocks" rel="noopener noreferrer"&gt;absence blocks&lt;/a&gt;. A pure evaluator is not useful if missing evidence silently becomes success. The evaluator needs stable inputs, and it needs to fail when a required input cannot satisfy a required claim.&lt;/p&gt;

&lt;h2 id="proof-is-a-record-you-can-replay"&gt;Proof is a record you can replay&lt;/h2&gt;

&lt;p&gt;Purity buys replay, audit, and freedom from a judge’s mood. It does not bless bad evidence; it makes the question of evidence impossible to hide.&lt;/p&gt;

&lt;p&gt;Subject-bound evidence is part of that discipline. The same command against a different commit proves nothing about the current one, so stale evidence stops counting. No self-approval is another part: whoever produced the evidence cannot approve it, though approver identity is unauthenticated today, so that check compares unverified strings. These are admission and gate rules around the pure evaluator, not decorations attached after a green result.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/anthonykewl20/ranex#status" rel="noopener noreferrer"&gt;repository’s Status section&lt;/a&gt; says the project is pre-release and describes the evaluator as working today. It also says the full governed loop is not all built. Read that as a boundary, not a sales pitch. A small, repeatable verdict path is useful precisely because it does not pretend to settle every question about a system.&lt;/p&gt;

&lt;p&gt;When someone challenges a PASS, the useful answer is not “trust the process.” It is “here are the gate, evidence, subject, and approver; run the evaluation again.” If the answer changes, the record or the evaluator changed. Either way, you have something concrete to investigate.&lt;/p&gt;

&lt;h2 id="questions-people-actually-ask"&gt;Questions people actually ask&lt;/h2&gt;

&lt;h3&gt;What is a pure-function verdict?&lt;/h3&gt;

&lt;p&gt;Ranex evaluates a pure-function verdict from gate, evidence, subject, and approver, so the same inputs produce the same verdict.&lt;/p&gt;

&lt;h3&gt;Why must a software verdict be deterministic?&lt;/h3&gt;

&lt;p&gt;A deterministic verdict lets an operator replay the same recorded inputs instead of trusting the conditions of one run.&lt;/p&gt;

&lt;h3&gt;Is AI-generated code deterministic?&lt;/h3&gt;

&lt;p&gt;Ranex says generated code is not deterministic; determinism describes the process and verdict, not the code an agent writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it. Break it. Tell me what broke.&lt;/strong&gt; Read the &lt;a href="https://github.com/anthonykewl20/ranex" rel="noopener noreferrer"&gt;Ranex repository&lt;/a&gt;, then try the credential-removal test on a verdict you rely on.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this post was drafted with AI assistance. Every factual claim traces to the repository’s README or slice records, the same fact gate the product enforces on code. It ships only after Anthony’s own review.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>evidence</category>
      <category>gateverdicts</category>
      <category>kernel</category>
    </item>
    <item>
      <title>A Signed Record Is Not a Fact</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:01:48 +0000</pubDate>
      <link>https://dev.to/anthonygarces/a-signed-record-is-not-a-fact-1bc</link>
      <guid>https://dev.to/anthonygarces/a-signed-record-is-not-a-fact-1bc</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Ed25519 proves who signed specific bytes, not whether the claim is true; verdict policy must come from the evaluated commit. &lt;a href="https://ranex.dev/blog/building-ranex-slice-log" rel="noopener noreferrer"&gt;This slice log&lt;/a&gt; started with the question that matters: what exactly did that green light prove?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your build says PASS, the record is signed, and everyone wants to move on. Before you do, what exactly did that green light prove?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id="in-this-note"&gt;In this note&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A signature proves origin, not truth&lt;/li&gt;
&lt;li&gt;The second reopening was the real scar&lt;/li&gt;
&lt;li&gt;What to hunt for in your own pipeline&lt;/li&gt;
&lt;li&gt;Why four audits found what the tests missed&lt;/li&gt;
&lt;li&gt;Make one trust decision visible this week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A signed record can be real and still be used to tell you a false story. The failure is in the trust root, not the cryptography.&lt;/p&gt;

&lt;p&gt;I learned this by closing the same slice, reopening it when audits showed the tests were narrower than reality, closing it again, then &lt;a href="https://github.com/anthonykewl20/ranex/blob/main/docs/slices/done/SLICE-002-evidence-authenticity.md" rel="noopener noreferrer"&gt;reopening it a second time&lt;/a&gt;. That cost belongs in the record because it gives you something useful to check in your own pipeline.&lt;/p&gt;

&lt;h2 id="a-signature-proves-origin-not-truth"&gt;A signature proves origin, not truth&lt;/h2&gt;

&lt;p&gt;An Ed25519 signature proves that the holder of a private key signed specific bytes. It does not prove that the claim inside those bytes is true.&lt;/p&gt;

&lt;p&gt;That distinction is the whole post. Your CI record can tell you who produced an observation. It cannot, by signature alone, tell you whether the rule that admitted it was trustworthy, &lt;a href="https://ranex.dev/blog/six-roads-to-a-false-pass" rel="noopener noreferrer"&gt;whether the command meant what the claim says&lt;/a&gt;, or whether an approver was independent.&lt;/p&gt;

&lt;p&gt;In Ranex, evidence is signed and bound to a producer in a committed public keyring. The verifier has public keys, not the private keys used to sign. That is useful when evidence is produced on one machine and verified on another.&lt;/p&gt;

&lt;p&gt;But the first version still had a dangerous opening: the keyring and gate catalog could be read from the working tree. An uncommitted edit could decide a verdict. The project said review of the committed keyring was the control. The code had not made that true.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Review cannot control bytes that were never committed for review.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a shape worth noticing. Security language can make a weak boundary sound solid: signed, verified, trusted, approved. Ask one plain question instead: &lt;strong&gt;which exact bytes decided this result, and who was allowed to choose them?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id="the-second-reopening-was-the-real-scar"&gt;The second reopening was the real scar&lt;/h2&gt;

&lt;p&gt;The second reopening showed that checking a committed file is not enough. You must also refuse a trust-root path the commit does not carry.&lt;/p&gt;

&lt;p&gt;SLICE-002 was reopened a second time on 2026-08-02 because the trust-root check skipped itself when the evaluated commit had no such path. The path came from a flag. The party being gated could name a catalog or keyring that the commit did not carry, and Ranex would read it unchecked.&lt;/p&gt;

&lt;p&gt;This is the attacker selecting the rulebook after the game, not an edge case.&lt;/p&gt;

&lt;p&gt;The slice record reproduced an attacker-named gate catalog that could rewrite the gate after the work. It also reproduced a keyring at a gitignored path, where a producer could register itself while &lt;code&gt;git status&lt;/code&gt; stayed clean. Another route used a committed symlink at a reviewed name: resolution followed the link before Git was asked, so the reviewed name was never the thing being checked.&lt;/p&gt;

&lt;p&gt;All of those routes landed on the same mistake: absence got a pass. The code returned without comparing anything.&lt;/p&gt;

&lt;p&gt;ADR-002 closed that opening by refusing any trust-root path the evaluated ref does not carry. It compares the bytes Git records for the path as named with the bytes that would decide the verdict, then returns committed bytes for the loaders to parse. The loader does not reopen a mutable path afterward.&lt;/p&gt;

&lt;p&gt;That last part matters. A compare followed by a second read is two chances for different bytes to appear. The record measured the change with &lt;code&gt;strace&lt;/code&gt;: one open per trust-root file after the fix, where there had been three and two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the policy from the committed state, not from wherever a flag points.&lt;/strong&gt; The &lt;a href="https://ranex.dev/blog/how-the-kernel-works" rel="noopener noreferrer"&gt;kernel model&lt;/a&gt; only helps when the inputs to its decision are pinned too.&lt;/p&gt;

&lt;h2 id="what-to-hunt-for-in-your-own-pipeline"&gt;What to hunt for in your own pipeline&lt;/h2&gt;

&lt;p&gt;Look for the files and paths that decide what a PASS means. If a worker, build, or command under test can choose them, your verifier has an opening.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration that declares required checks, trusted identities, allowed commands, or approval rules.&lt;/li&gt;
&lt;li&gt;CLI flags or environment variables that select policy files, keyrings, manifests, or catalogs.&lt;/li&gt;
&lt;li&gt;Loaders that check one path and then reopen that path later.&lt;/li&gt;
&lt;li&gt;Symlinks, normalized paths, and ignored files near a policy lookup.&lt;/li&gt;
&lt;li&gt;Code that treats a missing policy file as an empty policy, a default, or an innocent absence.&lt;/li&gt;
&lt;li&gt;Tests that edit a committed policy file but never name a policy file the commit does not carry.&lt;/li&gt;
&lt;li&gt;Green controls that prove ordinary work can still proceed after a refusal is added.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not stop at “the file is in the repository.” Ask whether the evaluated commit carries that exact path. A working tree is a place people work. It is not automatically a trustworthy source of policy.&lt;/p&gt;

&lt;p&gt;And do not mistake a clean status output for evidence that nothing changed. Gitignored inputs can be invisible to the habitual check while still deciding the result.&lt;/p&gt;

&lt;h2 id="why-four-audits-found-what-the-tests-missed"&gt;Why four audits found what the tests missed&lt;/h2&gt;

&lt;p&gt;Independent audits exist because your first test suite is narrower than reality. SLICE-002 closed after 17 defects across four independent audits, not because the first green run had been sufficient.&lt;/p&gt;

&lt;p&gt;The initial close was premature. Two independent auditors found defects and showed that two completed criteria were false. One test compared the same string to itself. Another used a single-claim gate even though the real repository gate required two claims.&lt;/p&gt;

&lt;p&gt;They are the lesson, embarrassing or not. A test can be green because it tested an easier world than the one your tool inhabits.&lt;/p&gt;

&lt;p&gt;The second reopen sharpened it further. The tests had caught an edit to a committed trust root. Nobody had asked what happened when the path was absent from the commit. The test proved the answer to one question. The code needed to survive another.&lt;/p&gt;

&lt;p&gt;There is a simple review prompt hiding here: &lt;strong&gt;what input shape did this test not ask about?&lt;/strong&gt; Missing path. Ignored path. Redirected path. File swap after check. A policy that is syntactically valid but weak. These are not exotic when the input selects the control.&lt;/p&gt;

&lt;p&gt;Ranex is pre-release. The closed slice does not mean the whole system has become a source of truth. Its own record states limits plainly: a signature does not stop a same-user attacker who can read the signing key, and the approver identity is an unauthenticated string. I do not have evidence that either is solved by signing evidence.&lt;/p&gt;

&lt;p&gt;Stating limits that plainly is part of the control. A signed record that looks stronger than it is can cause more damage than an unsigned one, because people stop asking questions.&lt;/p&gt;

&lt;h2 id="questions-people-actually-ask"&gt;Questions people actually ask&lt;/h2&gt;

&lt;p&gt;These questions separate signature origin from committed trust-root policy.&lt;/p&gt;

&lt;h3&gt;What does an Ed25519 signature prove in an evidence record?&lt;/h3&gt;

&lt;p&gt;An Ed25519 signature proves the record was signed by the holder of a registered producer key over the signed fields. An Ed25519 signature does not prove the claim is true or that the approval is authentic.&lt;/p&gt;

&lt;h3&gt;Why must a keyring and gate catalog come from the commit?&lt;/h3&gt;

&lt;p&gt;The keyring and gate catalog files decide which evidence counts. If the party being judged can point the verifier at unchecked bytes, it can choose the policy that judges it.&lt;/p&gt;

&lt;h3&gt;What should happen when a trust-root path is absent from the evaluated commit?&lt;/h3&gt;

&lt;p&gt;The verifier should refuse. A file no commit carries was reviewed by nobody, whatever its contents say.&lt;/p&gt;

&lt;h2 id="make-one-trust-decision-visible-this-week"&gt;Make one trust decision visible this week&lt;/h2&gt;

&lt;p&gt;Start with one green check you already trust. Identify its policy file, identity list, manifest, or rule catalog. Then answer: does the verifier read committed bytes, or does it read whatever the current process can reach?&lt;/p&gt;

&lt;p&gt;If the answer is the second one, make absence block. Test an uncarried path. Test an ignored one. Test a symlink. Test a swap between validation and load. Keep the ordinary committed path as a green control so “refuse everything” cannot impersonate safety.&lt;/p&gt;

&lt;p&gt;The slice records are in &lt;a href="https://github.com/anthonykewl20/ranex" rel="noopener noreferrer"&gt;the Ranex repository&lt;/a&gt;, under &lt;code&gt;docs/slices/done/&lt;/code&gt;. Read the failure before you borrow the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it. Break it. Tell me what broke.&lt;/strong&gt; If this helped, star the repository and send an honest critique. The useful reply is the one that finds the next unchecked input.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this post was drafted with AI assistance. Every factual claim traces to the repository’s README or slice records, the same fact gate the product enforces on code. It ships only after Anthony’s own review.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>evidence</category>
      <category>separationofduties</category>
      <category>signing</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Your AI Cited Source Code. Can You Prove Which Source?</title>
      <dc:creator>Anthony Garces</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:01:42 +0000</pubDate>
      <link>https://dev.to/anthonygarces/your-ai-cited-source-code-can-you-prove-which-source-22b5</link>
      <guid>https://dev.to/anthonygarces/your-ai-cited-source-code-can-you-prove-which-source-22b5</guid>
      <description>&lt;p&gt;Your coding agent just handed you a fix and backed it up with a quote: "based on the&lt;br&gt;
implementation of the retry logic in your HTTP client library." It even pasted the&lt;br&gt;
function. The fix looks right. Then a reviewer leaves one comment on your pull request:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which version of the library is that from?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And there it is. The citation names a project, but not a version. Not a tag, not a&lt;br&gt;
commit, not an artifact. The function might be from the release you have pinned, from&lt;br&gt;
&lt;code&gt;main&lt;/code&gt; at a different point in time, or from a fork with the same file name. The&lt;br&gt;
citation alone does not tell you which.&lt;/p&gt;

&lt;p&gt;This isn't a story about careless review. A citation and provenance are two different&lt;br&gt;
things, and the distinction matters whenever version-specific behavior affects a&lt;br&gt;
change. That is why this series starts here, alongside the site's other&lt;br&gt;
&lt;a href="/blog/field-notes"&gt;field notes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;A citation is a claim. Provenance is evidence.&lt;/h2&gt;

&lt;p&gt;When an agent says "this comes from library X," that's a report. Useful, often correct,&lt;br&gt;
and unverifiable on its own. Provenance is what turns the report into something a&lt;br&gt;
reviewer can check: an exact version, a specific artifact, a checksum that either&lt;br&gt;
matches or doesn't, a commit hash you can visit.&lt;/p&gt;

&lt;p&gt;Reports come from the worker. Verdicts come from a judge.&lt;/p&gt;

&lt;p&gt;The agent is the worker here. The judge is any verification step that someone else can&lt;br&gt;
rerun and get the same answer. If your review process accepts the report without a way&lt;br&gt;
to reach a verdict, the question isn't whether a mismatch will slip through. It's&lt;br&gt; whether you'll notice when it does.&lt;/p&gt;

&lt;h2&gt;Five questions that establish provenance&lt;/h2&gt;

&lt;p&gt;You don't need any particular tool for this. For a dependency your AI cited, can you&lt;br&gt;
answer these five questions with links?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does the version resolve to exactly one release?&lt;/strong&gt; "The requests library" is not
an answer; a specific resolved version is. Range specifiers need to collapse to the
one version your lockfile actually pins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are you looking at the registry artifact or a repo snapshot?&lt;/strong&gt; The package
published to a registry and the repository at a matching tag are different evidence
surfaces. Which one does your build consume, and which one did the citation use?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does a published checksum verify, and what happens when it doesn't?&lt;/strong&gt; A check
that warns and continues is a formality. A check that fails closed, refusing to
proceed on mismatch, is a control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the code comes from git, is the commit pinned and are the file contents&lt;br&gt;
verified?&lt;/strong&gt; Can you point to the commit hash and check the individual blobs against
the host's tree data?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is there a record you can attach to the review?&lt;/strong&gt; Provenance that lives in your
terminal history helps nobody next week. A manifest, even a pasted one, makes the
verdict portable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you can answer all five, the reviewer's "which version?" has an evidence-backed&lt;br&gt;
answer instead of another assertion.&lt;/p&gt;

&lt;h2&gt;Worked example: what the primary records show&lt;/h2&gt;

&lt;p&gt;Disclosure first: &lt;strong&gt;Leitir is maintained by Anthony Garces, who publishes ranex.dev.&lt;/strong&gt;&lt;br&gt;
It appears here as a transparent first-party example of the checklist above: a&lt;br&gt;
methodology walkthrough, not a ranking win or an independent endorsement.&lt;/p&gt;

&lt;p&gt;Checked on &lt;strong&gt;2026-08-31 (Asia/Manila)&lt;/strong&gt; against the project's primary sources: the&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/anthonykewl20/leitir/blob/main/README.md" rel="noopener noreferrer"&gt;Leitir README&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://api.github.com/repos/anthonykewl20/leitir/releases/latest" rel="noopener noreferrer"&gt;latest-release endpoint&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/anthonykewl20/leitir/tags" rel="noopener noreferrer"&gt;tags&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/anthonykewl20/leitir/blob/main/pyproject.toml" rel="noopener noreferrer"&gt;project metadata&lt;/a&gt;,&lt;br&gt;
and &lt;a href="https://github.com/anthonykewl20/leitir/blob/main/LICENSE" rel="noopener noreferrer"&gt;LICENSE&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leitir describes itself as a deterministic, provenance-bound dependency-source corpus
plus a deterministic code-search kernel for AI coding agents.&lt;/li&gt;
&lt;li&gt;Its README documents exact-version resolution, bounded registry source-artifact
fetching, fail-closed published-checksum verification, pinned git commit trees, blob
checks, and provenance manifests, the same five questions above, mechanized. I did
not run those paths for this article.&lt;/li&gt;
&lt;li&gt;Its README lists support for GitHub, GitLab, Bitbucket, Codeberg, Sourcehut, npm,
PyPI, crates.io, and Go sources.&lt;/li&gt;
&lt;li&gt;The README documents &lt;code&gt;leitir info &amp;lt;spec&amp;gt;&lt;/code&gt; as one JSON response containing provenance,
an API summary, examples, trust, and parity.&lt;/li&gt;
&lt;li&gt;GitHub's latest-release endpoint returned v0.1.6, published 2026-08-25. The tags
listing maps v0.1.6 to commit
&lt;code&gt;91e93e16466405bb95fcfb762e9319a5f716de09&lt;/code&gt;; that observation does not guarantee the
tag can never move.&lt;/li&gt;
&lt;li&gt;The README says distribution is through GitHub releases and tags rather than PyPI.
Current-main &lt;code&gt;pyproject.toml&lt;/code&gt; sets version 0.1.6 and &lt;code&gt;dependencies = []&lt;/code&gt;; that does
not prove anything about optional extras or the contents of the release artifacts.&lt;/li&gt;
&lt;li&gt;The checked LICENSE file contains MIT License text. GitHub's
&lt;a href="https://api.github.com/repos/anthonykewl20/leitir" rel="noopener noreferrer"&gt;repository metadata&lt;/a&gt; reports
&lt;code&gt;NOASSERTION&lt;/code&gt;, so this article does not claim GitHub classifies the repository as
MIT or that the checked license text covers every file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the scope of all that, stated exactly: the verification methods described above&lt;br&gt;
are designed to establish &lt;strong&gt;origin binding only&lt;/strong&gt;. When you run them successfully,&lt;br&gt;
they can show that specific bytes match a specific published artifact or commit. They&lt;br&gt;
do not prove software quality, security, correctness, suitability for your project,&lt;br&gt;
or the identity of the publisher. A perfectly provenanced dependency can still be a&lt;br&gt;
bad choice. Verification narrows what you have to trust; it doesn't remove trust from&lt;br&gt;
the picture.&lt;/p&gt;

&lt;p&gt;That's also why the series will use careful wording from here on: a citation either&lt;br&gt;
has &lt;strong&gt;"A scoped Leitir record is linked"&lt;/strong&gt; or &lt;strong&gt;"No Leitir record is linked."&lt;/strong&gt; Absence&lt;br&gt;
never means failed verification. It means unverified: a neutral state, and an honest&lt;br&gt;
one.&lt;/p&gt;

&lt;h2&gt;What could you verify this week?&lt;/h2&gt;

&lt;p&gt;Could you pick one dependency your AI assistant cited recently and run the five&lt;br&gt;
questions on it by hand? Can you resolve the exact version, find the registry artifact,&lt;br&gt;
and verify one checksum? Where does the trail go cold? That cold spot is your&lt;br&gt;
workflow's actual provenance gap, and it is worth knowing before a reviewer finds it.&lt;/p&gt;

&lt;p&gt;Caveats worth carrying with you: facts above were verified on 2026-08-31. The README&lt;br&gt;
behavior was not runtime-tested, and release assets were not independently hashed.&lt;br&gt;
If publication happens later, human review must recheck the version, release, activity,&lt;br&gt;
and license facts that morning. Follow the primary links rather than trusting this&lt;br&gt;
snapshot. Treat the &lt;em&gt;method&lt;/em&gt; as the takeaway, and evaluate any tool against your own&lt;br&gt;
constraints.&lt;/p&gt;

&lt;p&gt;Research and drafting were AI-assisted. Human review is required before publication.&lt;br&gt;
Primary sources are linked so you can check the claims yourself.&lt;/p&gt;

&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;Does a checksum match prove the code is safe?&lt;/h3&gt;

&lt;p&gt;No. A verified checksum proves the bytes you examined match match a published artifact:&lt;br&gt;
origin binding, nothing more. It does not prove quality, security, correctness,&lt;br&gt;
suitability for your use, or who the publisher really is. Those need separate review.&lt;/p&gt;

&lt;h3&gt;What does "No Leitir record is linked" mean?&lt;/h3&gt;

&lt;p&gt;Only that no scoped provenance record accompanies the citation, so origin has to be&lt;br&gt;
established another way. Absence never means failed verification. It means unverified,&lt;br&gt;
which is a different and honest state.&lt;/p&gt;

&lt;h3&gt;Can I check AI source code provenance without any tool?&lt;/h3&gt;

&lt;p&gt;Yes. Resolve the exact version, download the registry artifact, verify its published&lt;br&gt;
checksum, or pin the git commit and compare. It is manual and slow, but every step uses&lt;br&gt;
primary sources you can link in a review.&lt;/p&gt;

&lt;h3&gt;Why disclose that Leitir is a first-party project?&lt;/h3&gt;

&lt;p&gt;Because the example only has value if you can weigh the interest behind it. Leitir is&lt;br&gt;
maintained by the author of this site, so this article is a transparent methodology&lt;br&gt;
walkthrough, not an independent ranking or endorsement.&lt;/p&gt;

</description>
      <category>aicodingagents</category>
      <category>dependencies</category>
      <category>leitir</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
