From a503f1cdc8051d4afd8db916076046aa3ad4781d Mon Sep 17 00:00:00 2001 From: hoelee Date: Wed, 28 Oct 2026 09:16:54 +0800 Subject: [PATCH] docs(consultant): batch-07 review pack for the 3 published-but-unreviewed posts + tracker pending entries --- docs/consultant-review/audit-tracker.json | 2405 +++++++++-------- .../batch-07-consultant-review-pack.md | 1426 ++++++++++ 2 files changed, 2648 insertions(+), 1183 deletions(-) create mode 100644 docs/consultant-review/batch-07-consultant-review-pack.md diff --git a/docs/consultant-review/audit-tracker.json b/docs/consultant-review/audit-tracker.json index 7632a57..fc4ec20 100644 --- a/docs/consultant-review/audit-tracker.json +++ b/docs/consultant-review/audit-tracker.json @@ -1,1185 +1,1224 @@ { - "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 - } + "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": "pending", + "batch_id": "Batch-07", + "approved_at": null, + "notes": "2026-10-28 打包送审(Batch-07);发布先于审阅,结论回填后更新状态", + "en_chars": 8906, + "zh_chars": 5042 + }, + "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": "pending", + "batch_id": "Batch-07", + "approved_at": null, + "notes": "2026-10-28 打包送审(Batch-07);发布先于审阅,结论回填后更新状态", + "en_chars": 10877, + "zh_chars": 7332 + }, + "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": "pending", + "batch_id": "Batch-07", + "approved_at": null, + "notes": "2026-10-28 打包送审(Batch-07);发布先于审阅,结论回填后更新状态", + "en_chars": 15520, + "zh_chars": 8813 + } } \ No newline at end of file diff --git a/docs/consultant-review/batch-07-consultant-review-pack.md b/docs/consultant-review/batch-07-consultant-review-pack.md new file mode 100644 index 0000000..85531a9 --- /dev/null +++ b/docs/consultant-review/batch-07-consultant-review-pack.md @@ -0,0 +1,1426 @@ +# Batch-07 外部顾问审阅资料包 — 3 篇已上线但缺审核记录的工程文章 + +> **致顾问**:本轮不是新一轮选题策划,而是**补审**。这 3 篇已经在 blog.hoelee.com 上线(其中 2 篇在 Batch-06 资料包里已给过全文,1 篇是 2026-10-28 新增),但审阅结论回执一直没落到台账里。请您按同一套铁律与评分维度给出结论即可;如需撤回或重写,我会照办,线上可随时下线。 +> +> **审阅日期**:2026-10-28 | **资料包版本**:v1 | **文章语言**:英中双语(英文为线上主版本,中文为同 slug 镜像) + +--- + +## 一、本轮范围与状态 + +| # | slug | 分类 | 上线日 | 字数(EN / ZH) | 台账状态 | 本轮需要您给的结论 | +|---|---|---|---|---|---|---| +| **A** | `when-a-proxy-timeout-returns-html-instead-of-json` | engineering | 2026-10-28 | 15,520 / 8,813 | pending(本轮新增) | **全文审阅**(资料包第五章) | +| **B** | `when-lowering-crf-does-nothing-maxrate-deadlock` | devops | 2026-10-27 | 8,906 / 5,042 | pending | 全文在 Batch-06 包,本轮附录重贴,**请给最终结论** | +| **C** | `why-ffmpeg-map-0-breaks-your-mp4-and-mkv-remux` | engineering | 2026-10-27 | 10,877 / 7,332 | pending | 同上 | + +**为什么会出现「已上线却未审」**:Batch-06 那批 5 篇里,A/B/C 中的 B、C 按排期先发了(周二主打 + 周四深水区),审阅回执此前一直只看 Batch-01~05 的报告文件;A 是 2026-10-28 当天故障复盘当场写完并直接上线的(业主授权「按建议执行」),没有走 draft 预审闸门。这件事本身是我流程上的缺口,本轮补齐台账(3 条 `status: pending`)。 + +**未在本轮、但仍挂着的 Batch-06 剩余 3 篇**(都是 `draft: true`,未上线,等同一轮结论):`traefik-yaegi-plugin-package-name-trap`、`architecting-sea-payment-and-offline-licensing-pipelines`、`batch-generating-80-technical-covers-with-comfyui`。 + +--- + +## 二、战略任务与定位(与前几轮一致,未变) + +1. **双轨目标**:① 技术求职证明(Senior/Staff 全栈 · DevOps · AI 平台架构,US/EU/SG 远程或 hybrid);② 马来西亚中小企业 B2B 获客(网站设计与开发为主业,邮箱托管为次)。 +2. **No-Shopkeeper / No-Founder 红线**:商业交付一律写成高可用生产系统工程,不写成店铺经营、副业、电商小生意。 +3. **A 篇在这条线上的作用**:这是"能把别人的线上事故从一句错误的报错定位到正确层级"的证据。它不涉及任何自有产品,纯排障与架构判断,对求职线价值最高;对 B2B 线的作用是"我管理的基础设施出问题时,我是这样收尾的"。 + +--- + +## 三、铁律清单(自检结果,逐条给证据) + +| 铁律 | 自检方式 | 结果 | +|---|---|---| +| 禁用词 `homelab` / `home lab` / `家用` / `小主机` / `折腾` | 全文正则,EN+ZH 各跑一遍 | **0 命中** | +| 第一人称单数,禁 `we` / `our` / `我们` | 全文正则计数 | **0 命中**(EN 与 ZH 均为 0) | +| 技术正文禁第二人称(`you`/`your`/`你`),仅结尾 CTA 可用 | 全文正则 | 初稿有 **5 处**(`Your timeout budget`、`the worst case you have not measured`、`telling you / your code`,中文 2 处「你」),已全部改为第一人称/无人称,**当前 0 命中**,且已重新构建部署、线上实测生效 | +| 英中 100% 镜像(同 slug、同 commit) | 文件存在性 + 构建产物 | 两处 `dist/posts/`、`dist/posts/zh/` 均生成,`llms.txt` 各 2 条 | +| 标题 Title Case(EN) | 人工核对 | A/B/C 均符合 | +| OPSEC:无绝对路径、无内网 IP、无 token/密钥 | 人工逐段过 + 正则扫 `192.168`、`/volume1`、`D:\`、`token` | **0 命中**;文中出现的 `nginx` / `FastAPI` / `authentik` / `Docker` / `cgroups` 均为通用技术名词,非客户标识 | +| 数字一致(前后文不打架) | 逐条重算 | 13.6÷0.259 = **52.5×**(文中"中位 52×");13.6÷0.244~0.361 = **38~56×**(文中"38–57×");2507÷13.6 = **184.3×**(文中"184×");0.24–0.36 s 五连测中位 **0.259 s** | +| 长代码进 `
`,`` 写明产物与行数 | 人工核对 | A 篇两处:`52 行` 缓存实现、`22 行` 前端读取器;标题均写明产物 | +| 不手写 CTA(由 `dynamicCta: true` 注入) | frontmatter + 构建产物 | `dynamic-cta` 类在线上页面各 1 处,正文无手写表单 | + +--- + +## 四、技术背景(供您审"技术是不是真的") + +### 4.1 A 篇的触发:一句指向错误层级的报障 + +业主 2026-10-28 报障原文只有一行: + +``` +读取自动处理状态失败:SyntaxError: Unexpected token '<', "= vbps × SIZE_CEIL × 0.97`),此时调 CRF 数学上无效,调度器应直接取消重试并记录原因。 +- **C(`-map 0` 跨容器)**:`-map 0` 在 Matroska 输出上遇到 QuickTime 章节(`bin_data`)与手机时间码轨(`tmcd`)会直接 fatal;MP4 输出遇到 ASS/PGS/字体附件同样 fatal;iOS 的轨名只读 `udta/name` 而 ffprobe 只回读 `hdlr`,二次混流会静默丢标签。修法是显式轨道规划 + 包级 MD5 校验(`-f data -` 管道哈希 payload)。 + +--- + +## 五、我预判您会追问的两处(并附我的答案与证据边界) + +> 这一节是我主动交底的,不是让您放水。若您认为证据不足以支撑文中表述,我会改标题与结论,而不是改数字。 + +**Q-A:把"缓存读 0.26 s"和"全量审计 13.6 s"放在一起比,是不是苹果比橘子?** +- 文中表述为"对照它所替代的 13.6 秒审计",指的是**同一端点、同一份计算**在改造前后的对照,不是把它包装成"接口优化 52 倍"。 +- 我手上有:同函数三种条件下的耗时(13.6 / 17.7 / 98.5 / 35.8 s)、缓存读 5 连测(0.244 / 0.257 / 0.259 / 0.262 / 0.361 s)、后台自记的 8 次审计耗时。 +- **证据缺口(可补)**:我没有从访问日志里取该端点改造前后的 p50/p95 分布,因此文中不写"p95 下降多少"。若您要求,我可以补一次基于 access log 的分位统计再回填。 + +**Q-B:2507 s 里到底有多少是单核限流、多少是锁竞争/IO?** +- 该数字是**同一函数从进入到返回的墙钟时间**,由应用自己打印,不是我推算的;但它**没有拆段归因**(单核争用 / 磁盘 stat / 哈希 CPU 各占多少)。 +- 我能确定的因果:容器 1 核 + 1/4 权重、同宿主有 HEVC 转码任务、数据量 341 篇;我**没有**做过的:分段埋点、per-file IO 统计。 +- 因此文中只说"耗时是被允许借用多少 CPU 的函数",没有声称具体的归因比例。若您要求,我会加一次分段埋点实验再给比例。 + +--- + +## 六、全文双语 — A 篇(本轮待审主角) + +### 6.1 英文版(线上主版本) + +````markdown +--- +ogImage: "/og/when-a-proxy-timeout-returns-html-instead-of-json.jpg" +banner: "/banners/when-a-proxy-timeout-returns-html-instead-of-json.jpg" +title: "When a Proxy Timeout Returns HTML Instead of JSON: A 499 Post-Mortem" +description: "Why a self-hosted admin card failed with 'Unexpected token <': an O(n) library audit running inside a GET request, three reverse-proxy hops, and the 499 status no monitor watches — measured from 13.6 s on an idle box to 2,507 s on a busy one." +pubDate: 2026-10-28 +category: engineering +blueprint: self-hosted-infrastructure +blueprintLayer: L2-edge-proxy-runtime +tags: ["nginx", "fastapi", "reverse-proxy", "timeouts", "caching", "observability", "stale-while-revalidate"] +draft: false +summary: + audience: "Full-Stack Engineers, SREs & Platform Architects Who Own Their Own Edge" + painPoint: "An admin dashboard card died with a JavaScript syntax error, which pointed every investigation at the frontend — while the real failure was an O(n) audit burning one pinned CPU core behind three proxy hops that silently abort at their own timeouts." + impact: + - metric: "Endpoint Latency −98%" + detail: "Moved a 289-to-341-note library audit off the request thread into a single-flight background refresher; the cached read path now answers in 0.24–0.36 s over five consecutive calls instead of 13.6 s idle and 2,507 s under load." + - metric: "Failure Now Names Its Layer" + detail: "Replaced a blind res.json() with a text-first reader that preserves the HTTP status and the first 80 bytes of an HTML error page, so a proxy abort reports 'HTTP 503 · +🔍 View the stale-while-revalidate implementation — cache, single-flight refresh thread, startup warm (52 lines) + +```python +_AUTOMATION_TTL_SEC = float(os.environ.get("AUTOMATION_COUNTS_TTL_SEC", "300") or 300) +_automation_cache: Dict[str, Any] = {"data": None, "at": 0.0} +_automation_refreshing_flag = [False] +_automation_lock = threading.RLock() + + +@app.get("/api/automation", tags=["ops"]) +def automation_get(refresh: bool = False): + """Panels must never block on an audit.""" + flags = autoflow.settings(db=db) + now = time.time() + cached = dict(_automation_cache) + fresh = cached["data"] is not None and (now - cached["at"]) < _AUTOMATION_TTL_SEC + if refresh or not fresh: + _start_automation_refresh() + return { + "flags": flags, + "counts": (cached["data"] or {}).get("counts") + or {"notes": 0, "missing_cover": 0, "tts_stale": 0}, + "refreshing": _automation_refreshing() or not fresh, + "cached_at": (datetime.fromtimestamp(cached["at"]).isoformat(timespec="seconds") + if cached["at"] else None), + } + + +def _automation_counts() -> Dict[str, Any]: + """The slow half: cover gap + stale TTS over the whole library.""" + notes = [p for p in sorted(CONTENT_DIR.glob("*.md")) if not p.stem.endswith(".en")] + missing_cover = [...] + stale = tts_mod.stale_targets(content_dir=CONTENT_DIR, audio_root=AUDIO_DIR) + return {"counts": {"notes": len(notes), "missing_cover": len(missing_cover), + "tts_stale": len(stale)}, + "missing_cover": missing_cover[:20]} + + +def _start_automation_refresh() -> None: + with _automation_lock: + if _automation_refreshing_flag[0]: + return + _automation_refreshing_flag[0] = True + + def _run() -> None: + started = time.time() + try: + data = _automation_counts() + _automation_cache.update({"data": data, "at": time.time()}) + except Exception as exc: # a broken audit must not 500 the card + logging.warning("automation counts failed: %s", exc) + finally: + _automation_refreshing_flag[0] = False + logging.info("automation counts refreshed in %.1fs", time.time() - started) + + threading.Thread(target=_run, name="automation-counts", daemon=True).start() +``` + +
+ +**2. A stale value must announce itself.** The response carries `refreshing` and `cached_at`, and +the card renders `计算中…` next to the numbers while a recompute is in flight, then retries in three +seconds. The alternative — showing `0` until data arrives — is worse than being slow, because a +zero on a backlog counter is indistinguishable from "nothing to do". + +**3. The client never parses blindly.** `res.json()` was the actual defect on the browser side: it +inferred "malformed JSON" from "HTML error page" and discarded both the status code and the body +that would have named the failing hop. + +
+🔍 View the text-first response reader and the retry contract (22 lines) + +```javascript +function autoReadJson(res) { + // A proxy timeout / 502 / 503 returns an HTML error page. Blind res.json() turns that into + // "Unexpected token '<'" and hides which layer failed — keep the status and the head instead. + return res.text().then(function (text) { + try { + return { data: JSON.parse(text) }; + } catch (e) { + var head = String(text || '').trim().slice(0, 80).replace(/\s+/g, ' '); + return { error: 'HTTP ' + res.status + ' · ' + (head || 'empty response') }; + } + }); +} + +// refreshing: show the last known numbers, label them, come back in three seconds. +document.getElementById('auto-counts').textContent = + '笔记 ' + counts.notes + ' · 缺封面 ' + counts.missing_cover + + ' · 待朗读 ' + counts.tts_stale + (data.refreshing ? ' · 计算中…' : ''); +if (data.refreshing) { autoRetryTimer = setTimeout(window.autoReload, 3000); } +``` + +
+ +A fourth change is smaller but load-bearing: `?refresh=1` forces a recompute. Tests and humans both +need a way to demand fresh numbers without waiting out a TTL, and without that affordance the test +suite would have had to sleep for minutes. + +The test contract changed with the endpoint, which is the part most people skip: the suite now +polls until the background thread lands instead of asserting numbers on the first response. 28 tests +pass; the DOM contract check still confirms 16 cards, 68 buttons and 124 element ids, so the frontend +rewrite did not quietly drop a control. + +--- + +## Reflection: five rules for panel endpoints + +1. **A dashboard endpoint is not a query interface.** It is the most frequently called and least + cached surface in an admin UI. Anything O(n) over user data belongs in a background job with a + publish step, not in the GET that a browser polls on focus. +2. **The timeout budget is the smallest hop, not the sum.** Four proxies each "allowing" 120 s do + not add up; the request dies at whichever clock expires first, and the application cannot see any + of them. The application-side budget has to be an order of magnitude under the smallest one. +3. **`499` deserves an alert.** It is the only signal that reports "the caller gave up", and it + bypasses every 5xx-based monitor. A ratio of 499s to 200s on a single path, scraped from the + access log, detects this entire class of failure for almost no cost. +4. **Never trust `res.json()` on a proxied route.** Read text, keep the status, keep the first line. + The error message is the only artefact the next engineer will have. +5. **Stale-while-revalidate beats a bigger number.** Raising a timeout moves the wall; decoupling + compute from read removes it — and it removes it for the worst case I have not measured yet + (2,507 s here), not just the one I did (13.6 s). + +--- + +## The result + +- **Cached read path: 200 in 0.24–0.36 s** across five consecutive calls measured inside the + container — a median of 0.26 s against the 13.6 s audit it replaced, **52× faster at the median + and 38–57× across the spread**. +- **Worst observed case eliminated as a user-facing risk.** The audit still takes 2,507 s when the + box is saturated; it now costs a background thread instead of a broken card, and the panel keeps + answering throughout. +- **Correctness preserved, not masked.** Counts at the time of writing: 341 notes, 1 missing cover, + 47 notes pending audio — the same computation, moved. +- **The failure can no longer masquerade.** If any hop aborts again, the card prints + `HTTP 503 · …` and the investigation starts at the right layer. + +The single most useful line in this whole exercise was the one that lied: `Unexpected token '<'`. +An HTML error page handed to a JSON caller is not a syntax error — it is a proxy telling me, in the +only language it has, that it stopped waiting before my code ever woke up. +```` + +### 6.2 中文版(同 slug 镜像,`posts/zh/`) + +````markdown +--- +ogImage: "/og/zh-when-a-proxy-timeout-returns-html-instead-of-json.jpg" +banner: "/banners/when-a-proxy-timeout-returns-html-instead-of-json.jpg" +title: "反代超时返回 HTML,前端报 SyntaxError:一次 499 事故复盘" +description: "后台卡片报「Unexpected token '<'」,所有线索都指向前端,真正的故障却是一个跑在 GET 请求线程里的全库审计:289 到 341 篇笔记逐一切章、算哈希、stat 音频,穿过三层反代,实测从空闲时的 13.6 秒恶化到满载时的 2507 秒。" +pubDate: 2026-10-28 +category: engineering +blueprint: self-hosted-infrastructure +blueprintLayer: L2-edge-proxy-runtime +tags: ["nginx", "fastapi", "反向代理", "超时", "缓存", "可观测性", "stale-while-revalidate"] +draft: false +summary: + audience: "自建边缘层的全栈工程师、SRE 与平台架构师" + painPoint: "一张后台卡片被一句 JavaScript 语法错误判了死刑,排查方向全被带到前端;真实死因是一个 O(n) 全库审计占住单核、被三层反代各自的超时闸门无声掐断。" + impact: + - metric: "端点延迟 −98%" + detail: "把 289 至 341 篇笔记的全库审计挪出请求线程,改由单飞后台线程刷新;缓存读路径连续五次实测 0.24–0.36 秒,替代空闲时 13.6 秒、满载时 2507 秒的同步审计。" + - metric: "故障自动指明层级" + detail: "用「先读文本、保留状态码与首 80 字节」的读取器替代裸 res.json(),反代中断时面板直接打印「HTTP 503 · + +线上面板报上来只有一行字: + +``` +读取自动处理状态失败:SyntaxError: Unexpected token '<', " + +出问题的路由是发布控制台的一张状态卡片: + +``` +GET /api/automation +``` + +它返回三个开关和三个计数。两个计数很便宜,第三个叫 `tts_stale`,为了算它要跑一遍全库审计:枚举所有 markdown 笔记、逐篇切章节、对每章内容算哈希、再对每个目标音频文件 `stat` 一次。而这一切**就跑在请求线程里**,卡片每轮询一次就重算一次。 + +```python +# 出问题的写法:一个 GET,顺手把整个库量一遍 +@app.get("/api/automation") +def automation_get(): + notes = [p for p in sorted(CONTENT_DIR.glob("*.md")) if not p.stem.endswith(".en")] + missing_cover = [...] + stale = tts_mod.stale_targets(content_dir=CONTENT_DIR, audio_root=AUDIO_DIR) # O(n) 审计 + return {"counts": {"notes": len(notes), "tts_stale": len(stale)}, ...} +``` + +289 篇笔记时这是一次 13.6 秒的请求。光这个数字就已经足够取消它在轮询卡片上的资格,但让它从「慢」变成「坏」的是另一半原因:这个请求要穿过三层反向代理才摸到这个函数——一层 SSO 门禁、一层 portal nginx、再进容器网络——每一层都有自己的超时,而应用既设不了、也看不见。 + +portal 的 `/api/` 位置在现行配置里是 `proxy_read_timeout 120s`,而它前面的 SSO 门禁放弃得更早。任何一层的计时器一到,它就按 nginx 的本分处理:停止等待、断开上游连接、把**自己的 HTML 错误页**交给浏览器。前端接着对这份文档执行 `await res.json()`,于是就有了那行 `SyntaxError`。 + +整条链上没有任何一环返回 500。应用从头到尾都不知道这个请求存在过。 + +--- + +## 三条证据钉死「其实什么都没坏」 + +三个产物合起来就能定位故障,不需要猜。单独看每一条都是歧义的,其中一条还会主动误导。 + +| 证据 | 观测值 | 排除了什么 | +|---|---|---| +| portal 访问日志 | 该路径 7 次请求,**全是 `499`、响应体 0 字节**,无 4xx、无 5xx | 排除应用报错与鉴权拒绝 | +| 应用日志(uvicorn) | 故障窗口内该路径**一条记录都没有** | 证明请求根本没到应用,是代理先放弃的 | +| 容器内直连打点 | 同一调用直连应用:连续三次 **17.7 秒 / 98.5 秒 / 35.8 秒** | 确认慢是真的、且慢在审计内部,不是网络抖动 | + +`499` 是 nginx 在**客户端**先断开连接时写下的状态码。它不是服务端承认的错误,不是 5xx,FastAPI 的访问日志也永远看不到它——所以这张卡片可以坏上几个星期,而所有存活探测、错误率看板、「服务健康吗」的问答统统是绿的。真正描述这次故障的状态码,由放弃等待的那一层写下,指向的是一个应用从未收到的请求。 + +而最响的那条证据恰好在误导:`SyntaxError`。它对文档的描述是准确的,对原因的判断是错的。 + +--- + +## 隐藏变量:空闲时 13.6 秒,满载时 2507 秒 + +修之前,审计的耗时看起来像个常数。它不是。控制台容器被钉在单核(`cpuset "4"`),CPU 权重只有默认值的四分之一(`cpu_shares 256`),而宿主机上还跑着 HEVC 转码闸门和内容流水线。这场审计要跟别的任务抢那一个核。 + +把审计挪出请求路径之后,应用开始自己记录耗时,假设才变成测量: + +``` +automation counts refreshed in 76.1s +automation counts refreshed in 82.3s +automation counts refreshed in 93.5s +automation counts refreshed in 124.2s +automation counts refreshed in 165.1s +automation counts refreshed in 633.6s +automation counts refreshed in 1282.5s +automation counts refreshed in 2507.2s +``` + +同一份代码、同一个 341 篇的库,一个数量级的跨度,最坏值比空闲机上量到的数字**慢 184 倍**。这一条就足以否定那个「朴素修法」:把反代超时从 120 秒抬到 300 秒,解决不了一个 2507 秒的计算,只是把 120 秒失败改成 300 秒失败,晚五分钟、多占五分钟容量。 + +这就是我在边缘层反复吃到的教训:**端点延迟不是代码的属性,而是它被允许借用多少 CPU 的属性。** 单核 cgroup 限流会把一次 O(n) 文件审计变成耗时由邻居决定的无界操作。 + +--- + +## 修法:把读路径和计算路径彻底拆开 + +三处契约改动,每一处都小到可以独立验证。 + +**一、读路径绝不计算。** 计数放进 TTL 缓存(300 秒,`AUTOMATION_COUNTS_TTL_SEC` 可调)。请求要么拿到新鲜缓存,要么立刻拿到上一次的值并带 `refreshing: true`,同时由一个后台 daemon 线程重算。单飞用一把锁加一个标记守住,成串的轮询不会变成成串的审计。缓存还会在应用 lifespan 里预热一次,第一个打开面板的人不用替所有人付这笔账。 + +
+🔍 查看 stale-while-revalidate 实现 —— 缓存、单飞刷新线程与启动预热(52 行) + +```python +_AUTOMATION_TTL_SEC = float(os.environ.get("AUTOMATION_COUNTS_TTL_SEC", "300") or 300) +_automation_cache: Dict[str, Any] = {"data": None, "at": 0.0} +_automation_refreshing_flag = [False] +_automation_lock = threading.RLock() + + +@app.get("/api/automation", tags=["ops"]) +def automation_get(refresh: bool = False): + """面板绝不在审计上阻塞。""" + flags = autoflow.settings(db=db) + now = time.time() + cached = dict(_automation_cache) + fresh = cached["data"] is not None and (now - cached["at"]) < _AUTOMATION_TTL_SEC + if refresh or not fresh: + _start_automation_refresh() + return { + "flags": flags, + "counts": (cached["data"] or {}).get("counts") + or {"notes": 0, "missing_cover": 0, "tts_stale": 0}, + "refreshing": _automation_refreshing() or not fresh, + "cached_at": (datetime.fromtimestamp(cached["at"]).isoformat(timespec="seconds") + if cached["at"] else None), + } + + +def _automation_counts() -> Dict[str, Any]: + """慢的那一半:全库缺封面 + 过期朗读目标。""" + notes = [p for p in sorted(CONTENT_DIR.glob("*.md")) if not p.stem.endswith(".en")] + missing_cover = [...] + stale = tts_mod.stale_targets(content_dir=CONTENT_DIR, audio_root=AUDIO_DIR) + return {"counts": {"notes": len(notes), "missing_cover": len(missing_cover), + "tts_stale": len(stale)}, + "missing_cover": missing_cover[:20]} + + +def _start_automation_refresh() -> None: + with _automation_lock: + if _automation_refreshing_flag[0]: + return + _automation_refreshing_flag[0] = True + + def _run() -> None: + started = time.time() + try: + data = _automation_counts() + _automation_cache.update({"data": data, "at": time.time()}) + except Exception as exc: # 审计坏了也不能把卡片打成 500 + logging.warning("automation counts failed: %s", exc) + finally: + _automation_refreshing_flag[0] = False + logging.info("automation counts refreshed in %.1fs", time.time() - started) + + threading.Thread(target=_run, name="automation-counts", daemon=True).start() +``` + +
+ +**二、陈旧值必须自己说出来。** 响应里带 `refreshing` 与 `cached_at`,卡片在重算期间把「计算中…」挂在数字后面,3 秒后自动补取一次。反面做法——在数据到达前先显示 `0`——比慢更糟,因为积压计数器上的 0 与「确实没事可做」长得一模一样。 + +**三、客户端绝不盲解析。** 真正躺在浏览器侧的缺陷是 `res.json()`:它把「收到了 HTML 错误页」推断成「JSON 格式错误」,顺手丢掉了状态码和本该指出故障层级的那段正文。 + +
+🔍 查看「先读文本」的响应读取器与重试契约(22 行) + +```javascript +function autoReadJson(res) { + // 反代超时 / 502 / 503 回的是 HTML 错误页;裸 res.json() 会把它变成 + // "Unexpected token '<'",并把「哪一层挂了」这条信息彻底吞掉。 + return res.text().then(function (text) { + try { + return { data: JSON.parse(text) }; + } catch (e) { + var head = String(text || '').trim().slice(0, 80).replace(/\s+/g, ' '); + return { error: 'HTTP ' + res.status + ' · ' + (head || '空响应') }; + } + }); +} + +// refreshing 时:先显示上一次的数字并标注状态,3 秒后再来一次 +document.getElementById('auto-counts').textContent = + '笔记 ' + counts.notes + ' · 缺封面 ' + counts.missing_cover + + ' · 待朗读 ' + counts.tts_stale + (data.refreshing ? ' · 计算中…' : ''); +if (data.refreshing) { autoRetryTimer = setTimeout(window.autoReload, 3000); } +``` + +
+ +第四处改动更小但很关键:`?refresh=1` 强制重算。测试和人一样,都需要一条「立刻要新数字」的通道,否则整套测试只能靠 sleep 等 TTL 过期。 + +测试契约跟着端点一起改了,这是大多数人会跳过的一步:用例现在轮询到后台线程落地为止,而不是对第一次响应就断言数字。28 个测试通过;DOM 契约检查仍然确认 16 张卡片、68 个按钮、124 个元素 id,说明前端那轮改写没有悄悄丢掉控件。 + +--- + +## 复盘:我此后对每个面板端点都套用的五条规矩 + +1. **看板端点不是查询接口。** 它是后台里调用最频繁、缓存最少的表面,任何对用户数据的 O(n) 计算都属于后台任务加一次发布,不属于浏览器聚焦时轮询的那个 GET。 +2. **超时预算等于最小那一跳,不是各跳之和。** 四层反代各自「允许」120 秒并不叠加:请求死在最先到期的那个时钟上,而应用一个都看不见。应用侧的预算必须比最小的那个还低一个数量级。 +3. **`499` 值得配一条告警。** 它是唯一一个表达「调用方放弃了」的信号,且天然绕开所有基于 5xx 的监控。从访问日志抓单条路径上 499 与 200 的比值,几乎零成本覆盖这一整类故障。 +4. **在反代后面永远不要信 `res.json()`。** 先读文本,留下状态码,留下第一行。下一个接手的人手里只有这条错误信息。 +5. **stale-while-revalidate 优于一个更大的数字。** 抬高超时只是把墙往后推;把计算与读拆开才是把墙拆掉——而且拆掉的是我还没量到的那个最坏值(这里的 2507 秒),不只是量到的那个(13.6 秒)。 + +--- + +## 交付结果 + +- **缓存读路径:连续五次容器内实测 200,0.24–0.36 秒**,中位 0.26 秒,对照它所替代的 13.6 秒审计——**中位快 52 倍,整个区间快 38–57 倍**。 +- **最坏情况不再暴露给使用者。** 宿主机饱和时审计仍要 2507 秒,但它现在只花掉一个后台线程,而不是一张坏掉的卡片,面板全程照常应答。 +- **正确性保留而非掩盖。** 撰写时的计数:341 篇笔记、1 个缺封面、47 篇待生成朗读——同一个计算,只是搬了位置。 +- **故障再也无法伪装。** 任何一跳再次中断,卡片会打印 `HTTP 503 · …`,排查从正确的层级开始。 + +整件事里最有用的一行,恰恰是那行说谎的 `Unexpected token '<'`。把 HTML 错误页递给一个 JSON 调用方,不是语法错误——那是反代用它唯一会说的语言在告诉我:我的代码还没醒,它就已经不等了。 +```` + +--- + +## 附录一:B 篇全文 `when-lowering-crf-does-nothing-maxrate-deadlock` + +### EN + +````markdown +--- +ogImage: "/og/when-lowering-crf-does-nothing-maxrate-deadlock.jpg" +banner: "/banners/when-lowering-crf-does-nothing-maxrate-deadlock.jpg" +title: "When Lowering CRF Does Nothing: The -maxrate Ceiling and the SSIM Quality Deadlock" +description: "Why dropping CRF by five steps improved SSIM by only 0.00003: discovering the mathematical conflict between -maxrate bitrate ceilings and quality gates in high-throughput video re-encoding." +pubDate: 2026-10-27 +category: devops +blueprint: self-hosted-infrastructure +blueprintLayer: L1-compute-storage-network +tags: ["ffmpeg", "transcoding", "hevc", "ssim", "video-engineering", "quality-gate"] +draft: false +summary: + audience: "Video Infrastructure Engineers, SREs & Platform Architects" + painPoint: "A high-throughput video space-reclamation pipeline repeatedly burned 106 hours across 487 encoding rounds because quality-gate retries (crf -2) produced zero metric improvement on capped bitrates." + impact: + - metric: "Exposed Deadlock" + detail: "Proved that crf 19 to crf 14 under an identical -maxrate ceiling yielded an SSIM shift of only +0.00003, rendering lower CRF retries mathematically inert." + - metric: "Two-Tier Quality Gate" + detail: "Introduced a format-aware gate accepting already-compatible streams (H.264+AAC) at 0.990 SSIM while reserving 0.995 for format-remediation encodes." + - metric: "Eliminated Wasted Compute" + detail: "Bypassed 100% of pointless re-encode cycles on capped files, cutting retry overhead from 106+ hours to zero on boundary video files." + stack: ["FFmpeg", "libx265", "SSIM", "Python", "Linux Dedicated Nodes"] + jumpLinks: + business: "#the-batch-that-burned-106-hours-doing-nothing" + architecture: "#the-architectural-solution-two-tier-gates-and-cap-awareness" + result: "#the-result" +dynamicCta: true +--- + +## The batch that burned 106 hours doing nothing + +In a high-throughput transcoding pipeline running across dedicated on-premise infrastructure, +one of my automated ingestion queues had a strict rule: **the output must never exceed the size +of the source** (`SIZE_CEIL = 1.0`). + +The goal was automated space reclamation across tens of thousands of archived video files. If an +HEVC transcode came out larger than the H.264 original, the pipeline rejected it as counterproductive. +To enforce this, every encode passed `-maxrate ` and `-bufsize ` +to FFmpeg. + +At the same time, the pipeline guarded visual fidelity with an automated quality gate: +every transcoded file had its structural similarity (SSIM) measured against the source frame by frame. +If the mean SSIM fell below `0.995`, the item failed the quality gate. To recover borderline files, +the supervisor triggered an automatic retry: `quality+2` (dropping CRF from 29 to 27 or 19 to 17). + +Then the production telemetry came in. + +Over a two-week run, **487 quality rounds failed**, consuming **106.2 hours of dedicated compute**. +Worse: of 202 files that went through two consecutive rounds, **194 showed an SSIM movement of +less than 0.0015**. The second round burned full CPU cycles to produce a file almost byte-for-byte +identical to the one rejected ten minutes earlier. + +--- + +## The anomaly in the lab + +To isolate why the quality ladder was failing to move the needle, I isolated a representative test +video: a 720p@30 clip (`1280x720`, 96 seconds) with an original video bitrate of 1832 kbps. + +I ran three controlled encoding passes using `libx265`: + +```text +Run 1 (Baseline): + Command: -crf 19 -maxrate 1832k -bufsize 4580k + Size: 20.6 MB (87.5% of source) + SSIM Mean: 0.99192 --> REJECTED (below 0.995) + +Run 2 (The Aggressive Quality Retry): + Command: -crf 14 -maxrate 1832k -bufsize 4580k (5 quality steps lower!) + Size: 22.9 MB (97.3% of source) + SSIM Mean: 0.99195 --> REJECTED (below 0.995) + Delta: +0.00003 SSIM improvement + +Run 3 (Uncapped Ceiling): + Command: -crf 19 (no -maxrate, no -bufsize) + Size: 40.6 MB (172.5% of source) + SSIM Mean: 0.99648 --> PASS (above 0.995) +``` + +Look at the difference between Run 1 and Run 2: **dropping CRF by five full steps moved the mean +SSIM by exactly 0.00003**. + +Yet in Run 3, the exact same CRF 19 sailed past the 0.995 bar to 0.99648—at the cost of expanding +the file to **172%** of its original size. + +--- + +## The mathematical trap between CRF and -maxrate + +The culprit was not the video encoder, the preset, or the content complexity. It was a fundamental +contradiction in the pipeline's control architecture: + +1. **CRF is a quality target**, not a rate controller. It calculates the quantization parameter needed to maintain uniform visual fidelity across frames. +2. **`-maxrate` is a hard bitrate ceiling enforced by the VBV (Video Buffer Verifier)** buffer. +3. When complex scenes demand more bits to meet a lower CRF, the VBV governor intervenes and drops quality to honor the `-maxrate` ceiling. + +In high-motion, high-entropy scenes, reaching an SSIM mean of 0.995 required **at least 125% to +170%** of the source's native bitrate. But the `SIZE_CEIL = 1.0` contract I enforced forbade giving +the encoder those bits. + +Consequently, when an encode ran close to the ceiling (spending >= 97% of the allowable bits), +lowering the CRF did not buy more detail—it simply forced the encoder to hit the ceiling earlier in +the GOP and throw the extra bits away. The quality ladder stopped being a ladder. + +--- + +## The architectural solution: Two-tier gates and cap awareness + +To eliminate the 106 hours of wasted compute, I refactored the supervisor's decision engine in three ways: + +### 1. Identifying the true business objective (`space_reclaim_source`) +Why was a file being transcoded in the first place? +* **Case A: Format Remediation.** If the source was VP9, AV1, or inside an MKV container with AC-3 audio, it could not play natively in mobile or web browsers without server-side transcoding. Here, fidelity is paramount: I enforce the strict `0.995` threshold. +* **Case B: Pure Space Reclamation.** If the source was already an H.264 baseline MP4 with AAC audio, it already played everywhere natively. The *only* purpose of re-encoding was saving disk space. Holding a compatible file to a strict 0.995 bar that forces bitrate expansion is counterproductive. + +For Case B, the pipeline now accepts a secondary tier: `SSIM_MEAN_MIN_FALLBACK = 0.990`. + +### 2. Detecting the ceiling wall (`CAP_BOUND_SHARE`) +If an encode used >= 97% of the allowable source bitrate (`obps >= vbps * SIZE_CEIL * 0.97`), +the file was capped. Retrying with a lower CRF would mathematically yield zero benefit. The pipeline +now detects this condition and cancels the retry immediately. + +
+View pipeline quality-gate logic (Python excerpt) + +```python +SIZE_CEIL = 1.0 # Output video bitrate must not exceed the source's +CAP_BOUND_SHARE = 0.97 # Spent >= 97% of ceiling -> rate-limited, not gear-limited +QUALITY_STEPS = 2 # Retry offset (crf - 2) + +def space_reclaim_source(info, src): + """True if the video and audio streams already decode natively on mobile/web targets.""" + if info.get("vcodec") not in ("h264", "avc1"): + return False + if info.get("acodec") not in (None, "aac", "mp4a"): + return False + return os.path.splitext(src)[1].lower() in (".mp4", ".m4v", ".mov") + +def evaluate_quality_gate(info, src, mean_ssim, bad_share, vbps, obps): + capped = bool(vbps) and obps >= (vbps * SIZE_CEIL * CAP_BOUND_SHARE) + systematic = bad_share > MAX_BAD_SHARE or (mean_ssim < SSIM_TARGET) + + # Second-tier relaxation for files that already play everywhere + if systematic and bad_share <= MAX_BAD_SHARE and mean_ssim >= SSIM_MEAN_MIN_FALLBACK: + if space_reclaim_source(info, src): + return "PASS_SECOND_TIER", "Accepted at secondary 0.990 threshold for space reclamation" + + if systematic: + # If output is already hitting the bitrate ceiling, retry is mathematically inert + if capped: + return "FAIL_CAPPED", f"SSIM {mean_ssim:.4f} below bar, but output is capped ({obps/vbps*100:.1f}%) - retry skipped" + return "RETRY", f"Retry with crf - {QUALITY_STEPS}" + + return "PASS", "Clean pass" +``` + +
+ +--- + +## The result + +After deploying this change to the production nodes: +* The test file that previously bounced between `crf 19` and `crf 14` with a `FAIL quality 0.99192` now cleared on its first pass as `PASS_SECOND_TIER` (87.5% of original size, zero retries, 120s encode time). +* The 106-hour wasted compute overhead on boundary files dropped to **zero**. +* Pipeline throughput increased by **31%** across the archival queue because the supervisor stopped running provably inert second passes. + +When building automated transcoding pipelines, never evaluate quality metrics in isolation from your rate-control constraints. When CRF meets `-maxrate`, the ceiling always wins. +```` + +### ZH + +````markdown +--- +ogImage: "/og/zh-when-lowering-crf-does-nothing-maxrate-deadlock.jpg" +banner: "/banners/when-lowering-crf-does-nothing-maxrate-deadlock.jpg" +title: "当降低 CRF 毫无效果:视频转码中 -maxrate 封顶与 SSIM 质量门禁的死锁" +description: "为什么将 CRF 下调 5 档仅让 SSIM 微升 0.00003:深度拆解高吞吐视频转码管线中 -maxrate 码率天花板与客观质量门禁之间的数学死锁与两档放行解法。" +pubDate: 2026-10-27 +category: devops +blueprint: self-hosted-infrastructure +blueprintLayer: L1-compute-storage-network +tags: ["ffmpeg", "transcoding", "hevc", "ssim", "video-engineering", "quality-gate"] +draft: false +summary: + audience: "视频基础设施工程师、SRE 与平台架构师" + painPoint: "私有高吞吐转码管线在 487 轮转码中空转了 106 小时算力,原因是质量门禁的自动重试(CRF -2)在硬性码率上限拦截下完全失效。" + impact: + - metric: "揭露死锁根因" + detail: "实测证明在相同 -maxrate 上限下,CRF 从 19 降至 14 仅让 SSIM 提升 0.00003,重试在数学上完全惰性。" + - metric: "二档差异化门禁" + detail: "引入业务目标分流:全平台可播的原生 H.264+AAC 源按 0.990 空间回收档放行,非兼容格式保持 0.995 严格修复档。" + - metric: "消除无效算力损耗" + detail: "彻底旁路封顶文件的无效重试,将临界视频的重试开销从 106 小时直接清零。" + stack: ["FFmpeg", "libx265", "SSIM", "Python", "Linux 生产节点"] + jumpLinks: + business: "#消耗了-106-小时却毫无进展的转码批次" + architecture: "#架构重构二档门禁与封顶自适应旁路" + result: "#交付结果" +dynamicCta: true +--- + +## 消耗了 106 小时却毫无进展的转码批次 + +在我所维护的私有高吞吐媒体转码基础设施中,自动化入库队列设定了一条铁律:**转码输出文件体积绝对不可超过原文件**(`SIZE_CEIL = 1.0`)。 + +这套管线的核心业务目标是自动化磁盘空间瘦身。如果一次 HEVC(H.265)转码输出的文件比原来的 H.264 原档还要大,系统会直接将其判定为负收益并废弃。为了在编码期严格约束体积,调度器在调用 FFmpeg 时强制注入了 `-maxrate <原视频码率>` 与 `-bufsize <原视频码率 * 2.5>`。 + +与此同时,管线引入了端到端客观画质门禁:每一个转码产物都必须与原视频逐帧比对结构相似性(SSIM)。如果整片 SSIM 均值低于 `0.995`,该文件就会触发质量拦截。为了抢救临界画质文件,系统设计了自动化重试梯队:`quality+2`(将 CRF 调低 2 档重新编码)。 + +然而随后的生产监控日志揭露了一个反常现象。 + +在连续两周的运行中,**系统记录了 487 次质量门禁拦截**,白白消耗了 **106.2 小时的高性能算力**。更严重的是:在经历连续两次编码的 202 个文件中,**有 194 个文件的 SSIM 变动幅度低于 0.0015**。第二次重试在消耗了完全相同的 CPU 周期后,产出了一个与 10 分钟前被拒收的文件近乎完全相同的结果。 + +--- + +## 实验室受控排查与数据断点 + +为了彻底查清为什么质量梯队无法拉动指标,我从队列中抽取了一个典型测试样本:一段 720p@30 规格的视频(`1280x720`,时长 96 秒,原视频码率为 1832 kbps)。 + +在相同的硬件与 `libx265` 环境下,我进行了三组对照实验: + +```text +第 1 组(基线配置): + 参数: -crf 19 -maxrate 1832k -bufsize 4580k + 体积: 20.6 MB(原片 87.5%) + SSIM 均值:0.99192 --> 拦截拒收(低于 0.995) + +第 2 组(激进画质重试): + 参数: -crf 14 -maxrate 1832k -bufsize 4580k (整整调优 5 档!) + 体积: 22.9 MB(原片 97.3%) + SSIM 均值:0.99195 --> 拦截拒收(低于 0.995) + 指标增量: +0.00003 SSIM 提升 + +第 3 组(解除码率天花板): + 参数: -crf 19(不设置 -maxrate,不设置 -bufsize) + 体积: 40.6 MB(原片 172.5%) + SSIM 均值:0.99648 --> 顺利通过(高于 0.995) +``` + +仔细对比第 1 组与第 2 组:**CRF 整整下调了 5 档,但测得的 SSIM 均值仅仅前进了 0.00003**。 + +而在第 3 组中,完全相同的 CRF 19 参数毫无压力地达到了 0.99648——代价是生成的文件膨胀到了原文件的 **172%**。 + +--- + +## CRF 与 -maxrate 之间的数学死锁 + +导致这 106 个小时算力空转的根因不是编码器 Bug,也不是视频场景过于复杂,而是管线控制架构上的内在逻辑冲突: + +1. **CRF 是画质目标控制器**,而非码率分配器。它根据帧内复杂度动态计算量化步长,试图维持恒定的视觉保真度。 +2. **`-maxrate` 是硬性码率天花板**,由 VBV(Video Buffer Verifier)缓冲队列物理强制执行。 +3. 当复杂的运动场景需要更多比特来达成更低的 CRF 时,VBV 机制强制介入削减码率,以确保不突破 `-maxrate` 上限。 + +在纹理密集的高熵场景中,想要达到 0.995 的极端高保真度,物理上需要消耗原片 **125% 到 170%** 的码率。但我设定的 `SIZE_CEIL = 1.0` 契约明令禁止给编码器分配这些比特。 + +因此,当一次编码输出的码率已经逼近上限(消耗了超过 97% 的天花板容量)时,继续调低 CRF 根本无法换来画质细节——它只会让编码器在 GOP 的开头更快撞墙,然后把多余的比特全部丢弃。所谓的质量梯阶在上限面前彻底失去了调节能力。 + +--- + +## 架构重构:二档门禁与封顶自适应旁路 + +为了根除算力浪费,我对调度器的裁决引擎进行了三重重构: + +### 1. 拆解真实业务目标(`space_reclaim_source`) +一个视频为什么要被送入转码队列? +* **场景 A:格式兼容性修复**。如果原片是 VP9、AV1 编码,或是封装在 MKV 容器中带 AC-3 音轨,它无法直接在移动端或浏览器内硬解播放。此时兼容性是第一要务:我坚持死守 `0.995` 门禁。 +* **场景 B:纯粹的磁盘瘦身**。如果原片本身已经是标准的 H.264+AAC MP4 封装,在所有设备上都能原生硬解,转码的唯一目标就是省空间。如果对本来就能播的文件死守 0.995,最终只会逼迫码率膨胀,本末倒置。 + +针对场景 B,管线引入了二档放行标准:`SSIM_MEAN_MIN_FALLBACK = 0.990`。 + +### 2. 封顶阻断判定(`CAP_BOUND_SHARE`) +如果编码输出已经消耗了源视频码率的 97%(`obps >= vbps * SIZE_CEIL * 0.97`),说明该视频已经撞死在码率墙上。此时调低 CRF 重试在数学上注定无效。调度器检测到此状态后,直接取消重试并记录原因。 + +
+点击查看质量门禁自适应裁决核心代码(Python 关键片段) + +```python +SIZE_CEIL = 1.0 # 输出码率严禁超过源视频 +CAP_BOUND_SHARE = 0.97 # 达到上限 97% 表明已触顶,调节阶梯失效 +QUALITY_STEPS = 2 # 重试步长 (crf - 2) + +def space_reclaim_source(info, src): + """判定原视频图像与音频层是否均已具备原生硬件解码能力""" + if info.get("vcodec") not in ("h264", "avc1"): + return False + if info.get("acodec") not in (None, "aac", "mp4a"): + return False + return os.path.splitext(src)[1].lower() in (".mp4", ".m4v", ".mov") + +def evaluate_quality_gate(info, src, mean_ssim, bad_share, vbps, obps): + capped = bool(vbps) and obps >= (vbps * SIZE_CEIL * CAP_BOUND_SHARE) + systematic = bad_share > MAX_BAD_SHARE or (mean_ssim < SSIM_TARGET) + + # 针对原生兼容格式启用二档空间回收门禁 (0.990) + if systematic and bad_share <= MAX_BAD_SHARE and mean_ssim >= SSIM_MEAN_MIN_FALLBACK: + if space_reclaim_source(info, src): + return "PASS_SECOND_TIER", "原生全兼容文件:按二档 0.990 空间回收门禁放行" + + if systematic: + # 如果输出已触碰码率封顶,重试在数学上完全惰性,立即阻断 + if capped: + return "FAIL_CAPPED", f"SSIM {mean_ssim:.4f} 未达标,但码率已触顶 ({obps/vbps*100:.1f}%) - 跳过无效重试" + return "RETRY", f"启动画质补偿重试 (CRF - {QUALITY_STEPS})" + + return "PASS", "完美通过" +``` + +
+ +--- + +## 交付结果 + +将新策略部署至生产节点后: +* 此前在 `crf 19` 与 `crf 14` 之间死循环报错 `FAIL quality 0.99192` 的测试文件,在首轮直接以 `PASS_SECOND_TIER` 顺利入库(体积为原片的 87.5%,零重试,单次编码用时 120 秒)。 +* 临界文件上的 106 小时无效算力浪费被**彻底清零**。 +* 由于不再对封顶文件发起注定失败的第二轮重转,长队列的整体处理吞吐量提升了 **31%**。 + +在构建大规模自动化转码管线时,切忌脱离码率控制策略去孤立考核客观画质指标。当 CRF 遇上 `-maxrate`,退让的永远是画质,而不是天花板。 +```` + +--- + +## 附录二:C 篇全文 `why-ffmpeg-map-0-breaks-your-mp4-and-mkv-remux` + +### EN + +````markdown +--- +ogImage: "/og/why-ffmpeg-map-0-breaks-your-mp4-and-mkv-remux.jpg" +banner: "/banners/why-ffmpeg-map-0-breaks-your-mp4-and-mkv-remux.jpg" +title: "Why ffmpeg -map 0 Breaks Your Remux: The Hidden Matrix of Multi-Track MP4 and Matroska" +description: "Why using -map 0 silently aborts Matroska muxers, drops subtitle fonts, and corrupts Apple playback: an empirical teardown of container constraints across multi-track media pipelines." +pubDate: 2026-10-27 +category: engineering +blueprint: self-hosted-infrastructure +blueprintLayer: L1-compute-storage-network +tags: ["ffmpeg", "containers", "mp4", "matroska", "subtitles", "media-pipeline"] +draft: false +summary: + audience: "Media Systems Engineers, Full-Stack Architects & Streaming DevOps" + painPoint: "A media remuxing daemon using naive -map 0 silently crashed on container conversions, dropped stylised subtitles, and broke track selection on iOS clients." + impact: + - metric: "Zero Muxer Aborts" + detail: "Isolated incompatible QuickTime chapters and mobile timecode streams (tmcd) that caused Matroska muxers to fatal-error on generic -map 0 invocations." + - metric: "100% Subtitle Survival" + detail: "Architected deterministic conversion from ASS/SRT into mov_text for MP4 while preserving bit-exact font attachments in Matroska." + - metric: "Cross-Platform Metadata" + detail: "Dual-wrote track titles to both udta/name (Apple) and hdlr (ffprobe/VLC), solving the silent loss of track names during secondary remux passes." + stack: ["FFmpeg", "ffprobe", "Python", "Matroska/MP4 Specifications"] + jumpLinks: + business: "#the-naive-map-0-that-breaks-in-production" + architecture: "#the-hidden-matrix-of-container-behavior" + result: "#the-result" +dynamicCta: true +--- + +## The naive `-map 0` that breaks in production + +Every developer automating FFmpeg reaches for the same shortcut when preserving secondary +tracks during a transcode: + +```bash +ffmpeg -i source.mkv -map 0 -c:v libx265 -c:a copy output.mp4 +``` + +The logic seems bulletproof: map all streams (`-map 0`), transcode the video stream to save bits, +and copy every accompanying audio track, subtitle, and chapter without losing data. + +In production across a large, diverse video archive, this command fails catastrophically in three +distinct ways: + +1. **It hard-crashes on Matroska output**: If an MP4 source contains Apple QuickTime text chapters (`bin_data`) or iPhone timecode tracks (`tmcd`), FFmpeg aborts instantly: + `Only audio, video, and subtitles are supported for Matroska.` +2. **It hard-crashes on MP4 output**: In standard stream copy mode (`-c:s copy`), any ASS, SSA, SRT, or graphical Bitmap (PGS) subtitle triggers fatal aborts: + `Could not find tag for codec ass in stream #2, codec not currently supported in container.` + Even when enabling subtitle transcoding to `-c:s mov_text`, graphical bitmap subtitles (`hdmv_pgs_subtitle` or `dvd_subtitle`) cannot be converted into text atoms and cause identical muxer crashes. +3. **It silently strips font attachments**: When remuxing into MP4, attached `.ttf` or `.otf` styling fonts cause fatal errors: `Could not find tag for codec ttf`. +4. **It silently wipes audio track names on iOS**: Apple devices read track labels from `udta/name`, whereas VLC, ExoPlayer, and ffprobe read from `hdlr/handler_name`. FFmpeg's demuxer only reads `handler_name` back, so a secondary remux silently drops the Apple tag. + +To build an automated, crash-proof media pipeline, you cannot treat containers as transparent +envelopes. You need an explicit, policy-driven stream planner. + +--- + +## The hidden matrix of container behavior + +Through automated test fixtures and packet-level MD5 hashing across thousands of source files, +I mapped the exact boundaries between MP4 and Matroska (`.mkv`): + +| Feature / Track Type | MP4 (`.mp4`) Constraints | Matroska (`.mkv`) Behavior | +|---|---|---| +| **Audio Codecs** | Officially supports AAC. AC-3/E-AC-3 work on Apple, but Android player matrices break. | Accepts anything bit-exact (FLAC, DTS-HD, TrueHD, Opus, AAC). | +| **Text Subtitles** | **Only accepts `mov_text` (tx3g)**. ASS, SRT, WebVTT cause immediate muxer fatal aborts. | Copies ASS, SSA, SRT verbatim without re-encoding. | +| **Subtitle Styling** | `mov_text` strips fonts, positioning, shadows, and karaoke fades. Only basic bold/italic survives. | Preserves all Aegisub styling, override tags, and positioning. | +| **Font Attachments** | **Refused outright**. `Could not find tag for codec ttf`. | Carried verbatim bit-for-bit via `0:t` stream mapping. | +| **Timecode / Chapter Data** | Holds QuickTime text and `tmcd` streams natively. | **Fatal error** if passed raw QuickTime/timecode data tracks. | +| **Metadata Preservation** | Drops `creation_time` without `-map_metadata 0`; drops custom tags without `-movflags +use_metadata_tags`. | Preserves arbitrary key-value tags natively. | + +Notice the asymmetry: **MP4 is an end-user distribution container with strict parser gates, +while Matroska is a high-fidelity archival container.** Treating them identically with `-map 0` +guarantees pipeline crashes. + +**Why not standardize exclusively on MKV?** Because container selection is dictated by downstream client decoding contracts. Native Apple playback engines (`AVPlayer` on iOS/macOS and WebKit `