diff --git a/docs/consultant-review/audit-tracker.json b/docs/consultant-review/audit-tracker.json index e584bdc..7135111 100644 --- a/docs/consultant-review/audit-tracker.json +++ b/docs/consultant-review/audit-tracker.json @@ -1,1328 +1,1341 @@ { - "adding-a-domain-to-someone-elses-mail-server": { - "slug": "adding-a-domain-to-someone-elses-mail-server", - "title": "Adding a Domain to Someone Else's Mail Server", - "zh_title": "给别人家的邮件服务器加一个域名", - "category": "notes", - "pub_date": "2025-05-06", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 7886, - "zh_chars": 3603 - }, - "adding-english-mode-to-a-chinese-only-web-app": { - "slug": "adding-english-mode-to-a-chinese-only-web-app", - "title": "No API for Browser Translation: Adding an English Mode to a Chinese-Only Web App", - "zh_title": "浏览器翻译没有 API:我给一个纯中文网页应用加上了英文模式", - "category": "engineering", - "pub_date": "2025-06-11", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 11914, - "zh_chars": 6253 - }, - "ai-furniture-compositing-with-flux-kontext": { - "slug": "ai-furniture-compositing-with-flux-kontext", - "title": "How I Built an AI Furniture Compositing Demo With FLUX.1 Kontext", - "zh_title": "我如何用 FLUX.1 Kontext 打造 AI 家具合成演示", - "category": "case-studies", - "pub_date": "2026-09-11", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 05:26", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", - "en_chars": 12512, - "zh_chars": 7237 - }, - "authentik-css-greater-than-bug": { - "slug": "authentik-css-greater-than-bug", - "title": "The Character That Silently Broke My authentik CSS", - "zh_title": "一个字符让我的 authentik CSS 静默失效", - "category": "devops", - "pub_date": "2026-09-20", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 9317, - "zh_chars": 5589 - }, - "authentik-forward-auth-gate-wasnt-live": { - "slug": "authentik-forward-auth-gate-wasnt-live", - "title": "The Forward-Auth Gate That Verified Perfectly — and Wasn't Live", - "zh_title": "验证完全正确的 Forward-Auth 网关——其实并未上线", - "category": "devops", - "pub_date": "2026-08-12", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 16526, - "zh_chars": 8746 - }, - "authentik-major-upgrade-gotchas": { - "slug": "authentik-major-upgrade-gotchas", - "title": "Authentik 2025.8 to 2026.8: The Breakable Parts Nobody Warns You About", - "zh_title": "Authentik 从 2025.8 升级到 2026.8:那些没人提醒你会坏掉的部分", - "category": "devops", - "pub_date": "2026-09-09", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 11710, - "zh_chars": 6147 - }, - "automating-cyberpanel-without-the-ui": { - "slug": "automating-cyberpanel-without-the-ui", - "title": "Automating CyberPanel Without the UI: Reverse-Engineering an Undocumented v2 API", - "zh_title": "不用界面管理 CyberPanel:逆向一个没有文档的 v2 API", - "category": "devops", - "pub_date": "2026-09-09", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 8474, - "zh_chars": 5205 - }, - "best-ai-video-generators-2026": { - "slug": "best-ai-video-generators-2026", - "title": "Best AI Video Generators in 2026: Free vs Paid, and My LTX-2.3 Test", - "zh_title": "2026 年最佳 AI 视频生成器:免费与付费对比,以及我的 LTX-2.3 实测", - "category": "ai", - "pub_date": "2026-09-11", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 03:00", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 24245, - "zh_chars": 12700 - }, - "calibrating-intel-quick-sync-hevc-quality": { - "slug": "calibrating-intel-quick-sync-hevc-quality", - "title": "Calibrating Intel Quick Sync HEVC: A Cliff at 28, and a Preset That Does Nothing", - "zh_title": "校准 Intel Quick Sync HEVC:28 与 30 之间的悬崖,和一个毫无作用的 preset", - "category": "tutorials", - "pub_date": "2026-10-14", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 10968, - "zh_chars": 5968 - }, - "chainlink-vrf-v2-lottery-contract": { - "slug": "chainlink-vrf-v2-lottery-contract", - "title": "An Automated Lottery Contract on Chainlink VRF v2.5 and Automation", - "zh_title": "一个基于 Chainlink VRF v2.5 与 Automation 的自动化彩票合约", - "category": "web3", - "pub_date": "2024-12-10", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:11", - "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", - "en_chars": 16252, - "zh_chars": 9707 - }, - "debugging-remotion-video-jitter": { - "slug": "debugging-remotion-video-jitter", - "title": "Debugging Remotion Video Jitter: Why Bass Scaling Ruins Background Transitions", - "zh_title": "排查 Remotion 视频抽搐:为什么在背景图挂载音频低频 Scale 会毁了转场", - "category": "engineering", - "pub_date": "2025-11-26", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 9836, - "zh_chars": 5653 - }, - "docker-reported-exit-0-the-oom-killer-disagreed": { - "slug": "docker-reported-exit-0-the-oom-killer-disagreed", - "title": "Docker Reported Exit 0. The Kernel Had Already OOM-Killed It.", - "zh_title": "Docker 报告退出码 0,内核却早已执行 OOM-Kill", - "category": "devops", - "pub_date": "2026-10-16", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 10200, - "zh_chars": 5488 - }, - "does-hevc-actually-shrink-your-files": { - "slug": "does-hevc-actually-shrink-your-files", - "title": "Does Re-encoding to HEVC Actually Shrink Your Files? I Measured It.", - "zh_title": "转成 HEVC 到底会不会让文件变小?我实测了", - "category": "tutorials", - "pub_date": "2026-10-08", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 8042, - "zh_chars": 4159 - }, - "embedding-fonts-so-a-pdf-looks-the-same-everywhere": { - "slug": "embedding-fonts-so-a-pdf-looks-the-same-everywhere", - "title": "Embedding Fonts So a PDF Looks the Same on Every Machine", - "zh_title": "把字体嵌进 PDF,让它在任何机器上都长一样", - "category": "engineering", - "pub_date": "2026-10-15", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 8591, - "zh_chars": 3747 - }, - "every-player-stuttered-the-file-was-fine": { - "slug": "every-player-stuttered-the-file-was-fine", - "title": "Every Player Stuttered on the Same Video. The File Was Blameless.", - "zh_title": "每个播放器都在同一个视频上卡顿,而文件本身是无辜的", - "category": "devops", - "pub_date": "2026-10-08", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 8459, - "zh_chars": 4296 - }, - "five-step-ai-roadmap-what-i-actually-run": { - "slug": "five-step-ai-roadmap-what-i-actually-run", - "title": "The Five-Step AI Roadmap Everyone Shares — and What I Actually Run in Production", - "zh_title": "全网都在转的 AI 五步学习路线 —— 我真正在生产里跑的是哪几步", - "category": "ai", - "pub_date": "2026-10-13", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 03:00", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 13155, - "zh_chars": 5483 - }, - "from-audio-to-a-4-minute-ai-music-video": { - "slug": "from-audio-to-a-4-minute-ai-music-video", - "title": "From Audio to a 4-Minute Music Video: Orchestrating Gemini, AI Diffusion, and Filmora Editing", - "zh_title": "从一首歌到 4 分钟完整 MV:我如何用 Gemini、AI 生成与 Filmora 剪辑串起全流程实操", - "category": "case-studies", - "pub_date": "2026-10-25", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 05:26", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", - "en_chars": 11027, - "zh_chars": 5168 - }, - "fully-on-chain-svg-nfts": { - "slug": "fully-on-chain-svg-nfts", - "title": "Fully On-Chain SVG NFTs: Putting the Artwork Inside the Contract", - "zh_title": "全链上 SVG NFT:把艺术作品放进合约里", - "category": "web3", - "pub_date": "2024-10-08", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:11", - "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", - "en_chars": 16205, - "zh_chars": 10281 - }, - "generating-full-length-songs-with-yue2-comfyui": { - "slug": "generating-full-length-songs-with-yue2-comfyui", - "title": "Generating Full-Length Songs with YuE2 BF16: Overcoming Duration Limits and Audio Truncation", - "zh_title": "用 YuE2 BF16 生成完整长歌:突破时长限制与音频硬截断", - "category": "tutorials", - "pub_date": "2026-10-26", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 06:04", - "notes": "第3批微调项落地并通过:时长校正283.1s/单调时间戳与防死锁/专业人设去二次元俚语/B2B转化增强", - "en_chars": 134821, - "zh_chars": 127730 - }, - "generating-subtitles-for-songs-without-timestamps": { - "slug": "generating-subtitles-for-songs-without-timestamps", - "title": "Generating Subtitles for Songs That Ship Without Timestamps", - "zh_title": "给没有时间戳的歌曲自动生成字幕", - "category": "tutorials", - "pub_date": "2026-10-20", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:11", - "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", - "en_chars": 47576, - "zh_chars": 42677 - }, - "giving-ai-your-mouse-and-keyboard-safely": { - "slug": "giving-ai-your-mouse-and-keyboard-safely", - "title": "Giving AI Your Mouse and Keyboard: Safe Guardrails for Windows Desktop MCP", - "zh_title": "把鼠标和键盘交给 AI 之前:Windows 桌面 MCP 的安全边界与实战防线", - "category": "ai", - "pub_date": "2025-10-22", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 03:00", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 5906, - "zh_chars": 3149 - }, - "hardening-a-tor-onion-service": { - "slug": "hardening-a-tor-onion-service", - "title": "Hardening a Tor Onion Service: What Actually Matters", - "zh_title": "加固 Tor 洋葱服务:真正重要的是什么", - "category": "devops", - "pub_date": "2025-12-10", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 14907, - "zh_chars": 7041 - }, - "hello-world": { - "slug": "hello-world", - "title": "Hello, World — About This Blog", - "zh_title": "你好,世界 —— 关于这个博客", - "category": "notes", - "pub_date": "2024-09-24", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 1938, - "zh_chars": 1074 - }, - "how-i-built-an-automated-music-video-pipeline": { - "slug": "how-i-built-an-automated-music-video-pipeline", - "title": "How I Built an Automated Music Video Pipeline with Remotion and Dockerized YouTube CDP", - "zh_title": "我是如何用 Remotion 与 Docker CDP 打造全自动 MV 生成与多语种油管发布流的", - "category": "case-studies", - "pub_date": "2025-04-18", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 05:26", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", - "en_chars": 9265, - "zh_chars": 4931 - }, - "how-i-built-the-digikedai-telegram-bot": { - "slug": "how-i-built-the-digikedai-telegram-bot", - "title": "How I Built the DigiKedai Telegram AI Bot", - "zh_title": "我是如何构建 DigiKedai Telegram AI 客服机器人的", - "category": "case-studies", - "pub_date": "2026-09-06", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 05:26", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", - "en_chars": 10719, - "zh_chars": 5271 - }, - "how-i-host-this-blog": { - "slug": "how-i-host-this-blog", - "title": "How I Host This Blog: Astro, Gitea Actions, and Self-Hosted CI/CD", - "zh_title": "我是如何托管这个博客的:Astro、Gitea Actions 与自托管 CI/CD", - "category": "case-studies", - "pub_date": "2026-09-04", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 05:26", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", - "en_chars": 5908, - "zh_chars": 4145 - }, - "how-i-made-my-own-songs-with-suno-ai": { - "slug": "how-i-made-my-own-songs-with-suno-ai", - "title": "How I Made My Own Songs with Suno AI", - "zh_title": "我用 Suno AI 做出自己的歌", - "category": "case-studies", - "pub_date": "2026-09-11", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 05:26", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", - "en_chars": 16935, - "zh_chars": 11737 - }, - "how-i-vetted-20-vps-providers-with-parallel-subagents": { - "slug": "how-i-vetted-20-vps-providers-with-parallel-subagents", - "title": "How I Vetted 20 VPS Providers in Three Hours with Parallel Subagents", - "zh_title": "如何用并行子代理在三小时内核查 20 家 VPS 服务商", - "category": "case-studies", - "pub_date": "2025-11-12", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 8593, - "zh_chars": 3613 - }, - "how-to-verify-a-hosting-provider-before-you-buy": { - "slug": "how-to-verify-a-hosting-provider-before-you-buy", - "title": "How to Verify a Hosting Provider Before You Buy", - "zh_title": "购买主机前,如何核实一家服务商的真实情况", - "category": "tutorials", - "pub_date": "2025-11-26", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:11", - "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", - "en_chars": 7710, - "zh_chars": 3405 - }, - "i-let-an-agent-manage-my-shopee-vouchers": { - "slug": "i-let-an-agent-manage-my-shopee-vouchers", - "title": "I Let an Agent Manage My Shopee Vouchers — And It Created One I Never Approved", - "zh_title": "我让 AI 助手管我的 Shopee 优惠券 —— 它建了一张我没批准的券", - "category": "ai", - "pub_date": "2025-07-08", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 03:00", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 9981, - "zh_chars": 4824 - }, - "it-reported-success-nothing-had-changed": { - "slug": "it-reported-success-nothing-had-changed", - "title": "It Reported Success. Nothing Had Changed.", - "zh_title": "它报告了成功,可实际上什么都没变", - "category": "devops", - "pub_date": "2026-10-08", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 04:09", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", - "en_chars": 8349, - "zh_chars": 3886 - }, - "jpa-version-field-lost-update": { - "slug": "jpa-version-field-lost-update", - "title": "The JPA Field That Turns a Silent Lost Update Into a 409", - "zh_title": "那个把静默丢失更新变成 409 的 JPA 字段:@Version 乐观锁", - "category": "engineering", - "pub_date": "2026-08-19", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 12536, - "zh_chars": 7506 - }, - "loop-engineering-without-a-coding-agent": { - "slug": "loop-engineering-without-a-coding-agent", - "title": "Loop Engineering Without a Coding Agent: The 11 Cron Jobs That Run My Infrastructure", - "zh_title": "没有 Coding Agent 的 Loop Engineering:撑起我这套基础设施的 11 个定时任务", - "category": "case-studies", - "pub_date": "2026-10-01", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 14249, - "zh_chars": 6609 - }, - "migrating-codeigniter-iis-to-openlitespeed": { - "slug": "migrating-codeigniter-iis-to-openlitespeed", - "title": "Migrating a CodeIgniter 4 App From IIS to OpenLiteSpeed", - "zh_title": "把 CodeIgniter 4 应用从 IIS 迁移到 OpenLiteSpeed", - "category": "engineering", - "pub_date": "2026-09-27", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 14943, - "zh_chars": 8425 - }, - "my-backup-was-using-someone-elses-api-quota": { - "slug": "my-backup-was-using-someone-elses-api-quota", - "title": "My Backup Was Crawling Because I Was Using Someone Else's API Quota", - "zh_title": "我的备份爬得比爬虫还慢,因为我在用别人的 API 配额", - "category": "notes", - "pub_date": "2026-10-15", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 8572, - "zh_chars": 4527 - }, - "n8n-v1-to-v2-upgrade-gotchas": { - "slug": "n8n-v1-to-v2-upgrade-gotchas", - "title": "Upgrading n8n v1 to v2: Seven Deprecations in One Log File", - "zh_title": "n8n v1 升级到 v2:一个日志文件里的七项废弃警告", - "category": "devops", - "pub_date": "2026-09-18", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 04:09", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", - "en_chars": 12262, - "zh_chars": 6712 - }, - "nocodb-sso-is-a-licensed-feature": { - "slug": "nocodb-sso-is-a-licensed-feature", - "title": "NocoDB SSO Is a Licensed Feature — and the Env Vars Won't Tell You", - "zh_title": "NocoDB SSO 是授权功能:环境变量不会告诉你的事", - "category": "notes", - "pub_date": "2026-09-29", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 9937, - "zh_chars": 5421 - }, - "one-hostname-public-tracker-sso-dashboard": { - "slug": "one-hostname-public-tracker-sso-dashboard", - "title": "One Hostname for a Public Tracker and an SSO-Gated Dashboard", - "zh_title": "公开的埋点与要 SSO 的仪表盘,怎么共用一个域名", - "category": "devops", - "pub_date": "2026-09-30", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 04:09", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", - "en_chars": 14400, - "zh_chars": 6837 - }, - "one-prometheus-for-unraid-synology-and-a-vps": { - "slug": "one-prometheus-for-unraid-synology-and-a-vps", - "title": "One Prometheus for unRaid, a Synology NAS, and a VPS: What I Got Wrong", - "zh_title": "用一个 Prometheus 监控 unRaid、群晖 NAS 和 VPS:我做错的四件事", - "category": "case-studies", - "pub_date": "2026-07-29", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 8358, - "zh_chars": 4690 - }, - "passbolt-hang-three-failure-modes": { - "slug": "passbolt-hang-three-failure-modes", - "title": "Passbolt UI Kept Hanging — Three Failure Modes From One Missing Config File", - "zh_title": "Passbolt 界面反复卡死——一个缺失的配置文件引发的三种故障模式", - "category": "devops", - "pub_date": "2026-01-07", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 04:09", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", - "en_chars": 7864, - "zh_chars": 4187 - }, - "patching-workbench-26-for-mariadb": { - "slug": "patching-workbench-26-for-mariadb", - "title": "When Your Database Client Lies to You: Patching Workbench 26 for MariaDB", - "zh_title": "当数据库客户端对你撒谎:为 MariaDB 修补 Workbench 26", - "category": "devops", - "pub_date": "2026-09-19", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 04:09", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", - "en_chars": 15965, - "zh_chars": 9501 - }, - "php2shell-patching-every-php-runtime-i-manage": { - "slug": "php2shell-patching-every-php-runtime-i-manage", - "title": "PHP2Shell: I Measured Every PHP Runtime I Manage. All of Them Were Vulnerable.", - "zh_title": "PHP2Shell:我管的每台机器,PHP 运行时全都中了", - "category": "devops", - "pub_date": "2026-10-23", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 04:09", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", - "en_chars": 22354, - "zh_chars": 12531 - }, - "plugging-windows-desktop-control-into-four-ai-agents": { - "slug": "plugging-windows-desktop-control-into-four-ai-agents", - "title": "Plugging Windows Desktop Control into Four AI Agents: Hermes, OpenCode, Claw, and Claude", - "zh_title": "把 Windows 桌面控制接进 4 款 AI Agent:Hermes、OpenCode、Claw 与 Claude 的实战架构", - "category": "ai", - "pub_date": "2025-12-23", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 03:00", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 6554, - "zh_chars": 5030 - }, - "putting-a-front-door-on-an-app-you-cant-modify": { - "slug": "putting-a-front-door-on-an-app-you-cant-modify", - "title": "Putting a Front Door on an App You Can't Modify", - "zh_title": "给自己改不了源码的应用装一扇前门", - "category": "devops", - "pub_date": "2025-06-24", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 04:09", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", - "en_chars": 13927, - "zh_chars": 7415 - }, - "read-only-nocodb-dashboard-for-a-remote-database": { - "slug": "read-only-nocodb-dashboard-for-a-remote-database", - "title": "A Read-Only NocoDB Dashboard for a Database on Another Machine", - "zh_title": "给另一台机器上的数据库配一个 NocoDB 只读看板", - "category": "devops", - "pub_date": "2026-09-29", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 04:09", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", - "en_chars": 12009, - "zh_chars": 6287 - }, - "reading-a-containers-own-api-docs": { - "slug": "reading-a-containers-own-api-docs", - "title": "Read a Container's Own API Docs Instead of the Vendor's", - "zh_title": "与其看供应商文档,不如直接读容器自带的 API 文档", - "category": "notes", - "pub_date": "2025-10-01", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 8382, - "zh_chars": 5489 - }, - "replacing-rdpguard-with-ipban": { - "slug": "replacing-rdpguard-with-ipban", - "title": "Replacing RDPGuard With IPBan: The Traps Nobody Documents", - "zh_title": "用 IPBan 替换 RDPGuard:没人写出来的三个坑", - "category": "devops", - "pub_date": "2026-09-19", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 04:09", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", - "en_chars": 13131, - "zh_chars": 7795 - }, - "running-ai-video-locally-on-a-12gb-gpu": { - "slug": "running-ai-video-locally-on-a-12gb-gpu", - "title": "Running AI Video Locally on a 12 GB GPU: Real Numbers for Wan 2.2 and SeedVR2", - "zh_title": "12GB 显卡跑本地 AI 视频:Wan 2.2 加 SeedVR2 的真实数据", - "category": "ai", - "pub_date": "2026-10-20", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 03:00", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 18508, - "zh_chars": 12878 - }, - "running-production-infrastructure-solo": { - "slug": "running-production-infrastructure-solo", - "title": "Running Production Infrastructure Solo: 162 Containers Across Three Hosts", - "zh_title": "一个人跑生产基础设施:三台主机、162 个容器", - "category": "case-studies", - "pub_date": "2026-10-06", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 7975, - "zh_chars": 3722 - }, - "running-tts-as-a-service-with-token-sidecars": { - "slug": "running-tts-as-a-service-with-token-sidecars", - "title": "I Ran a Text-to-Speech Service for a Year on Two Cron Containers", - "zh_title": "我用两个 Cron 容器跑了一年的语音合成服务", - "category": "ai", - "pub_date": "2026-09-18", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 03:00", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 14141, - "zh_chars": 7929 - }, - "scraping-bot-walled-marketplace-warm-browser-session": { - "slug": "scraping-bot-walled-marketplace-warm-browser-session", - "title": "Scraping a Bot-Walled Marketplace With a Warm Browser Session", - "zh_title": "用「热浏览器会话」爬取有反爬墙的电商网站", - "category": "engineering", - "pub_date": "2026-09-13", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 11313, - "zh_chars": 5734 - }, - "self-healing-digital-goods-entitlements": { - "slug": "self-healing-digital-goods-entitlements", - "title": "Self-Healing Access Control for Digital Goods: NocoDB, n8n, and AList", - "zh_title": "数字商品的自愈式访问控制:NocoDB、n8n 与 AList", - "category": "case-studies", - "pub_date": "2026-09-18", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 14765, - "zh_chars": 6819 - }, - "self-hosted-speech-to-text-api": { - "slug": "self-hosted-speech-to-text-api", - "title": "Your Own Speech-to-Text Server: Faster Than Typing, Private by Default", - "zh_title": "自建语音转文字服务:打字速度的 3 倍以上,而且数据不出内网", - "category": "case-studies", - "pub_date": "2026-09-19", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 14708, - "zh_chars": 7794 - }, - "self-hosted-tts-cosyvoice2-n8n-gpu": { - "slug": "self-hosted-tts-cosyvoice2-n8n-gpu", - "title": "How I Replaced Cloud TTS with a Self-Hosted Neural Voice Stack on an RTX 3060", - "zh_title": "从云端转本地:在 12GB 显卡与群晖 N8N 上搭建中英混读高保真 TTS 语音栈", - "category": "case-studies", - "pub_date": "2026-10-22", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 8913, - "zh_chars": 4773 - }, - "self-hosting-mem0-memory-stack": { - "slug": "self-hosting-mem0-memory-stack", - "title": "How I Self-Hosted a Mem0 Memory Stack (So My AI Agent Remembers)", - "zh_title": "我如何在私有存储节点上自托管 Mem0 记忆栈(让 AI 助手拥有持久记忆)", - "category": "case-studies", - "pub_date": "2026-09-16", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 8057, - "zh_chars": 4849 - }, - "shipping-an-ai-photo-editor-as-a-wordpress-plugin": { - "slug": "shipping-an-ai-photo-editor-as-a-wordpress-plugin", - "title": "Shipping an AI Photo Editor as a WordPress Plugin: 22 Versions of Hard Lessons", - "zh_title": "把 AI 修图工具做成 WordPress 插件:22 个版本换来的经验", - "category": "case-studies", - "pub_date": "2026-09-18", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 13879, - "zh_chars": 6861 - }, - "slashing-agent-context-with-a-code-graph": { - "slug": "slashing-agent-context-with-a-code-graph", - "title": "Slashing Agent Context and Token Churn With a Code Graph", - "zh_title": "别再把整个 Repo 喂给 Agent:用 Code Graph 斩断 Token 膨胀与遗忘陷阱", - "category": "ai", - "pub_date": "2026-04-28", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 03:00", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 8122, - "zh_chars": 5040 - }, - "smartctl-exit-code-32-skips-the-disks-that-matter": { - "slug": "smartctl-exit-code-32-skips-the-disks-that-matter", - "title": "smartctl Exit Code 32: The Code That Skips the Disks You Care About", - "zh_title": "smartctl 退出码 32:专门跳过你最该看的硬盘的那个“错误”", - "category": "notes", - "pub_date": "2026-04-14", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 5475, - "zh_chars": 2881 - }, - "space-reclamation-wasnt-stuck-it-was-waiting": { - "slug": "space-reclamation-wasnt-stuck-it-was-waiting", - "title": "Space Reclamation Wasn't Stuck. It Was Waiting for Tomorrow at 09:29.", - "zh_title": "空间回收没卡住,它只是在等明天早上 09:29", - "category": "devops", - "pub_date": "2026-10-14", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 6574, - "zh_chars": 3505 - }, - "stop-writing-agent-prompts-for-deterministic-work": { - "slug": "stop-writing-agent-prompts-for-deterministic-work", - "title": "Stop Writing Agent Prompts for Deterministic Work", - "zh_title": "确定性的活,别再用 Agent Prompt 去跑", - "category": "notes", - "pub_date": "2026-10-01", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 05:22", - "notes": "外部顾问审阅通过并落地修改", - "en_chars": 4951, - "zh_chars": 2216 - }, - "syncing-a-self-improving-ai-agent-across-machines": { - "slug": "syncing-a-self-improving-ai-agent-across-machines", - "title": "Syncing a Self-Improving AI Agent Across Machines", - "zh_title": "在多台机器之间同步一个会自我学习的 AI Agent", - "category": "case-studies", - "pub_date": "2026-02-18", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 04:22", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", - "en_chars": 8861, - "zh_chars": 4541 - }, - "synology-api-401-with-the-correct-password": { - "slug": "synology-api-401-with-the-correct-password", - "title": "Why Your Synology API Login Returns 401 With the Correct Password", - "zh_title": "密码正确,Synology API 登录却返回 401,问题出在哪", - "category": "notes", - "pub_date": "2025-09-10", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 5053, - "zh_chars": 3063 - }, - "synology-spreadsheet-api-is-a-container": { - "slug": "synology-spreadsheet-api-is-a-container", - "title": "The Synology Spreadsheet API Is a Container, Not an API Endpoint", - "zh_title": "Synology 电子表格 API 是一个容器,不是一个 API 端点", - "category": "devops", - "pub_date": "2025-08-20", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 16101, - "zh_chars": 9200 - }, - "that-dying-ssd-was-just-a-bad-sata-cable": { - "slug": "that-dying-ssd-was-just-a-bad-sata-cable", - "title": "I Was About to RMA This SSD — the Fault Was a SATA Cable", - "zh_title": "我差点把这颗 SSD 送修——真正坏的是 SATA 数据线", - "category": "devops", - "pub_date": "2026-09-16", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 7471, - "zh_chars": 3940 - }, - "the-cause-was-trim-not-the-ssds": { - "slug": "the-cause-was-trim-not-the-ssds", - "title": "The Corruption Came Back on Different Drives — the Cause Was TRIM, Not the SSDs", - "zh_title": "换了不同的硬盘,同样的损坏又来了——真凶是 TRIM,不是 SSD", - "category": "devops", - "pub_date": "2026-09-18", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 13042, - "zh_chars": 7310 - }, - "the-nocodb-attachment-that-wouldnt-update": { - "slug": "the-nocodb-attachment-that-wouldnt-update", - "title": "The NocoDB Attachment That Wouldn't Update (and a Regex That Missed)", - "zh_title": "NocoDB 附件字段怎么改都不生效,和一个漏网的正则", - "category": "notes", - "pub_date": "2026-02-04", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 3798, - "zh_chars": 2312 - }, - "the-single-writer-rule-for-coding-agents": { - "slug": "the-single-writer-rule-for-coding-agents", - "title": "The Single-Writer Rule: Orchestrating Coding Agents Without Multi-Agent Chaos", - "zh_title": "代码只让一个 AI 改:我是如何避开多 Agent 协同踩坑的「单写多审」架构", - "category": "case-studies", - "pub_date": "2026-08-26", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 13331, - "zh_chars": 6954 - }, - "the-upstream-was-deleted-then-came-back-rewritten": { - "slug": "the-upstream-was-deleted-then-came-back-rewritten", - "title": "The Upstream Was Deleted, Then Came Back as a Rewrite", - "zh_title": "上游被删了,然后带着一个重写版回来", - "category": "devops", - "pub_date": "2026-10-06", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 11636, - "zh_chars": 5229 - }, - "two-ways-a-javascript-file-stops-working-and-looks-fine": { - "slug": "two-ways-a-javascript-file-stops-working-and-looks-fine", - "title": "Two Ways a JavaScript File Stops Working and Looks Fine", - "zh_title": "JavaScript 文件静默失效的两种方式:而页面看上去一切正常", - "category": "engineering", - "pub_date": "2025-02-20", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 8293, - "zh_chars": 3881 - }, - "unraid-stop-array-hangs-on-swapfile": { - "slug": "unraid-stop-array-hangs-on-swapfile", - "title": "Why Unraid Hangs When You Stop the Array With a Swapfile — and the Fix", - "zh_title": "Unraid 停止阵列时卡死的真相:Swapfile 锁住挂载点 — 附修复方案", - "category": "devops", - "pub_date": "2026-09-15", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 10470, - "zh_chars": 6376 - }, - "upgrading-codeigniter-46-to-47": { - "slug": "upgrading-codeigniter-46-to-47", - "title": "Upgrading CodeIgniter 4.6 to 4.7: The Breaking Changes the Guide Misses", - "zh_title": "CodeIgniter 4.6 升级到 4.7:官方指南没说到的破坏性变更", - "category": "notes", - "pub_date": "2026-09-27", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 05:22", - "notes": "外部顾问审阅通过并落地修改", - "en_chars": 9469, - "zh_chars": 5411 - }, - "uploading-large-videos-to-youtube-via-cdp": { - "slug": "uploading-large-videos-to-youtube-via-cdp", - "title": "Uploading 100MB+ Videos to YouTube via CDP: The Docker Archive Pattern", - "zh_title": "用 CDP 自动化上传百兆视频到 YouTube:跨越 DevTools 内存墙的 Docker 归档法", - "category": "devops", - "pub_date": "2025-08-22", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 11267, - "zh_chars": 7173 - }, - "using-chinese-llm-apis-from-malaysia": { - "slug": "using-chinese-llm-apis-from-malaysia", - "title": "Using Chinese LLM APIs From Malaysia: What Actually Works", - "zh_title": "从马来西亚使用中国大模型 API:真正可行的方法", - "category": "ai", - "pub_date": "2026-09-11", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 03:00", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 7540, - "zh_chars": 3536 - }, - "verifying-a-pdf-report-page-by-page": { - "slug": "verifying-a-pdf-report-page-by-page", - "title": "Verifying a 19-Page PDF Report Page by Page", - "zh_title": "逐页验证一份 19 页的 PDF 报告", - "category": "engineering", - "pub_date": "2026-09-28", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 12525, - "zh_chars": 6685 - }, - "vetting-an-open-source-dependency-before-you-bet-on-it": { - "slug": "vetting-an-open-source-dependency-before-you-bet-on-it", - "title": "How I Vet an Open-Source Dependency Before Betting On It", - "zh_title": "如何在押注前验证一个开源依赖:我用的六项检查", - "category": "devops", - "pub_date": "2026-09-29", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 8512, - "zh_chars": 4201 - }, - "when-smart-says-healthy-but-your-raid-is-corrupting-data": { - "slug": "when-smart-says-healthy-but-your-raid-is-corrupting-data", - "title": "When SMART Says Healthy and Your RAID Is Silently Corrupting Data", - "zh_title": "SMART 显示健康,RAID 却在悄悄损坏数据", - "category": "devops", - "pub_date": "2026-09-16", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 14160, - "zh_chars": 6246 - }, - "when-to-give-your-agent-physical-clicks": { - "slug": "when-to-give-your-agent-physical-clicks", - "title": "When to Give Your Agent Physical Clicks (And When to Never Touch Them)", - "zh_title": "什么时候该让 AI 操控物理鼠标(以及什么时候绝对不要用)", - "category": "case-studies", - "pub_date": "2026-05-28", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 6882, - "zh_chars": 3608 - }, - "why-500000-lines-of-commit-churn-only-left-230000-in-production": { - "slug": "why-500000-lines-of-commit-churn-only-left-230000-in-production", - "title": "I Committed 518,000 Lines in 30 Days. Production Kept 235,000.", - "zh_title": "30 天提交 51.8 万行,生产环境只留下 23.5 万行", - "category": "engineering", - "pub_date": "2026-10-18", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 8949, - "zh_chars": 4197 - }, - "why-chrome-forgets-its-tabs-in-a-container": { - "slug": "why-chrome-forgets-its-tabs-in-a-container", - "title": "Why Chrome Forgets Its Tabs in a Container — And How I Fixed It", - "zh_title": "为什么容器里的 Chrome 会忘记标签页 —— 以及我是怎么修好的", - "category": "devops", - "pub_date": "2026-09-29", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 15430, - "zh_chars": 9234 - }, - "why-i-built-a-video-knowledge-base": { - "slug": "why-i-built-a-video-knowledge-base", - "title": "Why I Built a Video Knowledge Base: Turning Saved Short Videos into Searchable Notes", - "zh_title": "为什么我要做视频精读知识库:把收藏夹吃灰的短视频变成可检索笔记", - "category": "case-studies", - "pub_date": "2024-11-28", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 5770, - "zh_chars": 2924 - }, - "why-i-still-bought-a-local-gpu": { - "slug": "why-i-still-bought-a-local-gpu", - "title": "Why I Still Bought a GPU to Run AI When Claude and GPT Are Obviously Stronger", - "zh_title": "Claude 和 GPT 明明更强,我为什么还是买了 GPU 跑本地 AI", - "category": "ai", - "pub_date": "2026-09-21", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 05:26", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", - "en_chars": 11811, - "zh_chars": 4924 - }, - "why-is-my-tts-model-repeating-percent": { - "slug": "why-is-my-tts-model-repeating-percent", - "title": "Why Is My TTS Model Repeating \\\"Percent\\\"? The URL-Encoding Trap in Speech Pipelines", - "zh_title": "为什么本地语音合成一直在念 “Percent”?语音管线里的 URL 编码与沙盒避坑记", - "category": "notes", - "pub_date": "2026-10-22", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 7728, - "zh_chars": 4968 - }, - "why-multi-agent-coding-fails": { - "slug": "why-multi-agent-coding-fails", - "title": "Why Multi-Agent Coding Fails: The Telephone Game and Architectural Drift", - "zh_title": "为什么多个 AI 一起写代码总会烂尾?传话失真与隐式决策困境", - "category": "notes", - "pub_date": "2025-07-29", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 6364, - "zh_chars": 3353 - }, - "why-my-on-chain-nft-art-changed-on-windows": { - "slug": "why-my-on-chain-nft-art-changed-on-windows", - "title": "Why My On-Chain NFT Art Changed When I Cloned It on Windows", - "zh_title": "为什么在 Windows 上克隆后,我的链上 NFT 图像变了", - "category": "web3", - "pub_date": "2026-08-19", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:11", - "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", - "en_chars": 8423, - "zh_chars": 4585 - }, - "why-telegram-bot-notifications-die": { - "slug": "why-telegram-bot-notifications-die", - "title": "Why Telegram Bot Notifications Die: IPv6, DNS, and a 400 That Took a Day", - "zh_title": "Telegram 机器人通知为什么发不出去:IPv6、DNS 和一个追查了一整天的 400", - "category": "devops", - "pub_date": "2026-03-25", - "status": "approved", - "batch_id": "Batch-02", - "approved_at": "2026-10-27 06:10", - "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", - "en_chars": 10753, - "zh_chars": 6005 - }, - "why-your-cdp-clicks-silently-fail": { - "slug": "why-your-cdp-clicks-silently-fail", - "title": "Why Your CDP Clicks Silently Fail: Smooth Scroll, Minimised Windows, and JS-Dispatched Events", - "zh_title": "为什么你的 CDP 点击会静默失效:平滑滚动、最小化窗口与 JS 派发事件", - "category": "devops", - "pub_date": "2025-03-19", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 8054, - "zh_chars": 4536 - }, - "why-your-grafana-dashboard-shows-no-data": { - "slug": "why-your-grafana-dashboard-shows-no-data", - "title": "Why Your Grafana Dashboard Shows No Data (When Prometheus Is Fine)", - "zh_title": "Prometheus 明明有数据,Grafana 面板却全是 No data 的原因", - "category": "devops", - "pub_date": "2026-05-17", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 7456, - "zh_chars": 3919 - }, - "why-your-headless-browser-cant-scrape-everything": { - "slug": "why-your-headless-browser-cant-scrape-everything", - "title": "Why Your Headless Browser Can't Scrape Everything", - "zh_title": "为什么你的无头浏览器不是万能爬虫", - "category": "case-studies", - "pub_date": "2026-01-21", - "status": "approved", - "batch_id": "Batch-03", - "approved_at": "2026-10-27 03:49", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", - "en_chars": 8076, - "zh_chars": 3792 - }, - "why-your-windows-mcp-calls-silently-fail": { - "slug": "why-your-windows-mcp-calls-silently-fail", - "title": "Why Your Windows Desktop MCP Calls Silently Fail: Taming the 10-Second Idle Gate", - "zh_title": "为什么你的 Windows 桌面 MCP 调用会静默失败:攻破 10 秒空闲门禁与避坑实录", - "category": "notes", - "pub_date": "2026-03-12", - "status": "approved", - "batch_id": "Batch-04", - "approved_at": "2026-10-27 05:21", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", - "en_chars": 5663, - "zh_chars": 3857 - }, - "your-disk-full-alert-is-lying": { - "slug": "your-disk-full-alert-is-lying", - "title": "Your Disk-Full Alert Is Lying: Stop Alerting on Percentages", - "zh_title": "你的“磁盘将满”告警在说谎:别再按百分比告警", - "category": "devops", - "pub_date": "2026-06-24", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 06:09", - "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", - "en_chars": 6139, - "zh_chars": 3090 - }, - "yue2-on-a-12gb-gpu-songs-that-lost-their-endings": { - "slug": "yue2-on-a-12gb-gpu-songs-that-lost-their-endings", - "title": "Why My AI Songs Kept Losing Their Endings: Running YuE2 on a 12 GB GPU", - "zh_title": "为什么我的 AI 歌曲总是丢结尾:在 12GB 显卡上跑 YuE2", - "category": "ai", - "pub_date": "2026-10-20", - "status": "approved", - "batch_id": "Batch-01", - "approved_at": "2026-10-27 05:26", - "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", - "en_chars": 28955, - "zh_chars": 24414 - }, - "when-lowering-crf-does-nothing-maxrate-deadlock": { - "slug": "when-lowering-crf-does-nothing-maxrate-deadlock", - "title": "When Lowering CRF Does Nothing: The -maxrate Ceiling and the SSIM Quality Deadlock", - "zh_title": "当降低 CRF 毫无效果:视频转码中 -maxrate 封顶与 SSIM 质量门禁的死锁", - "category": "devops", - "pub_date": "2026-10-27", - "status": "approved", - "batch_id": "Batch-07", - "approved_at": "2026-10-28 09:32", - "notes": "Batch-07 外部顾问审阅「需微调 → 通过」(事实4.8/人设4.7/CRO4.8/OPSEC4.7)。已落地:清日志残留符号、499 跨跳因果改写、I/O 余量措辞、details class+行数、双语第二人称清零、three→four、中文与含反引号标题补显式锚点;详见 report-batch-07.md", - "en_chars": 8970, - "zh_chars": 5317 - }, - "why-ffmpeg-map-0-breaks-your-mp4-and-mkv-remux": { - "slug": "why-ffmpeg-map-0-breaks-your-mp4-and-mkv-remux", - "title": "Why ffmpeg -map 0 Breaks Your Remux: The Hidden Matrix of Multi-Track MP4 and Matroska", - "zh_title": "为什么 ffmpeg -map 0 会搞崩混流:跨容器多音轨、字幕与字体的深水区", - "category": "engineering", - "pub_date": "2026-10-27", - "status": "approved", - "batch_id": "Batch-07", - "approved_at": "2026-10-28 09:32", - "notes": "Batch-07 外部顾问审阅「需微调 → 通过」(事实4.8/人设4.7/CRO4.8/OPSEC4.7)。已落地:清日志残留符号、499 跨跳因果改写、I/O 余量措辞、details class+行数、双语第二人称清零、three→four、中文与含反引号标题补显式锚点;详见 report-batch-07.md", - "en_chars": 11008, - "zh_chars": 7574 - }, - "when-a-proxy-timeout-returns-html-instead-of-json": { - "slug": "when-a-proxy-timeout-returns-html-instead-of-json", - "title": "When a Proxy Timeout Returns HTML Instead of JSON: A 499 Post-Mortem", - "zh_title": "反代超时返回 HTML,前端报 SyntaxError:一次 499 事故复盘", - "category": "engineering", - "pub_date": "2026-10-28", - "status": "approved", - "batch_id": "Batch-07", - "approved_at": "2026-10-28 09:32", - "notes": "Batch-07 外部顾问审阅「需微调 → 通过」(事实4.8/人设4.7/CRO4.8/OPSEC4.7)。已落地:清日志残留符号、499 跨跳因果改写、I/O 余量措辞、details class+行数、双语第二人称清零、three→four、中文与含反引号标题补显式锚点;详见 report-batch-07.md", - "en_chars": 15742, - "zh_chars": 8940 - }, - "architecting-sea-payment-and-offline-licensing-pipelines": { - "slug": "architecting-sea-payment-and-offline-licensing-pipelines", - "title": "Delivering Commercial Desktop Software in Southeast Asia: Multi-Gateway Billing with toyyibPay and Keygate Fulfillment", - "zh_title": "在东南亚交付桌面商业软件:对接 toyyibPay 本地网银与 Keygate 机器码离线授权", - "category": "case-studies", - "pub_date": "2026-10-27", - "status": "pending", - "batch_id": null, - "approved_at": null, - "notes": "", - "en_chars": 9252, - "zh_chars": 5620 - }, - "batch-generating-80-technical-covers-with-comfyui": { - "slug": "batch-generating-80-technical-covers-with-comfyui", - "title": "Batch Generating 80+ Technical Covers with ComfyUI: Constraining Style, Space, and Typography", - "zh_title": "用 ComfyUI 批量闭环 80+ 张技术图文封面:设计规范约束、留白控制与批处理工作流", - "category": "ai", - "pub_date": "2026-10-27", - "status": "pending", - "batch_id": null, - "approved_at": null, - "notes": "", - "en_chars": 10963, - "zh_chars": 7049 - }, - "traefik-yaegi-plugin-package-name-trap": { - "slug": "traefik-yaegi-plugin-package-name-trap", - "title": "Why Your Traefik Yaegi Plugin Fails on Boot: The Module Hyphen to Underscore Trap", - "zh_title": "Traefik 自定义插件启动崩溃:Yaegi 解释器的连字符与下划线陷阱", - "category": "notes", - "pub_date": "2026-10-27", - "status": "pending", - "batch_id": null, - "approved_at": null, - "notes": "", - "en_chars": 6896, - "zh_chars": 4766 - }, - "authentik-oidc-redirect-loop-node-set-header-overwrite": { - "slug": "authentik-oidc-redirect-loop-node-set-header-overwrite", - "title": "The Authentik OIDC Redirect Loop: Why Node.js res.setHeader Overwrites Rather Than Appends", - "zh_title": "Authentik OIDC 重定向死循环:Node.js res.setHeader 整头覆盖机制与会话丢失排障", - "category": "engineering", - "pub_date": "2025-02-14", - "status": "approved", - "batch_id": "Batch-08", - "approved_at": "2026-11-01 14:55", - "notes": "Batch-08 顾问审阅「通过」:修正 Bento 卡片行数与摘要对齐、中文标题去个人化、details 标注 12 行。Playwright 真实测试通过。", - "en_chars": 6367, - "zh_chars": 3970 - }, - "six-chrome-containers-locked-my-nas-btrfs-d-state-igpu-offload": { - "slug": "six-chrome-containers-locked-my-nas-btrfs-d-state-igpu-offload", - "title": "Why Six Desktop Chrome Containers Locked a Production NAS: CPU WebGL Emulation, Btrfs D-State, and Offloading to iGPU", - "zh_title": "六个 Chrome 容器打崩存储 NAS:CPU 软解 WebGL、Btrfs D 状态死锁与核显架构迁移", - "category": "devops", - "pub_date": "2025-03-20", - "status": "approved", - "batch_id": "Batch-08", - "approved_at": "2026-11-01 14:55", - "notes": "Batch-08 顾问审阅「通过」:修正 7.5GB 提交内存空间、折叠 docker-compose 配置(17行)、正文对齐 unRaid 宿主、英文标题去个人化。", - "en_chars": 6351, - "zh_chars": 3892 - }, - "cascading-docker-failures-nocodb-heap-limit-starved-n8n": { - "slug": "cascading-docker-failures-nocodb-heap-limit-starved-n8n", - "title": "Cascading Docker Failures: When a Node.js Heap Limit in NocoDB Starved an n8n Automation Stack", - "zh_title": "Docker 级联故障复盘:NocoDB 内存溢出如何拖垮整套 n8n 自动化栈", - "category": "devops", - "pub_date": "2025-04-12", - "status": "approved", - "batch_id": "Batch-08", - "approved_at": "2026-11-01 14:55", - "notes": "Batch-08 顾问审阅「通过」:校准 V8 堆内存与 cgroup 启发式机制描述、修正 details 为 17 行、对齐 Compose 语法。", - "en_chars": 5885, - "zh_chars": 3834 - }, - "apple-icloud-smtp-port-465-timeout-starttls-587-trap": { - "slug": "apple-icloud-smtp-port-465-timeout-starttls-587-trap", - "title": "Apple iCloud SMTP Times Out on Port 465: The 587 STARTTLS-Only Trap", - "zh_title": "Apple iCloud 邮箱发信 465 超时:仅支持 587 STARTTLS 的协议陷阱", - "category": "notes", - "pub_date": "2026-06-18", - "status": "approved", - "batch_id": "Batch-08", - "approved_at": "2026-11-01 14:55", - "notes": "Batch-08 顾问审阅「通过」:明确 Windows Winsock 10060 运行环境、校准 RFC 6409/3207 与 RFC 8314 演进史、中文标签去个人化、details 标注 11 行。", - "en_chars": 5070, - "zh_chars": 3550 - }, - "home-assistant-container-upgrade-ladder-four-hops": { - "slug": "home-assistant-container-upgrade-ladder-four-hops", - "title": "Home Assistant Container: Upgrading Thirteen Releases Across a Four-Hop Ladder", - "zh_title": "Home Assistant 容器版跨版本升级:四阶安全平跳十三期迭代", - "category": "devops", - "pub_date": "2026-07-22", - "status": "approved", - "batch_id": "Batch-08", - "approved_at": "2026-11-01 14:55", - "notes": "Batch-08 顾问审阅「通过」:将智能家居升维为机房物联网动环监控、平滑 2026.8 反代表述、修正 details 为 14 行。", - "en_chars": 5593, - "zh_chars": 3422 - } + "adding-a-domain-to-someone-elses-mail-server": { + "slug": "adding-a-domain-to-someone-elses-mail-server", + "title": "Adding a Domain to Someone Else's Mail Server", + "zh_title": "给别人家的邮件服务器加一个域名", + "category": "notes", + "pub_date": "2025-05-06", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 7886, + "zh_chars": 3603 + }, + "adding-english-mode-to-a-chinese-only-web-app": { + "slug": "adding-english-mode-to-a-chinese-only-web-app", + "title": "No API for Browser Translation: Adding an English Mode to a Chinese-Only Web App", + "zh_title": "浏览器翻译没有 API:我给一个纯中文网页应用加上了英文模式", + "category": "engineering", + "pub_date": "2025-06-11", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 11914, + "zh_chars": 6253 + }, + "ai-furniture-compositing-with-flux-kontext": { + "slug": "ai-furniture-compositing-with-flux-kontext", + "title": "How I Built an AI Furniture Compositing Demo With FLUX.1 Kontext", + "zh_title": "我如何用 FLUX.1 Kontext 打造 AI 家具合成演示", + "category": "case-studies", + "pub_date": "2026-09-11", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 05:26", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", + "en_chars": 12512, + "zh_chars": 7237 + }, + "authentik-css-greater-than-bug": { + "slug": "authentik-css-greater-than-bug", + "title": "The Character That Silently Broke My authentik CSS", + "zh_title": "一个字符让我的 authentik CSS 静默失效", + "category": "devops", + "pub_date": "2026-09-20", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 9317, + "zh_chars": 5589 + }, + "authentik-forward-auth-gate-wasnt-live": { + "slug": "authentik-forward-auth-gate-wasnt-live", + "title": "The Forward-Auth Gate That Verified Perfectly — and Wasn't Live", + "zh_title": "验证完全正确的 Forward-Auth 网关——其实并未上线", + "category": "devops", + "pub_date": "2026-08-12", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 16526, + "zh_chars": 8746 + }, + "authentik-major-upgrade-gotchas": { + "slug": "authentik-major-upgrade-gotchas", + "title": "Authentik 2025.8 to 2026.8: The Breakable Parts Nobody Warns You About", + "zh_title": "Authentik 从 2025.8 升级到 2026.8:那些没人提醒你会坏掉的部分", + "category": "devops", + "pub_date": "2026-09-09", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 11710, + "zh_chars": 6147 + }, + "automating-cyberpanel-without-the-ui": { + "slug": "automating-cyberpanel-without-the-ui", + "title": "Automating CyberPanel Without the UI: Reverse-Engineering an Undocumented v2 API", + "zh_title": "不用界面管理 CyberPanel:逆向一个没有文档的 v2 API", + "category": "devops", + "pub_date": "2026-09-09", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 8474, + "zh_chars": 5205 + }, + "best-ai-video-generators-2026": { + "slug": "best-ai-video-generators-2026", + "title": "Best AI Video Generators in 2026: Free vs Paid, and My LTX-2.3 Test", + "zh_title": "2026 年最佳 AI 视频生成器:免费与付费对比,以及我的 LTX-2.3 实测", + "category": "ai", + "pub_date": "2026-09-11", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 03:00", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 24245, + "zh_chars": 12700 + }, + "calibrating-intel-quick-sync-hevc-quality": { + "slug": "calibrating-intel-quick-sync-hevc-quality", + "title": "Calibrating Intel Quick Sync HEVC: A Cliff at 28, and a Preset That Does Nothing", + "zh_title": "校准 Intel Quick Sync HEVC:28 与 30 之间的悬崖,和一个毫无作用的 preset", + "category": "tutorials", + "pub_date": "2026-10-14", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 10968, + "zh_chars": 5968 + }, + "chainlink-vrf-v2-lottery-contract": { + "slug": "chainlink-vrf-v2-lottery-contract", + "title": "An Automated Lottery Contract on Chainlink VRF v2.5 and Automation", + "zh_title": "一个基于 Chainlink VRF v2.5 与 Automation 的自动化彩票合约", + "category": "web3", + "pub_date": "2024-12-10", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:11", + "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", + "en_chars": 16252, + "zh_chars": 9707 + }, + "debugging-remotion-video-jitter": { + "slug": "debugging-remotion-video-jitter", + "title": "Debugging Remotion Video Jitter: Why Bass Scaling Ruins Background Transitions", + "zh_title": "排查 Remotion 视频抽搐:为什么在背景图挂载音频低频 Scale 会毁了转场", + "category": "engineering", + "pub_date": "2025-11-26", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 9836, + "zh_chars": 5653 + }, + "docker-reported-exit-0-the-oom-killer-disagreed": { + "slug": "docker-reported-exit-0-the-oom-killer-disagreed", + "title": "Docker Reported Exit 0. The Kernel Had Already OOM-Killed It.", + "zh_title": "Docker 报告退出码 0,内核却早已执行 OOM-Kill", + "category": "devops", + "pub_date": "2026-10-16", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 10200, + "zh_chars": 5488 + }, + "does-hevc-actually-shrink-your-files": { + "slug": "does-hevc-actually-shrink-your-files", + "title": "Does Re-encoding to HEVC Actually Shrink Your Files? I Measured It.", + "zh_title": "转成 HEVC 到底会不会让文件变小?我实测了", + "category": "tutorials", + "pub_date": "2026-10-08", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 8042, + "zh_chars": 4159 + }, + "embedding-fonts-so-a-pdf-looks-the-same-everywhere": { + "slug": "embedding-fonts-so-a-pdf-looks-the-same-everywhere", + "title": "Embedding Fonts So a PDF Looks the Same on Every Machine", + "zh_title": "把字体嵌进 PDF,让它在任何机器上都长一样", + "category": "engineering", + "pub_date": "2026-10-15", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 8591, + "zh_chars": 3747 + }, + "every-player-stuttered-the-file-was-fine": { + "slug": "every-player-stuttered-the-file-was-fine", + "title": "Every Player Stuttered on the Same Video. The File Was Blameless.", + "zh_title": "每个播放器都在同一个视频上卡顿,而文件本身是无辜的", + "category": "devops", + "pub_date": "2026-10-08", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 8459, + "zh_chars": 4296 + }, + "five-step-ai-roadmap-what-i-actually-run": { + "slug": "five-step-ai-roadmap-what-i-actually-run", + "title": "The Five-Step AI Roadmap Everyone Shares — and What I Actually Run in Production", + "zh_title": "全网都在转的 AI 五步学习路线 —— 我真正在生产里跑的是哪几步", + "category": "ai", + "pub_date": "2026-10-13", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 03:00", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 13155, + "zh_chars": 5483 + }, + "from-audio-to-a-4-minute-ai-music-video": { + "slug": "from-audio-to-a-4-minute-ai-music-video", + "title": "From Audio to a 4-Minute Music Video: Orchestrating Gemini, AI Diffusion, and Filmora Editing", + "zh_title": "从一首歌到 4 分钟完整 MV:我如何用 Gemini、AI 生成与 Filmora 剪辑串起全流程实操", + "category": "case-studies", + "pub_date": "2026-10-25", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 05:26", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", + "en_chars": 11027, + "zh_chars": 5168 + }, + "fully-on-chain-svg-nfts": { + "slug": "fully-on-chain-svg-nfts", + "title": "Fully On-Chain SVG NFTs: Putting the Artwork Inside the Contract", + "zh_title": "全链上 SVG NFT:把艺术作品放进合约里", + "category": "web3", + "pub_date": "2024-10-08", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:11", + "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", + "en_chars": 16205, + "zh_chars": 10281 + }, + "generating-full-length-songs-with-yue2-comfyui": { + "slug": "generating-full-length-songs-with-yue2-comfyui", + "title": "Generating Full-Length Songs with YuE2 BF16: Overcoming Duration Limits and Audio Truncation", + "zh_title": "用 YuE2 BF16 生成完整长歌:突破时长限制与音频硬截断", + "category": "tutorials", + "pub_date": "2026-10-26", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 06:04", + "notes": "第3批微调项落地并通过:时长校正283.1s/单调时间戳与防死锁/专业人设去二次元俚语/B2B转化增强", + "en_chars": 134821, + "zh_chars": 127730 + }, + "generating-subtitles-for-songs-without-timestamps": { + "slug": "generating-subtitles-for-songs-without-timestamps", + "title": "Generating Subtitles for Songs That Ship Without Timestamps", + "zh_title": "给没有时间戳的歌曲自动生成字幕", + "category": "tutorials", + "pub_date": "2026-10-20", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:11", + "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", + "en_chars": 47576, + "zh_chars": 42677 + }, + "giving-ai-your-mouse-and-keyboard-safely": { + "slug": "giving-ai-your-mouse-and-keyboard-safely", + "title": "Giving AI Your Mouse and Keyboard: Safe Guardrails for Windows Desktop MCP", + "zh_title": "把鼠标和键盘交给 AI 之前:Windows 桌面 MCP 的安全边界与实战防线", + "category": "ai", + "pub_date": "2025-10-22", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 03:00", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 5906, + "zh_chars": 3149 + }, + "hardening-a-tor-onion-service": { + "slug": "hardening-a-tor-onion-service", + "title": "Hardening a Tor Onion Service: What Actually Matters", + "zh_title": "加固 Tor 洋葱服务:真正重要的是什么", + "category": "devops", + "pub_date": "2025-12-10", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 14907, + "zh_chars": 7041 + }, + "hello-world": { + "slug": "hello-world", + "title": "Hello, World — About This Blog", + "zh_title": "你好,世界 —— 关于这个博客", + "category": "notes", + "pub_date": "2024-09-24", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 1938, + "zh_chars": 1074 + }, + "how-i-built-an-automated-music-video-pipeline": { + "slug": "how-i-built-an-automated-music-video-pipeline", + "title": "How I Built an Automated Music Video Pipeline with Remotion and Dockerized YouTube CDP", + "zh_title": "我是如何用 Remotion 与 Docker CDP 打造全自动 MV 生成与多语种油管发布流的", + "category": "case-studies", + "pub_date": "2025-04-18", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 05:26", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", + "en_chars": 9265, + "zh_chars": 4931 + }, + "how-i-built-the-digikedai-telegram-bot": { + "slug": "how-i-built-the-digikedai-telegram-bot", + "title": "How I Built the DigiKedai Telegram AI Bot", + "zh_title": "我是如何构建 DigiKedai Telegram AI 客服机器人的", + "category": "case-studies", + "pub_date": "2026-09-06", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 05:26", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", + "en_chars": 10719, + "zh_chars": 5271 + }, + "how-i-host-this-blog": { + "slug": "how-i-host-this-blog", + "title": "How I Host This Blog: Astro, Gitea Actions, and Self-Hosted CI/CD", + "zh_title": "我是如何托管这个博客的:Astro、Gitea Actions 与自托管 CI/CD", + "category": "case-studies", + "pub_date": "2026-09-04", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 05:26", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", + "en_chars": 5908, + "zh_chars": 4145 + }, + "how-i-made-my-own-songs-with-suno-ai": { + "slug": "how-i-made-my-own-songs-with-suno-ai", + "title": "How I Made My Own Songs with Suno AI", + "zh_title": "我用 Suno AI 做出自己的歌", + "category": "case-studies", + "pub_date": "2026-09-11", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 05:26", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", + "en_chars": 16935, + "zh_chars": 11737 + }, + "how-i-vetted-20-vps-providers-with-parallel-subagents": { + "slug": "how-i-vetted-20-vps-providers-with-parallel-subagents", + "title": "How I Vetted 20 VPS Providers in Three Hours with Parallel Subagents", + "zh_title": "如何用并行子代理在三小时内核查 20 家 VPS 服务商", + "category": "case-studies", + "pub_date": "2025-11-12", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 8593, + "zh_chars": 3613 + }, + "how-to-verify-a-hosting-provider-before-you-buy": { + "slug": "how-to-verify-a-hosting-provider-before-you-buy", + "title": "How to Verify a Hosting Provider Before You Buy", + "zh_title": "购买主机前,如何核实一家服务商的真实情况", + "category": "tutorials", + "pub_date": "2025-11-26", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:11", + "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", + "en_chars": 7710, + "zh_chars": 3405 + }, + "i-let-an-agent-manage-my-shopee-vouchers": { + "slug": "i-let-an-agent-manage-my-shopee-vouchers", + "title": "I Let an Agent Manage My Shopee Vouchers — And It Created One I Never Approved", + "zh_title": "我让 AI 助手管我的 Shopee 优惠券 —— 它建了一张我没批准的券", + "category": "ai", + "pub_date": "2025-07-08", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 03:00", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 9981, + "zh_chars": 4824 + }, + "it-reported-success-nothing-had-changed": { + "slug": "it-reported-success-nothing-had-changed", + "title": "It Reported Success. Nothing Had Changed.", + "zh_title": "它报告了成功,可实际上什么都没变", + "category": "devops", + "pub_date": "2026-10-08", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 04:09", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", + "en_chars": 8349, + "zh_chars": 3886 + }, + "jpa-version-field-lost-update": { + "slug": "jpa-version-field-lost-update", + "title": "The JPA Field That Turns a Silent Lost Update Into a 409", + "zh_title": "那个把静默丢失更新变成 409 的 JPA 字段:@Version 乐观锁", + "category": "engineering", + "pub_date": "2026-08-19", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 12536, + "zh_chars": 7506 + }, + "loop-engineering-without-a-coding-agent": { + "slug": "loop-engineering-without-a-coding-agent", + "title": "Loop Engineering Without a Coding Agent: The 11 Cron Jobs That Run My Infrastructure", + "zh_title": "没有 Coding Agent 的 Loop Engineering:撑起我这套基础设施的 11 个定时任务", + "category": "case-studies", + "pub_date": "2026-10-01", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 14249, + "zh_chars": 6609 + }, + "migrating-codeigniter-iis-to-openlitespeed": { + "slug": "migrating-codeigniter-iis-to-openlitespeed", + "title": "Migrating a CodeIgniter 4 App From IIS to OpenLiteSpeed", + "zh_title": "把 CodeIgniter 4 应用从 IIS 迁移到 OpenLiteSpeed", + "category": "engineering", + "pub_date": "2026-09-27", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 14943, + "zh_chars": 8425 + }, + "my-backup-was-using-someone-elses-api-quota": { + "slug": "my-backup-was-using-someone-elses-api-quota", + "title": "My Backup Was Crawling Because I Was Using Someone Else's API Quota", + "zh_title": "我的备份爬得比爬虫还慢,因为我在用别人的 API 配额", + "category": "notes", + "pub_date": "2026-10-15", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 8572, + "zh_chars": 4527 + }, + "n8n-v1-to-v2-upgrade-gotchas": { + "slug": "n8n-v1-to-v2-upgrade-gotchas", + "title": "Upgrading n8n v1 to v2: Seven Deprecations in One Log File", + "zh_title": "n8n v1 升级到 v2:一个日志文件里的七项废弃警告", + "category": "devops", + "pub_date": "2026-09-18", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 04:09", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", + "en_chars": 12262, + "zh_chars": 6712 + }, + "nocodb-sso-is-a-licensed-feature": { + "slug": "nocodb-sso-is-a-licensed-feature", + "title": "NocoDB SSO Is a Licensed Feature — and the Env Vars Won't Tell You", + "zh_title": "NocoDB SSO 是授权功能:环境变量不会告诉你的事", + "category": "notes", + "pub_date": "2026-09-29", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 9937, + "zh_chars": 5421 + }, + "one-hostname-public-tracker-sso-dashboard": { + "slug": "one-hostname-public-tracker-sso-dashboard", + "title": "One Hostname for a Public Tracker and an SSO-Gated Dashboard", + "zh_title": "公开的埋点与要 SSO 的仪表盘,怎么共用一个域名", + "category": "devops", + "pub_date": "2026-09-30", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 04:09", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", + "en_chars": 14400, + "zh_chars": 6837 + }, + "one-prometheus-for-unraid-synology-and-a-vps": { + "slug": "one-prometheus-for-unraid-synology-and-a-vps", + "title": "One Prometheus for unRaid, a Synology NAS, and a VPS: What I Got Wrong", + "zh_title": "用一个 Prometheus 监控 unRaid、群晖 NAS 和 VPS:我做错的四件事", + "category": "case-studies", + "pub_date": "2026-07-29", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 8358, + "zh_chars": 4690 + }, + "passbolt-hang-three-failure-modes": { + "slug": "passbolt-hang-three-failure-modes", + "title": "Passbolt UI Kept Hanging — Three Failure Modes From One Missing Config File", + "zh_title": "Passbolt 界面反复卡死——一个缺失的配置文件引发的三种故障模式", + "category": "devops", + "pub_date": "2026-01-07", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 04:09", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", + "en_chars": 7864, + "zh_chars": 4187 + }, + "patching-workbench-26-for-mariadb": { + "slug": "patching-workbench-26-for-mariadb", + "title": "When Your Database Client Lies to You: Patching Workbench 26 for MariaDB", + "zh_title": "当数据库客户端对你撒谎:为 MariaDB 修补 Workbench 26", + "category": "devops", + "pub_date": "2026-09-19", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 04:09", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", + "en_chars": 15965, + "zh_chars": 9501 + }, + "php2shell-patching-every-php-runtime-i-manage": { + "slug": "php2shell-patching-every-php-runtime-i-manage", + "title": "PHP2Shell: I Measured Every PHP Runtime I Manage. All of Them Were Vulnerable.", + "zh_title": "PHP2Shell:我管的每台机器,PHP 运行时全都中了", + "category": "devops", + "pub_date": "2026-10-23", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 04:09", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", + "en_chars": 22354, + "zh_chars": 12531 + }, + "plugging-windows-desktop-control-into-four-ai-agents": { + "slug": "plugging-windows-desktop-control-into-four-ai-agents", + "title": "Plugging Windows Desktop Control into Four AI Agents: Hermes, OpenCode, Claw, and Claude", + "zh_title": "把 Windows 桌面控制接进 4 款 AI Agent:Hermes、OpenCode、Claw 与 Claude 的实战架构", + "category": "ai", + "pub_date": "2025-12-23", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 03:00", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 6554, + "zh_chars": 5030 + }, + "putting-a-front-door-on-an-app-you-cant-modify": { + "slug": "putting-a-front-door-on-an-app-you-cant-modify", + "title": "Putting a Front Door on an App You Can't Modify", + "zh_title": "给自己改不了源码的应用装一扇前门", + "category": "devops", + "pub_date": "2025-06-24", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 04:09", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", + "en_chars": 13927, + "zh_chars": 7415 + }, + "read-only-nocodb-dashboard-for-a-remote-database": { + "slug": "read-only-nocodb-dashboard-for-a-remote-database", + "title": "A Read-Only NocoDB Dashboard for a Database on Another Machine", + "zh_title": "给另一台机器上的数据库配一个 NocoDB 只读看板", + "category": "devops", + "pub_date": "2026-09-29", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 04:09", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", + "en_chars": 12009, + "zh_chars": 6287 + }, + "reading-a-containers-own-api-docs": { + "slug": "reading-a-containers-own-api-docs", + "title": "Read a Container's Own API Docs Instead of the Vendor's", + "zh_title": "与其看供应商文档,不如直接读容器自带的 API 文档", + "category": "notes", + "pub_date": "2025-10-01", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 8382, + "zh_chars": 5489 + }, + "replacing-rdpguard-with-ipban": { + "slug": "replacing-rdpguard-with-ipban", + "title": "Replacing RDPGuard With IPBan: The Traps Nobody Documents", + "zh_title": "用 IPBan 替换 RDPGuard:没人写出来的三个坑", + "category": "devops", + "pub_date": "2026-09-19", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 04:09", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/技术细节修补)", + "en_chars": 13131, + "zh_chars": 7795 + }, + "running-ai-video-locally-on-a-12gb-gpu": { + "slug": "running-ai-video-locally-on-a-12gb-gpu", + "title": "Running AI Video Locally on a 12 GB GPU: Real Numbers for Wan 2.2 and SeedVR2", + "zh_title": "12GB 显卡跑本地 AI 视频:Wan 2.2 加 SeedVR2 的真实数据", + "category": "ai", + "pub_date": "2026-10-20", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 03:00", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 18508, + "zh_chars": 12878 + }, + "running-production-infrastructure-solo": { + "slug": "running-production-infrastructure-solo", + "title": "Running Production Infrastructure Solo: 162 Containers Across Three Hosts", + "zh_title": "一个人跑生产基础设施:三台主机、162 个容器", + "category": "case-studies", + "pub_date": "2026-10-06", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 7975, + "zh_chars": 3722 + }, + "running-tts-as-a-service-with-token-sidecars": { + "slug": "running-tts-as-a-service-with-token-sidecars", + "title": "I Ran a Text-to-Speech Service for a Year on Two Cron Containers", + "zh_title": "我用两个 Cron 容器跑了一年的语音合成服务", + "category": "ai", + "pub_date": "2026-09-18", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 03:00", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 14141, + "zh_chars": 7929 + }, + "scraping-bot-walled-marketplace-warm-browser-session": { + "slug": "scraping-bot-walled-marketplace-warm-browser-session", + "title": "Scraping a Bot-Walled Marketplace With a Warm Browser Session", + "zh_title": "用「热浏览器会话」爬取有反爬墙的电商网站", + "category": "engineering", + "pub_date": "2026-09-13", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 11313, + "zh_chars": 5734 + }, + "self-healing-digital-goods-entitlements": { + "slug": "self-healing-digital-goods-entitlements", + "title": "Self-Healing Access Control for Digital Goods: NocoDB, n8n, and AList", + "zh_title": "数字商品的自愈式访问控制:NocoDB、n8n 与 AList", + "category": "case-studies", + "pub_date": "2026-09-18", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 14765, + "zh_chars": 6819 + }, + "self-hosted-speech-to-text-api": { + "slug": "self-hosted-speech-to-text-api", + "title": "Your Own Speech-to-Text Server: Faster Than Typing, Private by Default", + "zh_title": "自建语音转文字服务:打字速度的 3 倍以上,而且数据不出内网", + "category": "case-studies", + "pub_date": "2026-09-19", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 14708, + "zh_chars": 7794 + }, + "self-hosted-tts-cosyvoice2-n8n-gpu": { + "slug": "self-hosted-tts-cosyvoice2-n8n-gpu", + "title": "How I Replaced Cloud TTS with a Self-Hosted Neural Voice Stack on an RTX 3060", + "zh_title": "从云端转本地:在 12GB 显卡与群晖 N8N 上搭建中英混读高保真 TTS 语音栈", + "category": "case-studies", + "pub_date": "2026-10-22", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 8913, + "zh_chars": 4773 + }, + "self-hosting-mem0-memory-stack": { + "slug": "self-hosting-mem0-memory-stack", + "title": "How I Self-Hosted a Mem0 Memory Stack (So My AI Agent Remembers)", + "zh_title": "我如何在私有存储节点上自托管 Mem0 记忆栈(让 AI 助手拥有持久记忆)", + "category": "case-studies", + "pub_date": "2026-09-16", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 8057, + "zh_chars": 4849 + }, + "shipping-an-ai-photo-editor-as-a-wordpress-plugin": { + "slug": "shipping-an-ai-photo-editor-as-a-wordpress-plugin", + "title": "Shipping an AI Photo Editor as a WordPress Plugin: 22 Versions of Hard Lessons", + "zh_title": "把 AI 修图工具做成 WordPress 插件:22 个版本换来的经验", + "category": "case-studies", + "pub_date": "2026-09-18", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 13879, + "zh_chars": 6861 + }, + "slashing-agent-context-with-a-code-graph": { + "slug": "slashing-agent-context-with-a-code-graph", + "title": "Slashing Agent Context and Token Churn With a Code Graph", + "zh_title": "别再把整个 Repo 喂给 Agent:用 Code Graph 斩断 Token 膨胀与遗忘陷阱", + "category": "ai", + "pub_date": "2026-04-28", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 03:00", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 8122, + "zh_chars": 5040 + }, + "smartctl-exit-code-32-skips-the-disks-that-matter": { + "slug": "smartctl-exit-code-32-skips-the-disks-that-matter", + "title": "smartctl Exit Code 32: The Code That Skips the Disks You Care About", + "zh_title": "smartctl 退出码 32:专门跳过你最该看的硬盘的那个“错误”", + "category": "notes", + "pub_date": "2026-04-14", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 5475, + "zh_chars": 2881 + }, + "space-reclamation-wasnt-stuck-it-was-waiting": { + "slug": "space-reclamation-wasnt-stuck-it-was-waiting", + "title": "Space Reclamation Wasn't Stuck. It Was Waiting for Tomorrow at 09:29.", + "zh_title": "空间回收没卡住,它只是在等明天早上 09:29", + "category": "devops", + "pub_date": "2026-10-14", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 6574, + "zh_chars": 3505 + }, + "stop-writing-agent-prompts-for-deterministic-work": { + "slug": "stop-writing-agent-prompts-for-deterministic-work", + "title": "Stop Writing Agent Prompts for Deterministic Work", + "zh_title": "确定性的活,别再用 Agent Prompt 去跑", + "category": "notes", + "pub_date": "2026-10-01", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 05:22", + "notes": "外部顾问审阅通过并落地修改", + "en_chars": 4951, + "zh_chars": 2216 + }, + "syncing-a-self-improving-ai-agent-across-machines": { + "slug": "syncing-a-self-improving-ai-agent-across-machines", + "title": "Syncing a Self-Improving AI Agent Across Machines", + "zh_title": "在多台机器之间同步一个会自我学习的 AI Agent", + "category": "case-studies", + "pub_date": "2026-02-18", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 04:22", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/架构蓝图对齐/排版折叠)", + "en_chars": 8861, + "zh_chars": 4541 + }, + "synology-api-401-with-the-correct-password": { + "slug": "synology-api-401-with-the-correct-password", + "title": "Why Your Synology API Login Returns 401 With the Correct Password", + "zh_title": "密码正确,Synology API 登录却返回 401,问题出在哪", + "category": "notes", + "pub_date": "2025-09-10", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 5053, + "zh_chars": 3063 + }, + "synology-spreadsheet-api-is-a-container": { + "slug": "synology-spreadsheet-api-is-a-container", + "title": "The Synology Spreadsheet API Is a Container, Not an API Endpoint", + "zh_title": "Synology 电子表格 API 是一个容器,不是一个 API 端点", + "category": "devops", + "pub_date": "2025-08-20", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 16101, + "zh_chars": 9200 + }, + "that-dying-ssd-was-just-a-bad-sata-cable": { + "slug": "that-dying-ssd-was-just-a-bad-sata-cable", + "title": "I Was About to RMA This SSD — the Fault Was a SATA Cable", + "zh_title": "我差点把这颗 SSD 送修——真正坏的是 SATA 数据线", + "category": "devops", + "pub_date": "2026-09-16", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 7471, + "zh_chars": 3940 + }, + "the-cause-was-trim-not-the-ssds": { + "slug": "the-cause-was-trim-not-the-ssds", + "title": "The Corruption Came Back on Different Drives — the Cause Was TRIM, Not the SSDs", + "zh_title": "换了不同的硬盘,同样的损坏又来了——真凶是 TRIM,不是 SSD", + "category": "devops", + "pub_date": "2026-09-18", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 13042, + "zh_chars": 7310 + }, + "the-nocodb-attachment-that-wouldnt-update": { + "slug": "the-nocodb-attachment-that-wouldnt-update", + "title": "The NocoDB Attachment That Wouldn't Update (and a Regex That Missed)", + "zh_title": "NocoDB 附件字段怎么改都不生效,和一个漏网的正则", + "category": "notes", + "pub_date": "2026-02-04", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 3798, + "zh_chars": 2312 + }, + "the-single-writer-rule-for-coding-agents": { + "slug": "the-single-writer-rule-for-coding-agents", + "title": "The Single-Writer Rule: Orchestrating Coding Agents Without Multi-Agent Chaos", + "zh_title": "代码只让一个 AI 改:我是如何避开多 Agent 协同踩坑的「单写多审」架构", + "category": "case-studies", + "pub_date": "2026-08-26", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 13331, + "zh_chars": 6954 + }, + "the-upstream-was-deleted-then-came-back-rewritten": { + "slug": "the-upstream-was-deleted-then-came-back-rewritten", + "title": "The Upstream Was Deleted, Then Came Back as a Rewrite", + "zh_title": "上游被删了,然后带着一个重写版回来", + "category": "devops", + "pub_date": "2026-10-06", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 11636, + "zh_chars": 5229 + }, + "two-ways-a-javascript-file-stops-working-and-looks-fine": { + "slug": "two-ways-a-javascript-file-stops-working-and-looks-fine", + "title": "Two Ways a JavaScript File Stops Working and Looks Fine", + "zh_title": "JavaScript 文件静默失效的两种方式:而页面看上去一切正常", + "category": "engineering", + "pub_date": "2025-02-20", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 8293, + "zh_chars": 3881 + }, + "unraid-stop-array-hangs-on-swapfile": { + "slug": "unraid-stop-array-hangs-on-swapfile", + "title": "Why Unraid Hangs When You Stop the Array With a Swapfile — and the Fix", + "zh_title": "Unraid 停止阵列时卡死的真相:Swapfile 锁住挂载点 — 附修复方案", + "category": "devops", + "pub_date": "2026-09-15", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 10470, + "zh_chars": 6376 + }, + "upgrading-codeigniter-46-to-47": { + "slug": "upgrading-codeigniter-46-to-47", + "title": "Upgrading CodeIgniter 4.6 to 4.7: The Breaking Changes the Guide Misses", + "zh_title": "CodeIgniter 4.6 升级到 4.7:官方指南没说到的破坏性变更", + "category": "notes", + "pub_date": "2026-09-27", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 05:22", + "notes": "外部顾问审阅通过并落地修改", + "en_chars": 9469, + "zh_chars": 5411 + }, + "uploading-large-videos-to-youtube-via-cdp": { + "slug": "uploading-large-videos-to-youtube-via-cdp", + "title": "Uploading 100MB+ Videos to YouTube via CDP: The Docker Archive Pattern", + "zh_title": "用 CDP 自动化上传百兆视频到 YouTube:跨越 DevTools 内存墙的 Docker 归档法", + "category": "devops", + "pub_date": "2025-08-22", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 11267, + "zh_chars": 7173 + }, + "using-chinese-llm-apis-from-malaysia": { + "slug": "using-chinese-llm-apis-from-malaysia", + "title": "Using Chinese LLM APIs From Malaysia: What Actually Works", + "zh_title": "从马来西亚使用中国大模型 API:真正可行的方法", + "category": "ai", + "pub_date": "2026-09-11", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 03:00", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 7540, + "zh_chars": 3536 + }, + "verifying-a-pdf-report-page-by-page": { + "slug": "verifying-a-pdf-report-page-by-page", + "title": "Verifying a 19-Page PDF Report Page by Page", + "zh_title": "逐页验证一份 19 页的 PDF 报告", + "category": "engineering", + "pub_date": "2026-09-28", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 12525, + "zh_chars": 6685 + }, + "vetting-an-open-source-dependency-before-you-bet-on-it": { + "slug": "vetting-an-open-source-dependency-before-you-bet-on-it", + "title": "How I Vet an Open-Source Dependency Before Betting On It", + "zh_title": "如何在押注前验证一个开源依赖:我用的六项检查", + "category": "devops", + "pub_date": "2026-09-29", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 8512, + "zh_chars": 4201 + }, + "when-smart-says-healthy-but-your-raid-is-corrupting-data": { + "slug": "when-smart-says-healthy-but-your-raid-is-corrupting-data", + "title": "When SMART Says Healthy and Your RAID Is Silently Corrupting Data", + "zh_title": "SMART 显示健康,RAID 却在悄悄损坏数据", + "category": "devops", + "pub_date": "2026-09-16", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 14160, + "zh_chars": 6246 + }, + "when-to-give-your-agent-physical-clicks": { + "slug": "when-to-give-your-agent-physical-clicks", + "title": "When to Give Your Agent Physical Clicks (And When to Never Touch Them)", + "zh_title": "什么时候该让 AI 操控物理鼠标(以及什么时候绝对不要用)", + "category": "case-studies", + "pub_date": "2026-05-28", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 6882, + "zh_chars": 3608 + }, + "why-500000-lines-of-commit-churn-only-left-230000-in-production": { + "slug": "why-500000-lines-of-commit-churn-only-left-230000-in-production", + "title": "I Committed 518,000 Lines in 30 Days. Production Kept 235,000.", + "zh_title": "30 天提交 51.8 万行,生产环境只留下 23.5 万行", + "category": "engineering", + "pub_date": "2026-10-18", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 8949, + "zh_chars": 4197 + }, + "why-chrome-forgets-its-tabs-in-a-container": { + "slug": "why-chrome-forgets-its-tabs-in-a-container", + "title": "Why Chrome Forgets Its Tabs in a Container — And How I Fixed It", + "zh_title": "为什么容器里的 Chrome 会忘记标签页 —— 以及我是怎么修好的", + "category": "devops", + "pub_date": "2026-09-29", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 15430, + "zh_chars": 9234 + }, + "why-i-built-a-video-knowledge-base": { + "slug": "why-i-built-a-video-knowledge-base", + "title": "Why I Built a Video Knowledge Base: Turning Saved Short Videos into Searchable Notes", + "zh_title": "为什么我要做视频精读知识库:把收藏夹吃灰的短视频变成可检索笔记", + "category": "case-studies", + "pub_date": "2024-11-28", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 5770, + "zh_chars": 2924 + }, + "why-i-still-bought-a-local-gpu": { + "slug": "why-i-still-bought-a-local-gpu", + "title": "Why I Still Bought a GPU to Run AI When Claude and GPT Are Obviously Stronger", + "zh_title": "Claude 和 GPT 明明更强,我为什么还是买了 GPU 跑本地 AI", + "category": "ai", + "pub_date": "2026-09-21", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 05:26", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", + "en_chars": 11811, + "zh_chars": 4924 + }, + "why-is-my-tts-model-repeating-percent": { + "slug": "why-is-my-tts-model-repeating-percent", + "title": "Why Is My TTS Model Repeating \\\"Percent\\\"? The URL-Encoding Trap in Speech Pipelines", + "zh_title": "为什么本地语音合成一直在念 “Percent”?语音管线里的 URL 编码与沙盒避坑记", + "category": "notes", + "pub_date": "2026-10-22", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 7728, + "zh_chars": 4968 + }, + "why-multi-agent-coding-fails": { + "slug": "why-multi-agent-coding-fails", + "title": "Why Multi-Agent Coding Fails: The Telephone Game and Architectural Drift", + "zh_title": "为什么多个 AI 一起写代码总会烂尾?传话失真与隐式决策困境", + "category": "notes", + "pub_date": "2025-07-29", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 6364, + "zh_chars": 3353 + }, + "why-my-on-chain-nft-art-changed-on-windows": { + "slug": "why-my-on-chain-nft-art-changed-on-windows", + "title": "Why My On-Chain NFT Art Changed When I Cloned It on Windows", + "zh_title": "为什么在 Windows 上克隆后,我的链上 NFT 图像变了", + "category": "web3", + "pub_date": "2026-08-19", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:11", + "notes": "Batch-04 review: performUpkeep external-callable correction, _ownerOf dead-code fix, storage vs EIP-170 split, RDAP -L + ASN blocklist, subs-15 retimed from re-run ASR, cross-links, dynamicCta", + "en_chars": 8423, + "zh_chars": 4585 + }, + "why-telegram-bot-notifications-die": { + "slug": "why-telegram-bot-notifications-die", + "title": "Why Telegram Bot Notifications Die: IPv6, DNS, and a 400 That Took a Day", + "zh_title": "Telegram 机器人通知为什么发不出去:IPv6、DNS 和一个追查了一整天的 400", + "category": "devops", + "pub_date": "2026-03-25", + "status": "approved", + "batch_id": "Batch-02", + "approved_at": "2026-10-27 06:10", + "notes": "顾问二次审阅(其编号Batch-05)已落地:修正Multipart join原理解释与源码转义、Btrfs csum字段位置与字节序、losetup -p误用、UDMA_CRC机制、CONFIG_CFS_BANDWIDTH、GiB/h算术;补齐blueprint与机型待确认。详见 reports/report-batch-05-rereview.md", + "en_chars": 10753, + "zh_chars": 6005 + }, + "why-your-cdp-clicks-silently-fail": { + "slug": "why-your-cdp-clicks-silently-fail", + "title": "Why Your CDP Clicks Silently Fail: Smooth Scroll, Minimised Windows, and JS-Dispatched Events", + "zh_title": "为什么你的 CDP 点击会静默失效:平滑滚动、最小化窗口与 JS 派发事件", + "category": "devops", + "pub_date": "2025-03-19", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 8054, + "zh_chars": 4536 + }, + "why-your-grafana-dashboard-shows-no-data": { + "slug": "why-your-grafana-dashboard-shows-no-data", + "title": "Why Your Grafana Dashboard Shows No Data (When Prometheus Is Fine)", + "zh_title": "Prometheus 明明有数据,Grafana 面板却全是 No data 的原因", + "category": "devops", + "pub_date": "2026-05-17", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 7456, + "zh_chars": 3919 + }, + "why-your-headless-browser-cant-scrape-everything": { + "slug": "why-your-headless-browser-cant-scrape-everything", + "title": "Why Your Headless Browser Can't Scrape Everything", + "zh_title": "为什么你的无头浏览器不是万能爬虫", + "category": "case-studies", + "pub_date": "2026-01-21", + "status": "approved", + "batch_id": "Batch-03", + "approved_at": "2026-10-27 03:49", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见", + "en_chars": 8076, + "zh_chars": 3792 + }, + "why-your-windows-mcp-calls-silently-fail": { + "slug": "why-your-windows-mcp-calls-silently-fail", + "title": "Why Your Windows Desktop MCP Calls Silently Fail: Taming the 10-Second Idle Gate", + "zh_title": "为什么你的 Windows 桌面 MCP 调用会静默失败:攻破 10 秒空闲门禁与避坑实录", + "category": "notes", + "pub_date": "2026-03-12", + "status": "approved", + "batch_id": "Batch-04", + "approved_at": "2026-10-27 05:21", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含数字矛盾/OPSEC脱敏/双语CTA/排版折叠/禁用词清理)", + "en_chars": 5663, + "zh_chars": 3857 + }, + "your-disk-full-alert-is-lying": { + "slug": "your-disk-full-alert-is-lying", + "title": "Your Disk-Full Alert Is Lying: Stop Alerting on Percentages", + "zh_title": "你的“磁盘将满”告警在说谎:别再按百分比告警", + "category": "devops", + "pub_date": "2026-06-24", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 06:09", + "notes": "第一批外部顾问审阅完成,已全量采纳并落地修改(含数字矛盾纠正/OPSEC与禁用词清理/并发异常409映射/双语CTA补齐)", + "en_chars": 6139, + "zh_chars": 3090 + }, + "yue2-on-a-12gb-gpu-songs-that-lost-their-endings": { + "slug": "yue2-on-a-12gb-gpu-songs-that-lost-their-endings", + "title": "Why My AI Songs Kept Losing Their Endings: Running YuE2 on a 12 GB GPU", + "zh_title": "为什么我的 AI 歌曲总是丢结尾:在 12GB 显卡上跑 YuE2", + "category": "ai", + "pub_date": "2026-10-20", + "status": "approved", + "batch_id": "Batch-01", + "approved_at": "2026-10-27 05:26", + "notes": "外部顾问审阅完成,已采纳并落地全量修改意见(含GPU/Token成本算账、Gemini净化、双语CTA、代码折叠与简繁转换)", + "en_chars": 28955, + "zh_chars": 24414 + }, + "when-lowering-crf-does-nothing-maxrate-deadlock": { + "slug": "when-lowering-crf-does-nothing-maxrate-deadlock", + "title": "When Lowering CRF Does Nothing: The -maxrate Ceiling and the SSIM Quality Deadlock", + "zh_title": "当降低 CRF 毫无效果:视频转码中 -maxrate 封顶与 SSIM 质量门禁的死锁", + "category": "devops", + "pub_date": "2026-10-27", + "status": "approved", + "batch_id": "Batch-07", + "approved_at": "2026-10-28 09:32", + "notes": "Batch-07 外部顾问审阅「需微调 → 通过」(事实4.8/人设4.7/CRO4.8/OPSEC4.7)。已落地:清日志残留符号、499 跨跳因果改写、I/O 余量措辞、details class+行数、双语第二人称清零、three→four、中文与含反引号标题补显式锚点;详见 report-batch-07.md", + "en_chars": 8970, + "zh_chars": 5317 + }, + "why-ffmpeg-map-0-breaks-your-mp4-and-mkv-remux": { + "slug": "why-ffmpeg-map-0-breaks-your-mp4-and-mkv-remux", + "title": "Why ffmpeg -map 0 Breaks Your Remux: The Hidden Matrix of Multi-Track MP4 and Matroska", + "zh_title": "为什么 ffmpeg -map 0 会搞崩混流:跨容器多音轨、字幕与字体的深水区", + "category": "engineering", + "pub_date": "2026-10-27", + "status": "approved", + "batch_id": "Batch-07", + "approved_at": "2026-10-28 09:32", + "notes": "Batch-07 外部顾问审阅「需微调 → 通过」(事实4.8/人设4.7/CRO4.8/OPSEC4.7)。已落地:清日志残留符号、499 跨跳因果改写、I/O 余量措辞、details class+行数、双语第二人称清零、three→four、中文与含反引号标题补显式锚点;详见 report-batch-07.md", + "en_chars": 11008, + "zh_chars": 7574 + }, + "when-a-proxy-timeout-returns-html-instead-of-json": { + "slug": "when-a-proxy-timeout-returns-html-instead-of-json", + "title": "When a Proxy Timeout Returns HTML Instead of JSON: A 499 Post-Mortem", + "zh_title": "反代超时返回 HTML,前端报 SyntaxError:一次 499 事故复盘", + "category": "engineering", + "pub_date": "2026-10-28", + "status": "approved", + "batch_id": "Batch-07", + "approved_at": "2026-10-28 09:32", + "notes": "Batch-07 外部顾问审阅「需微调 → 通过」(事实4.8/人设4.7/CRO4.8/OPSEC4.7)。已落地:清日志残留符号、499 跨跳因果改写、I/O 余量措辞、details class+行数、双语第二人称清零、three→four、中文与含反引号标题补显式锚点;详见 report-batch-07.md", + "en_chars": 15742, + "zh_chars": 8940 + }, + "architecting-sea-payment-and-offline-licensing-pipelines": { + "slug": "architecting-sea-payment-and-offline-licensing-pipelines", + "title": "Delivering Commercial Desktop Software in Southeast Asia: Multi-Gateway Billing with toyyibPay and Keygate Fulfillment", + "zh_title": "在东南亚交付桌面商业软件:对接 toyyibPay 本地网银与 Keygate 机器码离线授权", + "category": "case-studies", + "pub_date": "2026-10-27", + "status": "pending", + "batch_id": null, + "approved_at": null, + "notes": "", + "en_chars": 9252, + "zh_chars": 5620 + }, + "batch-generating-80-technical-covers-with-comfyui": { + "slug": "batch-generating-80-technical-covers-with-comfyui", + "title": "Batch Generating 80+ Technical Covers with ComfyUI: Constraining Style, Space, and Typography", + "zh_title": "用 ComfyUI 批量闭环 80+ 张技术图文封面:设计规范约束、留白控制与批处理工作流", + "category": "ai", + "pub_date": "2026-10-27", + "status": "pending", + "batch_id": null, + "approved_at": null, + "notes": "", + "en_chars": 10963, + "zh_chars": 7049 + }, + "traefik-yaegi-plugin-package-name-trap": { + "slug": "traefik-yaegi-plugin-package-name-trap", + "title": "Why Your Traefik Yaegi Plugin Fails on Boot: The Module Hyphen to Underscore Trap", + "zh_title": "Traefik 自定义插件启动崩溃:Yaegi 解释器的连字符与下划线陷阱", + "category": "notes", + "pub_date": "2026-10-27", + "status": "pending", + "batch_id": null, + "approved_at": null, + "notes": "", + "en_chars": 6896, + "zh_chars": 4766 + }, + "authentik-oidc-redirect-loop-node-set-header-overwrite": { + "slug": "authentik-oidc-redirect-loop-node-set-header-overwrite", + "title": "The Authentik OIDC Redirect Loop: Why Node.js res.setHeader Overwrites Rather Than Appends", + "zh_title": "Authentik OIDC 重定向死循环:Node.js res.setHeader 整头覆盖机制与会话丢失排障", + "category": "engineering", + "pub_date": "2025-02-14", + "status": "approved", + "batch_id": "Batch-08", + "approved_at": "2026-11-01 14:55", + "notes": "Batch-08 顾问审阅「通过」:修正 Bento 卡片行数与摘要对齐、中文标题去个人化、details 标注 12 行。Playwright 真实测试通过。", + "en_chars": 6367, + "zh_chars": 3970 + }, + "six-chrome-containers-locked-my-nas-btrfs-d-state-igpu-offload": { + "slug": "six-chrome-containers-locked-my-nas-btrfs-d-state-igpu-offload", + "title": "Why Six Desktop Chrome Containers Locked a Production NAS: CPU WebGL Emulation, Btrfs D-State, and Offloading to iGPU", + "zh_title": "六个 Chrome 容器打崩存储 NAS:CPU 软解 WebGL、Btrfs D 状态死锁与核显架构迁移", + "category": "devops", + "pub_date": "2025-03-20", + "status": "approved", + "batch_id": "Batch-08", + "approved_at": "2026-11-01 14:55", + "notes": "Batch-08 顾问审阅「通过」:修正 7.5GB 提交内存空间、折叠 docker-compose 配置(17行)、正文对齐 unRaid 宿主、英文标题去个人化。", + "en_chars": 6351, + "zh_chars": 3892 + }, + "cascading-docker-failures-nocodb-heap-limit-starved-n8n": { + "slug": "cascading-docker-failures-nocodb-heap-limit-starved-n8n", + "title": "Cascading Docker Failures: When a Node.js Heap Limit in NocoDB Starved an n8n Automation Stack", + "zh_title": "Docker 级联故障复盘:NocoDB 内存溢出如何拖垮整套 n8n 自动化栈", + "category": "devops", + "pub_date": "2025-04-12", + "status": "approved", + "batch_id": "Batch-08", + "approved_at": "2026-11-01 14:55", + "notes": "Batch-08 顾问审阅「通过」:校准 V8 堆内存与 cgroup 启发式机制描述、修正 details 为 17 行、对齐 Compose 语法。", + "en_chars": 5885, + "zh_chars": 3834 + }, + "apple-icloud-smtp-port-465-timeout-starttls-587-trap": { + "slug": "apple-icloud-smtp-port-465-timeout-starttls-587-trap", + "title": "Apple iCloud SMTP Times Out on Port 465: The 587 STARTTLS-Only Trap", + "zh_title": "Apple iCloud 邮箱发信 465 超时:仅支持 587 STARTTLS 的协议陷阱", + "category": "notes", + "pub_date": "2026-06-18", + "status": "approved", + "batch_id": "Batch-08", + "approved_at": "2026-11-01 14:55", + "notes": "Batch-08 顾问审阅「通过」:明确 Windows Winsock 10060 运行环境、校准 RFC 6409/3207 与 RFC 8314 演进史、中文标签去个人化、details 标注 11 行。", + "en_chars": 5070, + "zh_chars": 3550 + }, + "home-assistant-container-upgrade-ladder-four-hops": { + "slug": "home-assistant-container-upgrade-ladder-four-hops", + "title": "Home Assistant Container: Upgrading Thirteen Releases Across a Four-Hop Ladder", + "zh_title": "Home Assistant 容器版跨版本升级:四阶安全平跳十三期迭代", + "category": "devops", + "pub_date": "2026-07-22", + "status": "approved", + "batch_id": "Batch-08", + "approved_at": "2026-11-01 14:55", + "notes": "Batch-08 顾问审阅「通过」:将智能家居升维为机房物联网动环监控、平滑 2026.8 反代表述、修正 details 为 14 行。", + "en_chars": 5593, + "zh_chars": 3422 + }, + "when-a-self-hosted-model-router-tripped-trust-and-safety": { + "slug": "when-a-self-hosted-model-router-tripped-trust-and-safety", + "title": "When a Self-Hosted Model Gateway Tripped Trust & Safety: Six Seats, Nine Seconds", + "zh_title": "自建模型网关触发风控复盘:九秒之内,六个席位全部熔断", + "category": "ai", + "pub_date": "2026-11-06", + "status": "approved", + "batch_id": "Ad-hoc-2026-11-06", + "approved_at": "2026-11-06 08:05", + "notes": "Argon 两轮审阅(博客正文 + LinkedIn/dev.to 分发稿)完成并全量采纳:时间戳去具体化、席位watch流重定性、403->502 错误契约修复、双档成本可复算、wrapper 改称 task dispatcher", + "en_chars": 13600, + "zh_chars": 6639 + } } \ No newline at end of file diff --git a/docs/devto/when-a-self-hosted-model-router-tripped-trust-and-safety.md b/docs/devto/when-a-self-hosted-model-router-tripped-trust-and-safety.md new file mode 100644 index 0000000..3f7c006 --- /dev/null +++ b/docs/devto/when-a-self-hosted-model-router-tripped-trust-and-safety.md @@ -0,0 +1,96 @@ +*Originally published on my blog — [read it there with the full context, code and updates](https://blog.hoelee.com/posts/when-a-self-hosted-model-router-tripped-trust-and-safety/).* + +A single self-hosted OpenAI-compatible gateway served every model tier: long-context reading, refactors, cheap background jobs. One key, one model picker, one dashboard where spend was visible in one place. Six independent workflow seats — repo indexing, CI triage, interactive refactors — sat behind it, each authenticating with its own subscription OAuth credential. + +Then all six came back disabled inside one short detection window. None of them had been doing anything unusual. What tripped was a scheduled credential check, and what it found was a block already applied upstream. + +The interesting part was not the block. It was the ten minutes I spent rotating credentials that were never wrong. + +## The error that sent me the wrong way + +For the first ten minutes, every consumer of the gateway's error reported a credential failure — orchestrator, shell probe, CLI — because that is what the body said: `401 invalid_api_key`. The upstream had actually returned a policy decision. + +Three probes separated the layers, and they are worth keeping for any "the model stopped answering" ticket: confirm the gateway answers its own health endpoint; confirm a model from an unrelated provider succeeds on the same key; then loop the subscription models and compare the error codes. One model failing means a broken model. Every model on one provider failing identically, while other providers succeed on the same key, means the provider relationship is closed — and no key rotation moves that. + +The real defect is the error contract, not the outage: + +```python +# Before: policy decisions disguised as auth failures +if upstream.status == 403: + return jsonify(error="invalid_api_key"), 401 + +# After: distinct classes, distinct client behaviour +if "Terms of Service" in upstream.text or "disabled in this account" in upstream.text: + return jsonify(error="upstream_policy_block", + upstream_status=403, + alert="credential-policy"), 502 +return jsonify(error="invalid_api_key"), 401 +``` + +A 401 that means "your key is wrong" and a 403 that means "the vendor closed this seat" must never collapse into the same response. Flattening them costs the responder the first ten minutes of every incident. + +## Why a shared token is not a shared key + +An API key is a bearer credential: whoever holds it can use it. An OAuth token is bound to a client identity — client id, consent record, scopes. When a different process presents that token, the request keeps the claims and loses the behaviour: headers, TLS fingerprint, request cadence. + +Two properties of the initial gateway topology made the pattern obvious to any detection system: + +- **One egress.** Every seat left through one container on a single public address. +- **One fingerprint.** The gateway normalised headers and stripped client telemetry, so six workflow seats looked like one client at six times the volume. + +Simultaneous failures across independent workflows are not six human reviews. They are one deterministic cluster rule firing. The vendor's terms had said it in advance: scripting the official CLI is acceptable, pooling seats or automating rotation across them is not. + +## Task dispatch instead of routing + +The lesson is not "use fewer seats". It is that a subscription is a seat, not an API. The moment its token leaves the vendor's client, it becomes a shared credential. + +So the gateway keeps serving models paid for by API key, and subscription work moved to task dispatch: the orchestrator runs isolated, single-task invocations of the official CLI bound to a dedicated workflow seat. No pooled credentials, no hijacked persistent session, no rotation. The token never leaves the vendor's binary. + +``` +orchestrator -> task dispatcher -> official CLI (owns the token) -> vendor + | + +- cwd, permission scope, timeout, tree-kill, no retries +``` + +Four dispatcher properties mattered more than any prompt: + +1. **Explicit working directory** — each task starts inside one repository, never a user home directory or a filesystem root, because an agent with edit rights inherits the whole shell. +2. **Closed stdin** — interactive prompts in a headless run become errors instead of silent hangs. One forgotten `-y` otherwise blocks the parent forever. +3. **Process-group kill on timeout** — killing the parent leaves grandchildren running; kill the group and return a timeout code, not a half-finished diff. +4. **No automatic retries** — an inner agent loop times an outer retry policy is hundreds of upstream calls in minutes, the exact cadence that got the previous design flagged. + +```python +proc = subprocess.Popen( + argv, cwd=repo, stdin=subprocess.DEVNULL, # no interactive hangs + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + start_new_session=True, # own process group +) +try: + out, err = proc.communicate(timeout=task_timeout) +except subprocess.TimeoutExpired: + os.killpg(proc.pid, signal.SIGKILL) # kill the whole tree + raise +``` + +Context stopped travelling in prompts as well: the delegate reads repository instruction files and queries a self-hosted memory service over MCP, so a brief stays six lines instead of a transcript dump. Delegate writes are tagged `status: draft` and final archiving stays with the orchestrator. + +## The economics survived, because the workload splits + +Nine turns in ten are routine orchestration — tool loops, retrieval, cron, log triage, short edits. Those run on a curated open-weight plan: $10/month carrying a **$100 monthly allowance** on the fast model, with per-model pools beside it. At $0.15 per million input tokens and $0.60 per million output, a 4k-in/500-out turn costs about `$0.0009` — under a tenth of a cent. + +The remaining tenth is heavy engineering: multi-file refactors, legacy archaeology, anything with a real build-and-test contract. That dispatches to an authorised CLI seat, where the vendor's client owns the credential and the allowance belongs to the subscription rather than to a pooled bucket. + +Pay for frequency with API-key models, pay for depth with authorised seats, and never let the two share a token. + +### 💬 Let's Compare Notes + +If a credential is bound to a client, where should a platform team draw the execution boundary when metered APIs and client-bound CLI tools have to coexist: standardise on pure APIs and pay the premium, or run a dispatcher that governs official binaries? I have moved to the second, and I would like to hear from anyone who has run the first at team scale and made it survive an audit. + +Two related posts from the same infrastructure work: + +- [Slashing agent context with a code graph](https://blog.hoelee.com/posts/slashing-agent-context-with-a-code-graph/) +- [It reported success. Nothing had changed.](https://blog.hoelee.com/posts/it-reported-success-nothing-had-changed/) + +--- + +*I write about self-hosted infrastructure, DevOps and the failures behind them at [blog.hoelee.com](https://blog.hoelee.com/).* diff --git a/docs/linkedin/when-a-self-hosted-model-router-tripped-trust-and-safety.first-comment.txt b/docs/linkedin/when-a-self-hosted-model-router-tripped-trust-and-safety.first-comment.txt new file mode 100644 index 0000000..90faa45 --- /dev/null +++ b/docs/linkedin/when-a-self-hosted-model-router-tripped-trust-and-safety.first-comment.txt @@ -0,0 +1 @@ +🔗 Full post-mortem, the isolation probes and the dispatcher core: https://blog.hoelee.com/posts/when-a-self-hosted-model-router-tripped-trust-and-safety/ diff --git a/docs/linkedin/when-a-self-hosted-model-router-tripped-trust-and-safety.md b/docs/linkedin/when-a-self-hosted-model-router-tripped-trust-and-safety.md new file mode 100644 index 0000000..d647475 --- /dev/null +++ b/docs/linkedin/when-a-self-hosted-model-router-tripped-trust-and-safety.md @@ -0,0 +1,13 @@ +I spent ten minutes rotating credentials that were never wrong — because my AI gateway translated an upstream policy block into HTTP 401. + +In the initial architecture, a self-hosted gateway unified every model tier behind a single interface: long-context reading, refactors, cheap background jobs. One key, one picker, one spend dashboard. Good engineering for a real problem, missing exactly one thing: a boundary. + +An OAuth token is bound to the client that obtained it. Isolated workflow seats egressing through a single container with normalised headers strip client telemetry and read as one anomalous cluster. Simultaneous trips across independent workflows are not manual reviews — they are one deterministic cluster rule firing. + +The root failure was not the upstream block, it was the error contract: turning an upstream HTTP 403 policy rejection into an OpenAI-style 401 invalid_api_key blinded every downstream consumer. That mapping is now fixed — policy blocks return 502 with an explicit upstream_policy_block signal and raise an alert, while credential failures stay 401. + +What replaced the pooled design: task dispatch. The orchestrator dispatches isolated, single-task invocations to the vendor's official CLI bound to a dedicated workflow seat — no credential pooling, no session hijacking, no rotation. Explicit working directory, closed stdin, process-group kill on timeout, zero automatic retries. Context moves through repository instruction files and an MCP memory service, not transcript dumps. + +The economics held because the workload splits: nine turns in ten are routine orchestration on a $10 plan carrying a $100 model allowance (about $0.0009 for a 4k/500 turn), and the last tenth is heavy engineering on an authorised seat. + +For platform teams building internal AI planes: where do you draw the execution boundary when mixing metered APIs with client-bound CLI tools — strict pure-API standardisation at ten times the cost, or a hybrid dispatcher governing official binaries? Where has that boundary broken under load? diff --git a/public/banners/when-a-self-hosted-model-router-tripped-trust-and-safety.jpg b/public/banners/when-a-self-hosted-model-router-tripped-trust-and-safety.jpg new file mode 100644 index 0000000..877ca5f Binary files /dev/null and b/public/banners/when-a-self-hosted-model-router-tripped-trust-and-safety.jpg differ diff --git a/public/banners/when-a-self-hosted-model-router-tripped-trust-and-safety.webp b/public/banners/when-a-self-hosted-model-router-tripped-trust-and-safety.webp new file mode 100644 index 0000000..0730df4 Binary files /dev/null and b/public/banners/when-a-self-hosted-model-router-tripped-trust-and-safety.webp differ diff --git a/public/og/thumb/when-a-self-hosted-model-router-tripped-trust-and-safety-400.webp b/public/og/thumb/when-a-self-hosted-model-router-tripped-trust-and-safety-400.webp new file mode 100644 index 0000000..64ada9a Binary files /dev/null and b/public/og/thumb/when-a-self-hosted-model-router-tripped-trust-and-safety-400.webp differ diff --git a/public/og/thumb/when-a-self-hosted-model-router-tripped-trust-and-safety-800.webp b/public/og/thumb/when-a-self-hosted-model-router-tripped-trust-and-safety-800.webp new file mode 100644 index 0000000..4c0bcb4 Binary files /dev/null and b/public/og/thumb/when-a-self-hosted-model-router-tripped-trust-and-safety-800.webp differ diff --git a/public/og/when-a-self-hosted-model-router-tripped-trust-and-safety.jpg b/public/og/when-a-self-hosted-model-router-tripped-trust-and-safety.jpg new file mode 100644 index 0000000..b0eb54f Binary files /dev/null and b/public/og/when-a-self-hosted-model-router-tripped-trust-and-safety.jpg differ diff --git a/public/og/zh-when-a-self-hosted-model-router-tripped-trust-and-safety.jpg b/public/og/zh-when-a-self-hosted-model-router-tripped-trust-and-safety.jpg new file mode 100644 index 0000000..4e63bde Binary files /dev/null and b/public/og/zh-when-a-self-hosted-model-router-tripped-trust-and-safety.jpg differ diff --git a/scripts/og-gen/art-meta.json b/scripts/og-gen/art-meta.json index 7c8b4bd..5023e1a 100644 --- a/scripts/og-gen/art-meta.json +++ b/scripts/og-gen/art-meta.json @@ -364,5 +364,10 @@ "evidence": "SSIM >= 0.995 · 10,000+ assets · 0 corrupt stubs", "ogTitle_en": "5 Quality Gates for FFmpeg", "ogTitle_zh": "视频转码流水线五道质量门禁" + }, + "when-a-self-hosted-model-router-tripped-trust-and-safety": { + "evidence": "6 seats - 9 s - 403 to 502 mapping", + "ogTitle_en": "The Gateway That Tripped Trust & Safety", + "ogTitle_zh": "网关撞上风控" } } \ No newline at end of file diff --git a/scripts/og-gen/banner-plan.json b/scripts/og-gen/banner-plan.json index c0db585..bba06ff 100644 --- a/scripts/og-gen/banner-plan.json +++ b/scripts/og-gen/banner-plan.json @@ -2403,6 +2403,30 @@ 1360, 768 ] + }, + "when-a-self-hosted-model-router-tripped-trust-and-safety": { + "sw": 1930, + "sh": 1090, + "left": 604, + "top": 102, + "fill": [ + 14, + 18, + 30 + ], + "zoom": 1.419, + "silhouette": 0.47, + "cut": 0.0, + "band": [ + 764, + 123, + 1294, + 655 + ], + "source_size": [ + 1360, + 768 + ] } } } \ No newline at end of file diff --git a/src/content/posts/when-a-self-hosted-model-router-tripped-trust-and-safety.md b/src/content/posts/when-a-self-hosted-model-router-tripped-trust-and-safety.md new file mode 100644 index 0000000..80aca9f --- /dev/null +++ b/src/content/posts/when-a-self-hosted-model-router-tripped-trust-and-safety.md @@ -0,0 +1,172 @@ +--- +ogImage: "/og/when-a-self-hosted-model-router-tripped-trust-and-safety.jpg" +banner: "/banners/when-a-self-hosted-model-router-tripped-trust-and-safety.jpg" +title: "When a Self-Hosted Model Gateway Tripped Trust & Safety: Six Seats, Nine Seconds" +description: "A post-mortem of consolidating subscription seats behind one OpenAI-compatible gateway: the OAuth client boundary that was crossed, the cluster block that followed nine seconds later, and the task-dispatch architecture that replaced it." +pubDate: 2026-11-06 +category: ai +blueprint: ai-agent-automation +blueprintLayer: L2-orchestration +tags: ["ai-agents", "oauth", "terms-of-service", "post-mortem", "self-hosting", "model-routing"] +draft: false +summary: + audience: "Platform Engineers, AI Tooling Builders & Engineering Leads" + painPoint: "One OpenAI-compatible gateway served every model behind a single key, until six subscription seats were disabled inside a nine-second window and every chat request started failing with a misleading 401." + impact: + - metric: "6 Seats, 9 Seconds" + detail: "A scheduled credential health check hit all six connections at once; every one came back disabled. Cluster-level detection, not six individual reviews." + - metric: "1 Wrong Error Code" + detail: "The gateway translated an upstream policy block into an OpenAI-style 401 invalid_api_key, so the first ten minutes of diagnosis went into credentials that were never wrong." + - metric: "10x Cost Leverage Kept" + detail: "The replacement two-tier design keeps a $10 subscription carrying a $100 model allowance for routine orchestration, while heavy work dispatches to official CLI seats." + stack: ["OpenAI-compatible gateway", "OAuth 2.0 token reuse", "Official vendor CLI", "MCP", "Structured post-mortem"] + jumpLinks: + business: "#the-nine-second-block" + architecture: "#why-a-shared-token-is-not-a-shared-key" + result: "#task-dispatch-instead-of-routing" +dynamicCta: true +--- + +## The Nine Second Block + +For eight months a self-hosted gateway on my internal orchestration node served every model behind one key and one model picker: a long-context model for reading large repos, a frontier model for refactors, an open-weight model for cheap background jobs. One endpoint, one dashboard, one place to see spend. It worked well enough that I stopped noticing what it was actually doing — presenting subscription OAuth tokens from software the vendor never authorised. + +On a recent Friday morning, in the middle of a routine maintenance window, that arrangement ended in nine seconds. The gateway's scheduled credential health check fired across all six connections at once, each returning: + +``` +403 This service has been disabled in this account +for violation of Terms of Service. +Please submit an appeal to continue using this product. +``` + +The first connection failed immediately, and the last followed inside the same nine-second window. None of the six seats had been doing anything unusual; the sweep itself was the trigger, and it found a block that had already been applied upstream. The gateway's own database flipped each connection to `test_status = banned`, `error_code = 403` in a single pass, and the first failed chat in the orchestrator log landed two minutes later. + +The failure mode that mattered was not the block. It was the ten minutes of confident misdiagnosis that followed, because the gateway surfaced an upstream policy decision as an HTTP 401 with `invalid_api_key`. Every client that consumed that error — the orchestrator, shell probes, the CLI — reported a credential problem. Those ten minutes went into rotating keys that were never the issue. + +## Why a Shared Token Is Not a Shared Key + +An API key is a bearer credential issued to whoever holds it. An OAuth token is not. It is bound to a client identity: a client id, a consent record, and scopes granted to a specific application. When a third-party process presents that token, the request keeps the original client's claims but loses the original client's behaviour — headers, TLS fingerprint, request cadence. + +That gap is precisely what Trust & Safety systems are built to detect, and the vendor's terms said so in advance. The acceptable-access clause prohibited reaching the service through third-party software, and the FAQ drew the line finer still: scripting the official CLI directly was acceptable, while pooling seats or automating rotation across them was not supported. + +Two properties of my setup made the pattern unmistakable: + +- **One egress.** All six seats left through the same container on the same public address, so every request shared one network identity. +- **One fingerprint.** The gateway normalised headers and stripped client telemetry, so six seats looked like one client generating six times the traffic. + +Nine seconds for six connections is not six human reviews. It is one rule matching one cluster. The seats had been configured for separate workflows — interactive work, CI jobs, staging probes — and I had collapsed their credentials into a single routing layer for convenience. That consolidation is the mistake, and it is independent of how many seats exist. + +## The Forensic Trail + +The useful part of a post-mortem is the evidence an engineer can re-derive, not the conclusion. Three artifacts settled it within an hour. + +| Artifact | What it proved | +|---|---| +| Gateway health endpoint | The gateway itself was healthy: HTTP 200, models listed, unrelated providers answering normally | +| A non-subscription model on the same key | Returned 200 with tool calls, so the credential and the routing layer were both fine | +| Every subscription model | Returned the identical 401, seconds apart, with the upstream 403 embedded in the body | + +The third row is the one that matters. When one model fails, the model is broken. When every model on one provider fails identically while every other provider on the same key succeeds, the failure lives in the provider relationship, and no amount of key rotation will move it. + +The gateway's connection table confirmed the state, and its call log gave the shape of the event: + +``` +t+0s connectivity sweep 6 connections -> 403 policy block +t+9s last connection 6/6 disabled +t+28s orchestrator first failed chat request +t+96s connection table rows flipped to banned / 403 +``` + +
+🔍 The full isolation probe: four commands that separate credential, gateway and provider (27 lines) + +```bash +# 1. Is the gateway itself alive? +curl -s -o /dev/null -w '%{http_code}\n' "$GW/api/health" +200 + +# 2. Does a model from an unrelated provider answer on the same key? +curl -s "$GW/v1/chat/completions" \ + -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \ + -d '{"model":"open-weight/unrelated","messages":[{"role":"user","content":"reply OK"}]}' \ + | jq -r '.choices[0].finish_reason' +tool_calls + +# 3. Do the subscription models fail identically? +for m in subscription/fast subscription/deep subscription/alt; do + curl -s "$GW/v1/chat/completions" \ + -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \ + -d "{\"model\":\"$m\",\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}" \ + | jq -r '.error.code // .choices[0].finish_reason' +done +invalid_api_key +invalid_api_key +invalid_api_key + +# 4. What does the gateway's own connection table say? +sqlite3 gateway.db \ + "select provider, test_status, error_code, count(*) + from connections group by 1,2,3;" +subscription|banned|403|6 +``` + +Row 2 succeeding while row 3 fails three times identically is the whole diagnosis: the key works, the gateway works, the provider relationship is closed. + +
+ +## Task Dispatch Instead of Routing + +The lesson is not "use fewer seats". The lesson is that a subscription is a **seat**, not an **API**. A seat grants a human, through the vendor's own client, the right to work. The moment its token leaves that client, the seat becomes a shared credential, and enforcement treats it as one. + +So the architecture changed shape. The gateway keeps serving models paid for by API key — that part was never the problem. Subscription work moved to **task dispatch**: the orchestrator runs isolated, single-task invocations of the vendor's official CLI under a seat it is authorised to use, and the token never leaves the vendor's binary. No pooled credentials, no persistent hijacked session, no rotation across seats. + +``` +orchestrator -> task dispatcher -> official CLI (owns the token) -> vendor + | + +- cwd, permission scope, timeout, tree-kill, no retries +``` + +Four properties of the dispatcher did more for safety than any prompt: + +1. **Explicit working directory.** Each task starts inside one repository, never in a user home directory or a filesystem root, because an agent with edit rights inherits the whole shell of the user running it. +2. **Closed stdin.** Interactive prompts inside a headless run — a package manager asking to initialise, an SSH client asking to trust a host key — become errors instead of silent hangs. Without this, one forgotten `-y` blocks the parent process forever. +3. **Process-group kill on timeout.** Killing the parent leaves grandchildren running; the dispatcher kills the whole group and returns a timeout code instead of a half-finished diff. +4. **No automatic retries.** An inner agent loop multiplied by an outer retry policy produces hundreds of upstream calls in minutes — exactly the cadence the previous design was flagged for. + +A minimal version of the execution core is small enough to audit in one screen: + +```python +proc = subprocess.Popen( + argv, cwd=repo, stdin=subprocess.DEVNULL, # no interactive hangs + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + start_new_session=True, # own process group +) +try: + out, err = proc.communicate(timeout=task_timeout) +except subprocess.TimeoutExpired: + os.killpg(proc.pid, signal.SIGKILL) # kill the whole tree + out, err = proc.communicate() + return {"status": "timeout", "detail": err[-2000:]} +``` + +Context stopped travelling in prompts as well. The delegate reads repository instruction files and queries a self-hosted memory service over MCP, so a brief stays six lines instead of a transcript dump. Delegate writes are tagged `status: draft`, and final archiving stays with the orchestrator — a subagent that records its own failed attempts as fact poisons every later job. + +One more fix belongs here, because it addresses the original ten wasted minutes: retained API-key traffic still passes through the gateway, and its error mapping now distinguishes classes instead of flattening them. An upstream policy block surfaces as `502` with `upstream_policy_block` metadata and fires an alert; authentication failures stay `401`. A real credential problem and a vendor decision no longer look identical from the client side. + +## The Economics Still Work + +Dropping OAuth reuse could have cost more than it saved, so the replacement had to clear a cost bar. It does, because the workload splits cleanly in two. + +**Routine orchestration — roughly nine turns in ten.** High-frequency tool loops, retrieval, cron jobs, log triage and short code edits run on a curated open-weight plan: $10/month carrying a **$100 monthly allowance** on the fast model, with separate per-model pools beside it. Off-peak pricing is $0.15 per million input tokens and $0.60 per million output, so a 4k-in/500-out turn costs `4000 × 0.15/1e6 + 500 × 0.60/1e6 ≈ $0.0009` — just under a tenth of a cent. Two guardrails keep that allowance honest: per-model pools, so throttling one model does not stop the pipeline, and hard escalation rules — two failed attempts on the same error, three or more files touched, more than five exploratory steps, or any irreversible production operation moves the task off the cheap model before it burns the allowance. + +**Heavy engineering — the remaining tenth.** Multi-file refactors, legacy archaeology and anything with a real build-and-test contract dispatch to an official CLI seat, where the vendor's own client owns the credential and the allowance is the subscription's, not a pooled one. + +That split is the actual answer to keeping this cheap without crossing the boundary: pay for frequency with API-key models, pay for depth with authorised seats, and never let the two share a token. + +## Reflection + +What I keep from this is that the gateway was genuinely good engineering solving a real problem — one interface for many providers, one place to see spend, failover that worked. What it lacked was a boundary. A credential issued to one client cannot be re-hosted by another and expected to keep its identity, and no amount of clean code changes what the enforcement layer sees: one address, six connections, one fingerprint. + +The pooled pattern is now permanently retired. The seats that remain are used the way the vendor intends — through its client, one task at a time, at a cadence a human developer could plausibly produce — and the compliance work to move subscription access onto properly licensed tiers is in progress. + +Task dispatch is slower to build and uglier to debug than a router that answers for everything. It is also the only version of this that survives an audit. diff --git a/src/content/posts/zh/when-a-self-hosted-model-router-tripped-trust-and-safety.md b/src/content/posts/zh/when-a-self-hosted-model-router-tripped-trust-and-safety.md new file mode 100644 index 0000000..eaf22ec --- /dev/null +++ b/src/content/posts/zh/when-a-self-hosted-model-router-tripped-trust-and-safety.md @@ -0,0 +1,172 @@ +--- +ogImage: "/og/zh-when-a-self-hosted-model-router-tripped-trust-and-safety.jpg" +banner: "/banners/when-a-self-hosted-model-router-tripped-trust-and-safety.jpg" +title: "自建模型网关触发风控复盘:九秒之内,六个席位全部熔断" +description: "一次把订阅席位收敛到单一 OpenAI 兼容网关的事故复盘:被越过的 OAuth 客户端边界、九秒后到来的集群封锁,以及取代它的任务分派架构。" +pubDate: 2026-11-06 +category: ai +blueprint: ai-agent-automation +blueprintLayer: L2-orchestration +tags: ["ai-agents", "oauth", "terms-of-service", "post-mortem", "self-hosting", "model-routing"] +draft: false +summary: + audience: "平台工程师、AI 工具链开发者与研发负责人" + painPoint: "一个 OpenAI 兼容网关把全部模型收在单一密钥之后,直到六个订阅席位在九秒内同时被停用,此后每一次对话请求都以一条误导性的 401 失败。" + impact: + - metric: "六席位 · 九秒" + detail: "一次计划内的凭据健康检查同时打到六条连接,六条全部返回停用。这是集群级检测,不是六次独立审核。" + - metric: "一个错误码" + detail: "网关把上游的策略决定翻译成 OpenAI 风格的 401 invalid_api_key,前十分钟的诊断因此全部指向了从未出错的凭据。" + - metric: "十倍成本杠杆保留" + detail: "替代的双层设计保住了 10 美元订阅换 100 美元模型额度用于常规编排,重度工作则分派给官方 CLI 席位。" + stack: ["OpenAI 兼容网关", "OAuth 2.0 令牌复用", "厂商官方 CLI", "MCP", "结构化复盘"] + jumpLinks: + business: "#the-nine-second-block" + architecture: "#why-a-shared-token-is-not-a-shared-key" + result: "#task-dispatch-instead-of-routing" +dynamicCta: true +--- + +## 九秒熔断 + +八个月里,内部编排节点上的一个自建网关把我用到的全部模型收进一把密钥、一个模型选择器:长上下文模型读大仓库,前沿模型做重构,开源权重模型跑廉价后台任务。一个端点、一块面板、一处花费视图。它运转得足够顺,以至于我不再去注意它实际在做的事——用厂商从未授权的软件出示订阅的 OAuth 令牌。 + +某个周五凌晨的维护窗口里,这套安排以九秒收场。网关计划内的凭据健康检查同时打到全部六条连接,每一条都返回: + +``` +403 This service has been disabled in this account +for violation of Terms of Service. +Please submit an appeal to continue using this product. +``` + +第一条响应立刻失败,最后一条落在同一个九秒窗口内。六个席位当时都没有异常行为;触发者是这次探针本身,它撞上的是上游早已生效的封锁标记。网关自己的数据库在一次写入里把每条连接改成 `test_status = banned`、`error_code = 403`,编排日志里第一条失败的对话在两分钟后出现。 + +真正要命的不是封锁,而是随后十分钟十分自信的误判:网关把上游的策略决定以 HTTP 401 `invalid_api_key` 的形式抛出。消费这条错误的每一个客户端——编排器、shell 探测、CLI——都报告凭据有问题。那十分钟全花在轮换从未出错的密钥上。 + +## 共享令牌为何不等于共享密钥 + +API 密钥是签发给持有一方的持有者凭据。OAuth 令牌不是。它绑定的是一个客户端身份:客户端 ID、一份同意记录、一组授予特定应用的权限范围。当第三方进程出示这份令牌,请求仍然带着原客户端的声明,却丢掉了原客户端的行为——请求头、TLS 指纹、请求节奏。 + +这个缺口正是风控系统被造出来要检测的东西,而厂商条款事先就写清了。可接受访问的条款禁止通过第三方软件触达服务,FAQ 把界线划得更细:直接脚本化官方 CLI 可以接受,而汇集席位、或自动化地在席位之间轮换,不受支持。 + +我自己的两个设计决定,让这个模式一目了然: + +- **单一出口。** 六个席位全部经由同一台容器、同一个公网地址发出,于是每条请求共享一个网络身份。 +- **单一指纹。** 网关规范化了请求头、剥离了客户端遥测,于是六个席位看起来像一个客户端制造了六倍流量。 + +六条连接只用九秒,不是六个人在审核六个案例,而是一条规则命中了一个集群。这些席位原本服务于不同工作流——交互式工作、CI 任务、预发布探针——我为了省事把它们的凭据收敛进了同一个路由层。那次收敛才是错误本身,与席位数无关。 + +## 取证链路 + +复盘里有价值的是别人能重新推演的证据,不是结论。三个产物在一小时内定了案。 + +| 产物 | 它证明了什么 | +|---|---| +| 网关健康端点 | 网关本身健康:HTTP 200,模型列表正常,非订阅类供应商照常应答 | +| 同一把密钥上的非订阅模型 | 返回 200 且带工具调用,说明凭据与路由层都没问题 | +| 全部订阅类模型 | 返回完全一致的 401,前后相差数秒,响应体里嵌着上游 403 | + +第三行是关键。单个模型失败,是模型坏了。一个供应商下所有模型以同样方式失败、而同一把密钥上其他供应商全部成功,故障就在供应商关系里,轮换密钥一步也挪不动它。 + +网关的连接表确认了状态,调用日志给出了事件的形状: + +``` +t+0s connectivity sweep 6 connections -> 403 policy block +t+9s last connection 6/6 disabled +t+28s orchestrator first failed chat request +t+96s connection table rows flipped to banned / 403 +``` + +
+🔍 完整隔离探测:四条命令分开凭据、网关与供应商(27 行) + +```bash +# 1. 网关自己活着吗? +curl -s -o /dev/null -w '%{http_code}\n' "$GW/api/health" +200 + +# 2. 同一把密钥上,无关供应商的模型能答吗? +curl -s "$GW/v1/chat/completions" \ + -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \ + -d '{"model":"open-weight/unrelated","messages":[{"role":"user","content":"reply OK"}]}' \ + | jq -r '.choices[0].finish_reason' +tool_calls + +# 3. 订阅类模型是否以同样方式失败? +for m in subscription/fast subscription/deep subscription/alt; do + curl -s "$GW/v1/chat/completions" \ + -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \ + -d "{\"model\":\"$m\",\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}" \ + | jq -r '.error.code // .choices[0].finish_reason' +done +invalid_api_key +invalid_api_key +invalid_api_key + +# 4. 网关自己的连接表怎么说? +sqlite3 gateway.db \ + "select provider, test_status, error_code, count(*) + from connections group by 1,2,3;" +subscription|banned|403|6 +``` + +第 2 行成功、第 3 行连续三次以同样方式失败,诊断就齐了:密钥没坏,网关没坏,是供应商关系被关闭了。 + +
+ +## 改为任务分派,而不是路由 + +教训不是"少用几个席位"。教训是:订阅是一把**席位**,不是一个**API**。席位授予的是一个人通过厂商自己的客户端工作的权利。令牌一旦离开那个客户端,席位就变成共享凭据,执法也照共享凭据处理。 + +于是架构改了形状。网关继续服务那些用 API 密钥付费的模型——那部分从来不是问题。订阅类工作改成**任务分派**:编排器在获得授权的席位下,运行厂商官方 CLI 的隔离式单任务调用,令牌始终不离开厂商的二进制文件。没有汇集凭据,没有劫持长连接会话,也没有跨席位轮换。 + +``` +orchestrator -> task dispatcher -> official CLI (owns the token) -> vendor + | + +- cwd, permission scope, timeout, tree-kill, no retries +``` + +这个分派器的四条属性,比任何提示词都更管用: + +1. **显式工作目录。** 每个任务只在某一个仓库内启动,绝不在用户主目录或文件系统根目录启动;一个拥有编辑权限的 agent 继承的是运行它的用户的整个 shell。 +2. **关闭标准输入。** headless 运行里的交互式提问——包管理器询问是否初始化、SSH 客户端询问是否信任主机密钥——会变成错误而不是静默挂起。少了这一步,一个忘写的 `-y` 就能把父进程永远卡住。 +3. **超时杀进程组。** 只杀父进程会留下孙进程;分派器杀掉整个进程组并返回超时码,而不是交回一份做了一半的 diff。 +4. **不自动重试。** 内层 agent 循环乘以外层重试策略,几分钟内就是几百次上游调用——正是上一套设计被标记的节奏。 + +执行内核的最小实现,小到可以一屏审完: + +```python +proc = subprocess.Popen( + argv, cwd=repo, stdin=subprocess.DEVNULL, # no interactive hangs + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + start_new_session=True, # own process group +) +try: + out, err = proc.communicate(timeout=task_timeout) +except subprocess.TimeoutExpired: + os.killpg(proc.pid, signal.SIGKILL) # kill the whole tree + out, err = proc.communicate() + return {"status": "timeout", "detail": err[-2000:]} +``` + +上下文也不再靠提示词搬运。委托进程从仓库级指令文件读项目规则,通过 MCP 查询自建记忆服务,所以一份委托简报只有六行,而不是一份对话倾泻。委托方写入的记忆带 `status: draft` 标记,最终归档权留在编排者手里——一个把自己失败的尝试当事实记下来的子 agent,会污染之后每一次任务。 + +还有一处修复属于这里,因为它正好回答最初浪费的那十分钟:保留下来的 API 密钥流量仍然经过网关,而它的错误映射已经改为区分类别,而不是抹平。上游的策略封锁以 `502` 加 `upstream_policy_block` 元数据呈现并触发告警;认证失败仍然是 `401`。真正的凭据问题与厂商的决定,在客户端一侧不再长得一模一样。 + +## 经济性依然成立 + +丢掉 OAuth 复用本可能让成本高过收益,所以替代方案必须先过成本线。它过了,因为负载干净地分成两半。 + +**常规编排——大约每十轮里的九轮。** 高频工具循环、检索、cron、日志分诊与短代码修改,跑在一份精选开源权重计划上:每月 10 美元,快速模型带**每月 100 美元额度**,旁边还有按模型划分的独立池。低峰价是每百万输入 token 0.15 美元、每百万输出 0.60 美元,于是一次 4k 输入 / 500 输出的回合成本为 `4000 × 0.15/1e6 + 500 × 0.60/1e6 ≈ $0.0009`,略低于十分之一美分。两个护栏让这份额度不至于成为幻想:按模型分池,一个模型被限流不至于让流水线停摆;以及硬性升级规则——同一错误连续失败两次、触及三个以上文件、超过五步探索性诊断、或任何不可逆的生产操作,都要在烧掉额度之前转到更强模型。 + +**重度工程——剩下的那一成。** 多文件重构、遗产代码考古、以及任何带真实构建与测试契约的任务,分派给官方 CLI 席位:凭据由厂商自己的客户端持有,额度属于那份订阅,而不是一个汇集池。 + +这个划分才是"怎样既便宜又不越过边界"的答案:用 API 密钥模型为频率付费,用授权席位为深度付费,绝不让两者共享同一份令牌。 + +## 反思 + +我要留下的是这一点:那个网关本身是解决真问题的好工程——多供应商一个接口、一处看花费、可用的故障转移。它缺的是一条边界。签发给某个客户端的凭据,不能被另一个客户端重新托管并指望它保留身份;代码写得再干净,也改变不了执法层看到的事实:一个地址、六条连接、一个指纹。 + +汇集模式现已永久退役。保留下来的席位按厂商意图使用——经由它的客户端、一次一个任务、频率落在人类开发者能做到的范围;而把订阅访问迁到正规授权层级的合规工作正在进行。 + +任务分派比一个什么都答的路由器更难建、更难调。它也是这套东西里唯一能扛过一次审计的版本。