Add post: How I Vet an Open-Source Dependency Before Betting On It (EN + ZH)
Deploy / build (push) Successful in 15s
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:
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -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';
|
||||
|
||||
@@ -219,6 +219,24 @@ TERMINALS['adding-english-mode-to-a-chinese-only-web-app'] = `
|
||||
<div class="line"><span class="prompt"> </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"> </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"> </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"> </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)
|
||||
Reference in New Issue
Block a user