Add post: How I Vet an Open-Source Dependency Before Betting On It (EN + ZH)
Deploy / build (push) Successful in 15s

New `devops` post on vetting an upstream dependency before building on it:
the six checks that corrected six assumptions from a 1,306-line architecture
spec — repo vital signs via the GitHub API, grepping for the feature instead
of reading for it (OIDC/SSO/SAML → 0 hits), reading a feature doc's target
branch (white-label lives on `multi-tenant`, not `main`), reading the data
model rather than the feature list (percent/fixed vs five assumed rule types),
the Community-vs-Enterprise tier gate (API tokens free, SSO paid), and whether
the money rail works in-country (selfhost + manual payouts, not Stripe Connect).
Closes with the fallback question and the authentik proxy-provider answer.

- EN: src/content/posts/vetting-an-open-source-dependency-before-you-bet-on-it.md
- ZH: src/content/posts/zh/<same slug>.md (same filename → auto language switch)
- Custom OG (1200x630) + banner (1600x900) art via TERMINALS/BANNERS entries
- pubDate 2026-09-29; build verified 93 pages, 17/17 content checks, listing
  order monotonic on /posts/, / and /zh/

Note: the generator maps also carry two entries belonging to parallel
in-flight posts (adding-english-mode…, and the sessions' other slugs);
their post files were left uncommitted.
This commit is contained in:
2026-09-29 01:07:43 +08:00
parent a77fb0b5d3
commit 87111360ce
6 changed files with 370 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

+62
View File
@@ -706,6 +706,68 @@ BANNERS['adding-english-mode-to-a-chinese-only-web-app'] = {
],
};
BANNERS['vetting-an-open-source-dependency-before-you-bet-on-it'] = {
titlebar: 'root@dsm — due diligence · 6 checks',
lines: [
{ t: 'prompt', text: '$' }, { t: 'cmd', text: 'curl -s api.github.com/repos/getcoherence/openpartner' },
{ t: 'dim', text: 'MIT · created 2026-04 · 8 stars · 15 open issues' },
{ t: 'prompt', text: '$' }, { t: 'cmd', text: "grep -in 'oidc\\|sso\\|saml' README.md ARCHITECTURE.md docs/*.md" },
{ t: 'err', text: '0 hits ← the spec assumed OIDC for partner login' },
{ t: 'hl', text: 'Community: API tokens ✓ · SSO ✗ (Enterprise only)' },
{ t: 'prompt', text: '$' }, { t: 'cmd', text: 'selfhost mode · manual payouts · pin the commit' },
{ t: 'ok', text: '→ 6 assumptions corrected before integration ✓' },
],
flow: [
{ n: '1', label: '1,306-line spec' },
{ n: '2', label: 'repo vital signs' },
{ n: '3', label: 'grep the feature' },
{ n: '4', label: 'tier + rail fit' },
{ n: '5', label: 'fallback decided ✓' },
],
};
BANNERS['authentik-forward-auth-gate-wasnt-live'] = {
titlebar: 'root@dsm — forward-auth · two ingress layers',
lines: [
{ t: 'prompt', text: '$' }, { t: 'cmd', text: "curl -sk -H 'Host: app.example.com' https://localhost/" },
{ t: 'err', text: '302 → /outpost.goauthentik.io/start ← looks gated' },
{ t: 'prompt', text: '$' }, { t: 'cmd', text: 'curl -sI https://app.example.com/' },
{ t: 'err', text: 'x-powered-by: Express ← the app answered, not the outpost' },
{ t: 'hl', text: 'the tunnel rule went straight to the container — nginx never saw the request' },
{ t: 'dim', text: 'grep -c "proxy_pass :10000" → 11 vhosts share that port' },
{ t: 'cmd', text: 'verify from outside · read which layer replied' },
{ t: 'ok', text: '→ one line to revert · ten gates left intact ✓' },
],
flow: [
{ n: '1', label: 'vhost → outpost' },
{ n: '2', label: 'tunnel → app', err: true },
{ n: '3', label: 'x-powered-by told' },
{ n: '4', label: 'count the port' },
{ n: '5', label: 'gate verified ✓' },
],
};
BANNERS['nocodb-sso-is-a-licensed-feature'] = {
titlebar: 'root@dsm — nocodb CE 2026.09.0',
lines: [
{ t: 'prompt', text: '$' }, { t: 'cmd', text: 'NC_SSO=oidc NC_SSO_OIDC_ISSUER=https://auth…' },
{ t: 'dim', text: 'enforced at boot — the vars are wired in, not vestigial' },
{ t: 'prompt', text: '$' }, { t: 'cmd', text: 'docker run --name nc-sso-test -p 10399:8080 (no NC_DB → SQLite)' },
{ t: 'err', text: 'curl /auth/oidc → ### UNCAUGHT EXCEPTION ### exits(1)' },
{ t: 'hl', text: 'one unauthenticated GET is enough to stop the instance' },
{ t: 'dim', text: 'CE mode · meta=MySQL (licensing needs Postgres) · fork 0.255.2' },
{ t: 'prompt', text: '$' }, { t: 'cmd', text: 'gate at the edge instead · prod container never touched' },
{ t: 'ok', text: '→ OIDC SSO is Business+ · one sentence, not an afternoon ✓' },
],
flow: [
{ n: '1', label: 'NC_SSO vars', err: true },
{ n: '2', label: 'throwaway box' },
{ n: '3', label: '/auth/oidc dies' },
{ n: '4', label: 'licence + Postgres' },
{ n: '5', label: 'edge gate ✓' },
],
};
// ---------- read frontmatter ----------
const postPath = join(ROOT, 'src', 'content', 'posts', `${slug}.md`);
let category = 'devops';
+18
View File
@@ -219,6 +219,24 @@ TERMINALS['adding-english-mode-to-a-chinese-only-web-app'] = `
<div class="line"><span class="prompt">&nbsp;</span><span class="err">the UI is 100% Chinese · browsers then never offer translate</span></div>
<div class="line"><span class="prompt">$</span><span class="cmd">nginx sub_filter + gate-en.js · 871 zh→en labels</span><span class="fix">→ 88% English ✓</span></div>`;
TERMINALS['vetting-an-open-source-dependency-before-you-bet-on-it'] = `
<div class="line"><span class="prompt">$</span><span class="cmd">grep -in 'oidc\|sso\|saml' README.md ARCHITECTURE.md docs/*.md</span><span class="err">→ 0 hits</span></div>
<div class="line"><span class="prompt">&nbsp;</span><span class="err">the spec assumed OIDC · partner login was never implementable</span></div>
<div class="line"><span class="prompt">$</span><span class="cmd">head -5 docs/white-label-custom-domains.md</span><span class="err">→ Target branch: multi-tenant</span></div>
<div class="line"><span class="prompt">$</span><span class="cmd">selfhost mode · manual payouts · pin the commit</span><span class="fix">→ 6 assumptions corrected ✓</span></div>`;
TERMINALS['authentik-forward-auth-gate-wasnt-live'] = `
<div class="line"><span class="prompt">$</span><span class="cmd">curl -sk -H 'Host: app.example.com' https://localhost/</span><span class="fix">→ 302 gated ✓</span></div>
<div class="line"><span class="prompt">$</span><span class="cmd">curl -sI https://app.example.com/</span><span class="err">→ 200 x-powered-by: Express</span></div>
<div class="line"><span class="prompt">&nbsp;</span><span class="err">the tunnel rule answered · nginx was never in the path</span></div>
<div class="line"><span class="prompt">$</span><span class="cmd">verify from outside · read which layer replied</span><span class="fix">→ gate real ✓</span></div>`;
TERMINALS['nocodb-sso-is-a-licensed-feature'] = `
<div class="line"><span class="prompt">$</span><span class="cmd">NC_SSO=oidc · app boot</span><span class="fix">→ env keys enforced</span></div>
<div class="line"><span class="prompt">$</span><span class="cmd">curl -s localhost:10399/auth/oidc</span><span class="err">→ uncaught TypeError</span></div>
<div class="line"><span class="prompt">&nbsp;</span><span class="err">unhandledRejection · container exits(1)</span></div>
<div class="line"><span class="prompt">$</span><span class="cmd">CE mode · meta=MySQL · fork 0.255.2 (2024)</span><span class="fix">→ gate at the edge ✓</span></div>`;
// ---------- read frontmatter ----------
const postPath = join(ROOT, 'src', 'content', 'posts', `${slug}.md`);
if (!existsSync(postPath)) {
@@ -0,0 +1,177 @@
---
title: "How I Vet an Open-Source Dependency Before Betting On It"
description: "Six checks that catch a repo which doesn't do what its docs claim — grep for the feature, read the doc's target branch, check the tier gate and the payment rail."
pubDate: 2026-09-29
category: devops
tags: ["open-source", "due-diligence", "oidc", "self-hosting", "docker"]
ogImage: "/og/vetting-an-open-source-dependency-before-you-bet-on-it.png"
banner: "/banners/vetting-an-open-source-dependency-before-you-bet-on-it.png"
draft: false
---
I was handed a 1,300-line architecture spec for a partner program. It read like a
plan: consistent, cross-referenced, confident. It named a self-hosted
open-source engine as the attribution and commission layer, and it assumed three
things about that engine — that it spoke OIDC so my existing identity provider
could log partners in, that its commission rules covered the five shapes the
business needed, and that its white-label feature was shipped.
Ten minutes of `curl` and `grep` later, two of those were false and the third was
half-true. None of that is unusual. The README describes the *product*; the
repository describes the *software*. The gap between those two is exactly where
integration projects die — and it is cheap to measure before you write any code.
## The problem, phrased as the question I'd actually search for
**How do I know an open-source project does what its documentation claims before
I build on it?**
A spec is a claim *about other software*. The more polished the spec, the more its
assumptions read like facts — this one cited sources, defined stable IDs, and had
a source-of-truth matrix. What it never did was check whether the engine down the
chain implemented the features being assigned to it. So before installing
anything, I went looking for those specific features.
## What I tried first, and why it wasn't enough
**1. Read the README end to end.** It sells the pipeline well:
`click → identity → event → attribution → commission → payout`. It never once
says which authentication protocol the app speaks. For a feature you *need*,
silence isn't neutral — but an absent mention is easy to skim past when the rest
of the page is convincing.
**2. Searched the web.** Several of my queries returned nothing usable. The
project is a few months old and its SEO surface is thin. For software that's
fine — the source *is* the documentation. It just means the reading has to happen
in the repo, not in a blog post.
**3. Found a 78 KB feature design doc and assumed the feature shipped.** It was
labelled "FINAL, post-review", listed three reviewers, and carried a date. I took
that as proof. That was the worst inference of the three, and the easiest to check.
## The six checks that actually settled it
### 1. Read the repo's vital signs before any feature claim
```bash
curl -s -H "User-Agent: hermes" https://api.github.com/repos/<owner>/<repo> \
| grep -E '"created_at"|"pushed_at"|"stargazers_count"|"forks_count"|"open_issues_count"|"archived"|"spdx_id"'
```
What came back: MIT, created 2026-04-23, last push 2026-09-10, 8 stars, 3 forks,
15 open issues, not archived. Read the same as: **real, still moving, and very
small** — one vendor, which also sells a hosted tier. Now I know how much of my
business I want resting on it *before* I start grading its features.
### 2. Grep for the feature instead of reading for it
```bash
grep -in 'oidc\|sso\|saml' README.md ARCHITECTURE.md docs/*.md | wc -l
```
Zero hits. The app authenticates with its own magic-link sessions and
per-persona API keys; it does not speak OIDC at all. That one line of output
killed a phase of the plan — "put the portal behind our SSO provider" — which
would otherwise have surfaced weeks later, mid-integration, with a partner
already invited. Reading for a feature you can't find is how you lose an
afternoon; grepping makes the absence loud.
### 3. Read the target branch line at the top of a feature doc
```bash
head -5 docs/white-label-custom-domains.md
```
`Target branch: multi-tenant`. The doc is a design for a branch that is not the
default branch — it describes work in flight, so the feature is not in the code
you would install. A finished-looking document with reviewers and dates can
document something unshipped. That isn't dishonesty; it's what design docs are.
### 4. Read the data model, not the feature list
The commission section named exactly two rule types — `percent` (with a recurring
flag) and `fixed`. The business spec had enumerated five. That gap isn't a bug;
it's a modelling job I'd have to do in configuration instead of expecting from
the product:
| Spec assumed | What the engine actually has |
|---|---|
| first-order bonus | a `fixed` rule on the first event |
| event-specific rules (`lead` vs `invoice_paid`) | one rule per campaign — so: two campaigns |
| recurring commission | `percent` + `recurring: true` |
| percentage | `percent` ✅ |
| fixed | `fixed` ✅ |
Two rule primitives, five behaviours — workable, but only if you design for it up
front instead of discovering it during implementation.
### 5. Check which paid tier gates the feature you need
For the database tool in the same stack, the edition table was blunt:
| Feature I needed | Community (free) | Enterprise (paid) |
|---|---|---|
| API tokens, so I can automate it | ✅ | ✅ |
| SSO / OIDC | ❌ | ✅ |
| Page designer, hide branding, 2FA | ❌ | ✅ |
The thing I wanted for the partner-facing door (SSO) sits behind the commercial
gate, while the thing I wanted for automation (an API token) is free. Knowing
which side of the wall each requirement falls on changes the design — before
you've designed around it.
### 6. Check that the money rail works in your country
The payout model was `stripe_connect | manual`, and self-hosted mode was
documented as "operator manages out-of-band" — i.e. you move the money yourself.
Stripe Connect is not how Malaysian businesses pay partners, so that one line
decided the entire deployment shape: self-host mode, manual payouts, and event
ingestion pushed from my own order system rather than leaning on the provider's
payment integration. That line is never on a marketing page, and it mattered more
than any feature.
### Then: ask the fallback question before you need it
"What if the feature I need isn't there?" For the missing SSO the answer was:
don't put login in the app, put it in front of the app. authentik's proxy
provider exists to protect "applications that do not support native
authentication protocols such as OIDC, SAML, or LDAP", and it injects
`X-authentik-username` and `X-authentik-groups` headers upstream, so the reverse
proxy can do the gating. That turned "no SSO support" from a disqualification
into an afternoon of config.
## What I'd do differently
- **Write the requirement list first, then go look for it.** Five lines — the
features *I* need — and search for those. Starting from the README's feature
list means grading the software on the vendor's exam.
- **Treat "a doc exists" as different from "a feature ships".** Read the branch
line.
- **Grep, don't read.** Presence is cheap to prove; absence is the thing you're
hunting.
- **Decide the fallback before the decision**, so a missing feature becomes a
design change rather than a surprise.
- **Pin the commit.** This project's README says its API is "stable but
unversioned" and there is no OpenAPI description anywhere in the tree. There's
no contract to compile against, so the commit hash *is* the contract — the
endpoints I use go into my own docs, and upgrades get a regression pass.
## The result
Six assumptions corrected in about ten minutes of `curl` and `grep`, before a
single line of integration code existed. The project stayed viable — but
reshaped: no OIDC, manual payouts, a pinned commit, and a plan that no longer
depends on a feature sitting on an unmerged branch.
The spec wasn't wrong to be optimistic. It was wrong to be unverified, and that's
the cheapest thing on the whole project to fix.
---
**Building on a self-hosted stack, and want a second pair of eyes on whether
those features will actually be there when you integrate?** That's the kind of
review I do.
[WhatsApp +60 12-797 2969](https://wa.me/60127972969) ·
[[email protected]](mailto:[email protected]?subject=Open-source%20dependency%20review) ·
[hoelee.com](https://hoelee.com)
@@ -0,0 +1,113 @@
---
title: "如何在押注前验证一个开源依赖:我用的六项检查"
description: "六项检查,专门抓出「文档说得漂亮、代码里其实没有」的仓库:用 grep 找功能、读文档头部标注的目标分支、看清付费档位门槛,以及收款通道在不在你的国家能用。"
pubDate: 2026-09-29
category: devops
tags: ["open-source", "due-diligence", "oidc", "self-hosting", "docker"]
ogImage: "/og/vetting-an-open-source-dependency-before-you-bet-on-it.png"
banner: "/banners/vetting-an-open-source-dependency-before-you-bet-on-it.png"
draft: false
---
有人给我一份 1306 行的伙伴系统架构规格书。读起来像一份计划:前后一致、交叉引用、语气笃定。它指定一个自托管的开源引擎作为归因与佣金层,并且假定了那个引擎的三件事——它支持 OIDC,所以我现有的身份提供方能登录伙伴;它的佣金规则覆盖业务需要的五种形态;它的白标功能已经上线。
十分钟的 `curl` 加 `grep` 之后,三件里有两件是假的,第三件只对了一半。
这没什么稀奇。README 描述的是**产品**,仓库描述的是**软件**。这两者之间的落差,正是集成项目死掉的地方——而且在写任何代码之前,测量这个落差很便宜。
## 问题,写成我真的会去搜的那句话
**我怎么知道一个开源项目真的做了它文档宣称的事,然后才在它上面开始建东西?**
规格书是**关于别的软件**的一种主张。规格书越漂亮,它的假设就越像事实——这一份引用了来源、定义了稳定 ID、还画了一张「真源矩阵」。它唯一没做的事,就是核对链条下游那个引擎到底有没有实现被指派给它的功能。所以在安装任何东西之前,我去找那几个具体功能。
## 我一开始试的,以及为什么不够
**一、把 README 从头读到尾。** 它把流水线卖得很好:`click → identity → event → attribution → commission → payout`。但它一次都没说这个应用讲哪种认证协议。对一个你**必须要有**的功能来说,沉默不是中立——只是当页面其他部分都很有说服力时,一个「没被提到」很容易被眼睛滑过去。
**二、上网搜。** 好几条查询都没返回能用的结果。这个项目才几个月大,SEO 面很薄。对软件来说这没关系——源码**就是**文档。只是意味着阅读必须在仓库里进行,而不是在博客文章里。
**三、看到一份 78 KB 的功能设计文档,就假定功能已上线。** 它标着「FINAL, post-review」,列了三位评审,还带着日期。我把它当成了证据。这是三个推断里最糟的一个,也是最容易核对的一个。
## 真正定案的六项检查
### 1. 先看仓库的生命体征,再看任何功能宣称
```bash
curl -s -H "User-Agent: hermes" https://api.github.com/repos/<owner>/<repo> \
| grep -E '"created_at"|"pushed_at"|"stargazers_count"|"forks_count"|"open_issues_count"|"archived"|"spdx_id"'
```
返回的是:MIT、创建于 2026-04-23、最后推送 2026-09-10、8 个 star、3 个 fork、15 个 open issue、未归档。翻译过来就是:**真实、还在动、但非常小**——单一厂商,而且它同时卖托管档。现在我在开始评它的功能之前,先知道了我愿意把多少生意压在它身上。
### 2. 用 grep 去找功能,而不是用读的
```bash
grep -in 'oidc\|sso\|saml' README.md ARCHITECTURE.md docs/*.md | wc -l
```
零命中。这个应用用的是它自己的 magic-link 会话和按身份签发的 API key;它根本不讲 OIDC。就这一行输出,砍掉了计划里的一个阶段——「把门户挂到我们 SSO 后面」——否则这一步会在几周后才浮现,而且是在集成到一半、伙伴已经被邀请进来的时候。**用读的方式去找一个找不到的功能,就是你失去一个下午的方式;grep 让「不存在」这件事变得响亮。**
### 3. 读功能文档最上方那行「目标分支」
```bash
head -5 docs/white-label-custom-domains.md
```
`Target branch: multi-tenant`。这份文档描述的是**另一个分支**上的设计,不是你默认会安装的那个分支——它写的是在途工作,所以功能不在你会装下来的代码里。一份有评审、有日期、看起来已经完成的设计文档,完全可能描述着尚未上线的东西。这不是不诚实,设计文档本来就是这个样子。
### 4. 读数据模型,不要读功能清单
佣金那一节只列了两种规则类型——`percent`(带续期标记)与 `fixed`。业务规格书列了五种。这个落差不是 bug,而是我必须在**配置层**做的建模工作,不能指望产品自带:
| 规格书假设 | 引擎实际有的 |
|---|---|
| 首单奖励 | 对首个事件用一条 `fixed` 规则 |
| 按事件区分(`lead` vs `invoice_paid`) | 一个 campaign 一条规则——所以:两个 campaign |
| 续期佣金 | `percent` + `recurring: true` |
| 百分比 | `percent` ✅ |
| 固定金额 | `fixed` ✅ |
两种规则原语,五种行为——能做,但前提是你**一开始**就照这个设计,而不是在实现途中才发现。
### 5. 看清你需要的那项功能被锁在哪一档
同一个技术栈里还有个数据库工具,它的版本对照表很直白:
| 我需要的功能 | Community(免费) | Enterprise(付费) |
|---|---|---|
| API token,好让我自动化 | ✅ | ✅ |
| SSO / OIDC | ❌ | ✅ |
| 页面设计器、隐藏品牌、双因素 | ❌ | ✅ |
我想给「伙伴看到的那个门」用的东西(SSO)在商业门后面;而我想用来做自动化的东西(API token)是免费的。在围绕它做设计**之前**,先弄清每项需求落在墙的哪一侧。
### 6. 核对收款通道在你的国家能不能用
它的打款模型是 `stripe_connect | manual`,而自托管模式被写成「operator manages out-of-band」——也就是钱由你自己搬。Stripe Connect 不是马来西亚企业付钱给伙伴的方式,所以就是这一行决定了整个部署形态:自托管模式、手工打款、事件由我自己的订单系统推送过去,而不是依赖它内置的支付集成。这一行从来不会出现在营销页上,而它比任何功能都重要。
### 然后:在你需要之前,先问「兜底方案」
「如果我需要的功能不在,怎么办?」针对缺失的 SSO,答案是:不要把登录做进应用里,把登录放到应用**前面**。authentik 的 proxy provider 存在的意义就是保护「不支持 OIDC、SAML 或 LDAP 等原生认证协议的应用」,并且向上游注入 `X-authentik-username` 和 `X-authentik-groups` 头,于是反向代理就能承担准入判断。这就把「不支持 SSO」从一条否决理由,变成了一个下午的配置工作。
## 如果重来,我会怎么做
- **先写需求清单,再去找它。** 五行——**我**需要的功能——然后就搜这几个。从 README 的功能列表出发,等于用厂商的考卷给软件打分。
- **把「文档存在」和「功能上线」当成两件不同的事。** 去读那行目标分支。
- **用 grep,不要用读的。** 证明「有」很便宜;你要猎的是「没有」。
- **在决定之前先定兜底**,这样一个缺失的功能就只是一次设计调整,而不是一场意外。
- **把 commit 钉住。** 这个项目的 README 自己写着 API「stable but unversioned」,而且仓库里没有任何 OpenAPI 描述。没有可以编译期对齐的契约,那么 commit hash **就是**契约——我用到的端点要记进我自己的文档,升级前跑一遍回归。
## 结果
六个假设在大概十分钟的 `curl` 和 `grep` 里被修正,而当时一行集成代码都还没写。项目依然可行——只是换了形状:没有 OIDC、手工打款、钉住 commit,以及一份不再依赖某个未合并分支上功能的计划。
那份规格书乐观并没有错。它错在**没被验证过**——而这是整个项目里最便宜的修补项。
---
**你正在自托管技术栈上建东西,想在集成之前找人看一眼那些功能到底在不在?** 这正是我在做的复核工作。
[WhatsApp +60 12-797 2969](https://wa.me/60127972969) ·
[[email protected]](mailto:[email protected]?subject=Open-source%20dependency%20review) ·
[hoelee.com](https://hoelee.com)