Skip to content

[pull] master from yeasy:master - #97

Open
pull[bot] wants to merge 1169 commits into
ds-ebooks:masterfrom
yeasy:master
Open

[pull] master from yeasy:master#97
pull[bot] wants to merge 1169 commits into
ds-ebooks:masterfrom
yeasy:master

Conversation

@pull

@pull pull Bot commented Mar 14, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

@pull pull Bot locked and limited conversation to collaborators Mar 14, 2026
@pull pull Bot added ⤵️ pull merge-conflict Resolve conflicts manually labels Mar 14, 2026
yeasy and others added 30 commits July 23, 2026 17:14
…solete

The build script anticipated an older mdPress behaviour where a shared
--output made the site land in a sibling directory, then moved it back:

  mdpress build --format site --output _site && rm -rf _site && mv _site_site _site

mdPress 0.8.1 writes straight into --output, so the sibling never appears and
the mv aborts the step:

  ✓ Generated site  → .../docker_practice/_site/index.html
  mv: cannot stat '_site_site': No such file or directory

Caught by a smoke test after 0.8.1 shipped, before any content push fanned it
out — this repo is the only one with a live copy of the workaround (the three
matches in other repos are inside gitignored _book/ build artifacts).

The site itself built fine; only the post-processing dance failed.
Neither step did what its name said, and together they meant no Dependabot PR
was ever auto-merged — the job died before reaching the merge step.

1. "Confirm required checks are configured" failed OPEN. It read branch
   protection, which GITHUB_TOKEN cannot do (administration scope is not even
   a valid permissions key), so gh api returned 403 JSON into REQUIRED and
   `[ "$REQUIRED" -eq 0 ]` died with "integer expression expected". A failing
   test inside an `if` condition is exempt from set -e, so the guard evaluated
   false and let execution continue. A guard whose whole purpose is refusing
   an unsafe merge silently passed whenever it could not check.

2. "Approve low-risk Dependabot PR" can never succeed: GitHub Actions is not
   permitted to approve pull requests. It is also unnecessary — these repos do
   not require reviews.

The real gate is now branch protection, which as of today requires BOTH
check-commit-identity and CI on every repo, and gh pr merge --auto cannot
complete until they pass. That is stronger than the removed probe, which only
checked that some required check existed.

This makes the 13 repos match oc_guide, which already had exactly this shape
and was the only one whose auto-merge was not broken.

prompt_engineering_guide asserted the old step ORDER; its test now asserts the
invariants that still hold — no self-approve attempt, and auto-merge gated on
the low-risk update-type allowlist.
… a paragraph

The book style writes a bold label then a list with no blank line between them:

    **触发分离的条件**:
    - 输入序列长度 > 某阈值

Strict CommonMark (pandoc default -f markdown) requires a blank line before a
list, so it folded every such list into a single <p>. Measured 640 occurrences
across 13 books — troubleshooting checklists and diagnostic steps shipped in the
offline HTML reader as run-on paragraphs, while GitBook, GitHub, and the PDF all
rendered them correctly. The source was fine; only this reader was wrong.

Fix is the additive pandoc extension markdown+lists_without_preceding_blankline,
verified on real book content through real pandoc: the three bullets that
previously joined into one <p> now produce <ul><li>, with no effect on ordinary
paragraphs or on tables (pipe_tables stays enabled).
…ndoc

The reason 640 collapsed lists shipped green is that no test exercised real
markdown->HTML rendering — the existing build_html_reader tests stub pandoc with
a fake, covering TOC/mermaid plumbing but never the markdown parse. This feeds
the exact bold-lead-in pattern through the real pandoc invocation the reader uses
and asserts the list survives as <ul>. Proven both ways: passes with the
extension, fails when it is removed.
…sis check

check_project_rules.py only checks fences + local links, so a bold marker that
CJK punctuation stops from rendering (e.g. `**(配比)**相互交织` — the closer is
preceded by punctuation and followed by a letter, so it can't close and the
asterisks print literally) would pass this repo's own CI. That class of bug had
to be swept out of every book by hand; nothing here was watching for it.

Adds check_emphasis.py — a standalone gate (identical across all books) that
runs CommonMark's flanking + process-emphasis rules and flags markers that
print literally or silently bold the wrong span. The ci.yaml content step now
runs it right after check_project_rules.py. Verified clean on this book.
The step ran `check_project_rules.py && check_emphasis.py` with more
commands on following lines. Under `bash -e` the left operand of `&&`
is an exempt context, so a rules failure did NOT abort the step: it
short-circuited past check_emphasis.py, execution continued to the next
line, and the step exited with that last command's status -- green.

Demonstrated:
  bash -e -c 'false_check && emphasis
              next_cmd'          -> exit 0   (failure swallowed)
  bash -e -c 'false_check
              emphasis
              next_cmd'          -> exit 1   (correctly blocks)

So the repo's main content gate could fail while CI stayed green, and
today's new CJK emphasis gate was skipped entirely whenever it did.
Splitting the line restores fail-fast for both.

Swept all 14 repos: only this one and the other CI whose && line is
followed by further commands were affected; the 12 where the && list is
the last line of its run block already exit non-zero and are unchanged.

Verified: YAML parses, both checkers pass, repo test suite unchanged.
FENCE_RE was `^\s*(`{3,}|~{3,})`, which matches leading whitespace but not
a blockquote marker. A fence written as `> ```python` was therefore never
recognised, so the quoted code was scanned as prose and CJK-adjacent `**`
inside it was reported as non-rendering emphasis. Since the emphasis check
became a blocking CI gate, that false positive could red-line a legitimate
push over a code sample in a callout.

This file documents itself as mirroring check 6 of the workspace-level
format_checker.py, whose FENCE_RE already handles up to three spaces of
indent plus any number of `>` prefixes. Adopting that pattern restores the
stated invariant.

Demonstrated before/after on a file holding the same line both inside and
outside a blockquoted fence: previously only the quoted copy was flagged
(exit 1); now neither is (exit 0), while a genuine prose defect is still
reported (exit 1), so the gate is not weakened. All 14 repos re-checked:
check_emphasis and check_project_rules pass on all 1,615 markdown files,
and the 14 copies remain byte-identical. A mutation-tested regression guard
now lives in the workspace repo at tests/test_blockquote_fence_emphasis.py.
A reader dates a book by the book. An inline 截至 <date> or (核验日期:<date>)
tells them nothing they cannot infer, and it decays: this cluster's clearest
case is the eight files that carried 截至 2026-05-18 while git showed they were
written 33 days earlier, one commit having later nudged the date a day for
visual consistency.

Two kinds of date deliberately survive, because they are not as-of stamps:

- Forward-looking deadlines. "Sonnet 5 的 $2/$10 是截至 2026-08-31 的介绍价"
  states when a price expires; deleting that date would lose a fact.
- Ledger metadata. verified_at / expires_at / ttl_days in the volatile-facts
  appendices are inputs to check_volatile_facts(), which fails the build when
  they lapse. That is the right home for a verification date — machine-checked
  and self-expiring, rather than prose nothing re-earns.

Event dates are untouched throughout (2026-06-09 GA, 2026-06-12 暂停). Where a
sentence needed the sequence, "截至 2026-07-09 官方模型页已恢复" became
"官方模型页此后已恢复", which keeps the ordering without the observation date.
深审发现 14 处正文与上游现状不符(另有 4 处在 .agent/ 本地记忆,不入库)。
全部经官方一手源核验,并由独立复核者逐条尝试证伪后保留。

**Docker Content Trust 已从 CLI 移除(本书基线正是 Engine 29.x)**
- 18.6:`docker trust`、`DOCKER_CONTENT_TRUST` 的示例此前标为“历史用法”,
  但未说明命令在 29.x 上根本不存在。Engine 29.0 release notes 原文:
  "Docker Content Trust was removed from the Docker CLI";实测 docker/cli
  的 cli/trust 包在 v28.5.0 返回 200、v29.0.0 返回 404
- 2.3、18.6:DCT 不再是“弃用阶段、时间线待公布”——官方 retired 页已给出
  完整时间表(notary.docker.io 2026-12-08 关闭)
- 注意:`--disable-content-trust` 是 MarkDeprecated(隐藏但仍接受为 no-op),
  不是“不可用”。初版提议写成不可用,经复核纠正后才落地

**etcd 维护分支写反了**
- 15_etcd README/15.1/15.2 均称“3.5 已退出维护窗口”,但 2026-07-23 官方与
  v3.7.1、v3.6.14 同批发布了 v3.5.33,公告称三个分支均在维护;3.4 EOL 也应
  是 2026-06(v3.4.45),非 5 月

**其他上游变化**
- 17.6:containerd 首个 2.x LTS 是 2.0(2024-11-05 起),非 2.3
- 6.4:`sonatype/nexus3:3.69` 这个 tag 从不存在(Sonatype 用三段式),改 3.69.0
- appendix/repo/centos:`rockylinux` 官方镜像已冻结(最后推送 2024-05-30),
  改用官方指引的 `rockylinux/rockylinux`(2026-07-12 仍在更新)
- 6.2/6.3:docs.docker.com/registry/ 已 301 到 retired 页,改指
  distribution.github.io(实测 200)
- 6.1:`docker search centos` 的示例输出补上上游现有的 DEPRECATED 标记

**刻意未改**:`minio/minio` 虽在 Docker Hub 标记 archived,但提议替换的
quay.io/minio/minio 最新 tag 停在 2024-07-31,比 Docker Hub 的 2025-09-07
更旧,照改会把读者引向更陈旧的仓库,留待作者定夺。

验证:check_project_rules 202 文件全过、emphasis 全过、40 测试通过、format 干净。
第 18、21 章的工作流示例仍用 actions/checkout@v6 与 actions/upload-artifact@v4,
而上游当前大版本分别是 v7.0.1(2026-07-20)与 v7.0.1(2026-04-10);本仓库自己的
.github/workflows/* 早已按 SHA 钉在 v7.0.1 上,书里教的和书自己在跑的不一致。
21.2 更是同一个文件内自相矛盾:第 27/108 行写 @v6,第 59 行却钉着 v7.0.0 的 SHA。

sigstore/cosign-installer 一并从 @V3 提到 @v4.1.2:官方 v4.0.0 发布说明写明
"You must upgrade to cosign-installer v4 if you want to install Cosign v3+",
而 18.6 手工安装那段用的是 releases/latest(今天就是 Cosign 3.x),两条路径装的
不是同一个大版本。该仓库没有可移动的 v4 标签,只能引用完整版本号,已就地加注说明。

docker/{build-push,setup-buildx,login}-action、anchore/sbom-action@v0、
github/codeql-action@v3 逐一核对后确认仍是当前受支持版本,未改动;
trivy-action 的 SHA 57a97c7e 经 GitHub API 核对确与 v0.35.0 一致,注释无误。
1. FROM「必须是第一条指令」(4.5、7.16、summary 三处)与本书 7.7.4「FROM 之前的
   ARG」整节直接冲突。官方 Dockerfile 参考写的是 "A Dockerfile must begin with a
   FROM instruction. This may be after parser directives, comments, and globally
   scoped ARGs",且 "ARG is the only instruction that may precede FROM"。三处
   一并改成「第一条构建指令」并点明例外。

2. 12 章小结把 USER Namespace 列进「默认启用」的一档,并写「容器 root ≠ 宿主机
   root」;而同章 12.2.2 明写 USER Namespace 默认**不**启用、需 userns-remap 显式
   开启,18.1 更直说「在默认情况下,容器内的 root 用户(UID=0)就是宿主机上的
   root 用户」。小结按正文口径改回——这条读反了会直接误判容器逃逸的风险面。

3. 11.5「目前支持三种日志驱动类型」是 Compose 早期文本的残留。官方支持 json-file、
   local、syslog、journald、gelf、fluentd、awslogs、splunk、etwlogs、gcplogs、
   none 共十余种,本书 19 章小结自己就列了六种。改为给出常见取值并链到官方清单。

4. 7.8 与 7.5 的示例注释建议 postgres:latest / redis:latest,而 4.1、7.10、
   7.16、7 章小结、4.5 全都要求避免 latest(7.8 同一文件第 177 行也写「避免
   latest」)。按全书口径改掉这两处。

另:ENV 的空格分隔旧写法(7.6 的「格式一」、附录四的 PG_MAJOR/PATH 示例)改为等号
形式并加注。BuildKit 的 LegacyKeyValueFormat 检查会报
"ENV key=value" should be used instead of legacy "ENV key value" format,
而本书 10.2 与 07 章 README 正是在推荐 docker buildx build --check。
* appendix/repo/nodejs.md:`docker build -t my-nodejs-app` 缺上下文参数,实际会
  报 "docker buildx build" requires exactly 1 argument;补 `.`。同文件 docker run
  示例里 `# -v "$ ":/usr/src/myapp \` 这一行的注释把自己的续行反斜杠也注掉了,加上
  紧跟的空行,命令在 `--name my-running-script` 处就结束、没有镜像名,后面几行变成
  独立的无效命令。该行内容与下一行的 --mount 重复,且 `"$ "` 已是残缺文本,删去。

* 06_repository/6.4:`openssl s_client -connect YourDomainName OR HostIP:443` 里的
  占位符带空格,shell 会切成三个参数,-connect 只收到 YourDomainName,openssl 直接
  报错;`docker login YourDomainName OR HostIP` 同理。改为一个变量。

* 08_data/8.2:`docker run -v $(pwd):/app -p 3000:3000 node npm run dev` 跑不起来
  ——官方 node 镜像没有设置 WORKDIR(docker-node 的 Dockerfile 里只有 ENTRYPOINT
  与 CMD),工作目录是 /,npm 找不到 /package.json。补 -w /app,顺手引号包住 $(pwd)
  并给出确定的标签(本书 4.1、7.10 都要求避免 latest)。

* 12_implementation/12.6:整段用的是 iproute2(ip link add / ip netns exec),中间
  却夹了一句 `brctl addif`。bridge-utils 在当前 Debian/Ubuntu/RHEL 默认不再安装,
  照抄会在这一行断掉;改成等价的 `ip link set A master docker0`。

* 14.1/14.2 的 `sysctl --system`、14.1 join 节点的 `systemctl enable/start
  containerd`、6.4 的 `systemctl restart docker` 都缺 sudo,而紧邻的行(sudo tee、
  14.1 第 47 行的 sudo systemctl restart containerd)都带。补齐。

* 15_etcd/demo/cluster/docker-compose.yml 仍留着顶层 `version: "3.6"`,Compose 会
  警告 obsolete;11.1 明写「新文件建议直接省略该字段」,15.3 正文内联的同一份文件
  也早已省略,只有磁盘上的 demo 落下了。删除后 YAML 仍可正常解析。

* 11.4:「对于 web 项目中的一个 db 容器,可能是 web_db」是 Compose V1 的下划线拼接,
  V2 起统一改用连字符,只有 --compatibility 才回到下划线。

* 附录四 CMD 一节四处写成 `CMD ['executable', 'param1']` 单引号,还说「我们建议任何
  服务镜像都使用这种形式」。exec 形式是 JSON,单引号解析不出来会退回 shell 形式,本书
  7.4 就把 `CMD ['node', 'server.js']` 明确标为「❌ 错误:单引号(JSON 不支持)」。
  一并把 `CMD ['PHP', '-a']` 的二进制名改回小写 php。
11.4.2 让读者在 V2 里改用 `COMPOSE_DEBUG=1` 打开调试输出,但这个变量既不在官方
[Compose 环境变量清单](https://docs.docker.com/compose/how-tos/environment-variables/envvars/)
里(该页只有 COMPOSE_PROJECT_NAME、COMPOSE_FILE、COMPOSE_PROFILES … COMPOSE_PROGRESS
等 15 个),在 docker/compose 源码里也搜不到——照做不会有任何效果。

同时 `--verbose` 的定性收紧一档:源码 cmd/compose/compose.go 里它仍会
`logrus.SetLevel(logrus.TraceLevel)`,只是被 `MarkHidden("verbose")` 从 --help 里
藏起来了,并没有像 `--no-ansi` 那样被标 DEPRECATED,所以说"已弃用"不准确。

另:同节下一行的 `-v, --version` 经核实是对的,虽然官方文档的 global options 表里
没有它,但源码 `c.Flags().BoolVarP(&version, "version", "v", ...)` 确实注册了该短选项
(同样是 MarkHidden 才没进文档),故**未改动**。
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.12 to 3.4.13.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](cure53/DOMPurify@3.4.12...3.4.13)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.4.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 11.16.0 to 11.16.1.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Commits](https://github.com/mermaid-js/mermaid/compare/mermaid@11.16.0...mermaid@11.16.1)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-version: 11.16.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
附录三的客户端命令表停留在管理命令引入之前的形态,本书自己讲了整整两章的
`docker buildx`(第 10 章)与 `docker compose`(第 11 章)都不在表里,而全书正文
分别用到 27 次和 127 次;`image`(41 次)、`container`(20 次)、`system`(10 次)、
`manifest`(9 次)、`scout`(5 次)、`init`(3 次)同样缺席。按 docker/cli v29.0.0
的 `cli/command/commands/commands.go`(builder/checkpoint/config/container/context/
image/manifest/network/node/plugin/registry/secret/service/stack/swarm/system/volume)
与官方 CLI 参考页的 Subcommands 表补齐 15 条,保持原有字母序。`trust` 不补——
Engine 29 发行说明明写 "Docker Content Trust was removed from the Docker CLI",
与 2.3 节的叙述一致。

另两处按 v29.0.0 `cli/flags/options.go` 核对后修正:

- TLS 三个默认路径写成了绝对路径 `/.docker/*.pem`,与同一张表第一行
  「`--config` 默认为 `~/.docker`」自相矛盾。源码里默认证书目录就是客户端配置
  目录,应为 `~/.docker/{ca,cert,key}.pem`。
- 全局选项漏了 `-c, --context`。
- `search` 的说明还写着「Docker index」,官方为 "Search Docker Hub for images"。
四条链接指向的仓库都换了归属,旧地址目前只靠 GitHub 的改名重定向活着:

- Podman、Buildah、Skopeo 已从 `containers` 迁到 2026-04-20 新建的
  `podman-container-tools` 组织。podman.io 首页与 Get Started 页现在
  直接链向 `podman-container-tools/podman`;GitHub API 显示该仓库 32.7k
  星、homepage 为 podman.io、最后推送在 2026-08-21,是活跃主仓。
  注意 `containers` 组织本身还在(`containers/image`、`common`、`storage`
  等仍留在原处),迁走的只有这三个,故只改这三条。
- Spin 已随项目捐给 CNCF 改名 Spin Framework,仓库为 `spinframework/spin`
  (homepage `https://spinframework.dev`),`developer.fermyon.com/spin`
  现在整站 302 到 `spinframework.dev`。

17.5 的「属于 containers 项目生态」一句同步改掉——那句话本身随迁移失效了。
上一轮深审只查了「有没有死链」,这批全是 200 但被重定向到别处的:目标页仍在,
路径/域名却换了。逐条 curl 追 final URL 后对齐:

- docs.docker.com 的旧 `engine/reference/commandline/` 命令参考已整体搬到
  `reference/cli/docker/`(resources、learning_roadmap、10.2 三处);
  `get-docker/` 搬到 `get-started/get-docker/`。
- `config/labels-custom-metadata/` 搬到 `engine/manage-resources/labels/`。
  best_practices 那句里两个不同的链接文字原本指向同一个 URL,现按新页的两个
  小节分开:Label keys and values / Manage labels on objects(浏览器实读该页
  DOM,两个 id 都在)。
- Fedora 官网域名由 getfedora.org 改为 fedoraproject.org(3.3、20.4 两处)。
- kubernetes.io 的中文站点是 `/zh-cn/`,`/zh/` 是旧前缀。
- Azure 的产品页由 `/services/` 改到 `/products/`。
- 阿里云容器服务改名 ACK:`/product/kubernetes` → `/product/ack`;
  文档站 `help.aliyun.com/product/85222.html` → `help.aliyun.com/zh/ack/`。
- Sonatype 下载页由 `/product/repository-oss-download` 改到
  `/products/sonatype-nexus-oss-download`。
4.2.5 用一条裸 `docker images` 演示 `<none>` 行,但本书 1.1 的版本提示写明
「本书内容及示例基于 Docker Engine v29.x 及以上版本」,而这条输出在 29 上
不可能出现:docker/cli#6574(2025-10-29 合入,随 29.0 发布)把无标签镜像
从默认列表里拿掉了,Engine 29 发行说明写作 "docker image ls no longer shows
untagged images by default if no --all flag is provided",官方 `docker image ls`
参考页的 Description 现在也是 "Untagged (dangling) images are also hidden by
default. Use the -a (--all) flag to show intermediate layers and dangling
images."(该页 Examples 里的老输出反而还没跟上,以 Description 与发行说明为准)。

示例改用本节自己后面就在推荐的 `-f dangling=true`,并把这条默认行为写进导语。
4.2.6 「-a 会显示很多无标签镜像——这些是构建过程中产生的中间层」同步补上虚悬镜像
——`-a` 的官方说明正是 "default hides intermediate and dangling images",两类
都藏,只说中间层会让读者以为虚悬镜像仍在默认列表里。
全书此前是真·五五开:含中文的括号 336 处半角 : 336 处全角,而且**在同一文件
内混用**——`18_security/18.1_kernel_ns.md` 第 11/17/20 行写 `(如 runC)`,
第 31 行写 `(用户命名空间映射)`。这不是体例问题,是它自己跟自己不一致。

集群里其余 13 本都一边倒(llm_internals 5 : 1049、oc_guide 2 : 713、
forward_deployed_engineering_guide 0 : 108),blockchain_guide 已在 2026-08-05
按同一方向统一过 97 处,故取全角,而非另立新规。全角括号前后不留空格,
原先 `概念 (镜像、容器、仓库) 和` 两侧的空格一并收掉。

**纯拉丁内容的半角括号一个没动**——`新建一个层 (Layer)`、`项目 (project)`
保持原样:改动前后「半角且内容纯拉丁」的数量都是 **1163**,而「半角且含中文」
从 **336 变成 0**。

其余验证:改动前后各自抹掉全部空白与圆括号后**逐字节相同**(两侧均 19559 字符);
左右括号总数改动前后都是 399 : 399,证明没有吞括号也没有配错对。
`check_project_rules` 202/202、`check_emphasis` 202/202、37 tests OK、
根 `format_checker` 与 `validate_codeblocks` 干净、`git diff --check` 干净。
《现代汉语词典》与《中文技术文档写作风格指南》都以「其他」为规范词形;
本书基线是 90 处「其他」比 35 处「其它」,是集群里遗留最多的一本
(其余各书 0~4 处,blockchain_guide 已于 2026-08-05 统一过 47 处)。

其中 6 处在标题和目录上——`4.6 其它制作镜像的方式`、`第十七章 容器其它生态`、
`18.5 其它安全特性`——标题与 SUMMARY 条目**同批改写以保持一致**:
改动后 196 条目录名与各文件标题仍是 0 处不一致。

**`CHANGELOG.md` 里那一处刻意不动**:它是历史发布记录,改它等于改历史。

改动前后逐字符对称差:只有 34 个「它」变成「他」,无其他字符变化。
`check_project_rules` 202/202、`check_emphasis` 202/202、37 tests OK。
按本书自己的多数体例收口,不是引入新规范;同批在集群 14 本书上做同一件事。

**中英文空格**:改动前 8753 处已空格 : 36 处紧贴(0.4%),改动后紧贴 **0**。只在「汉字 ↔ 拉丁字母/数字」直接相邻处插一个半角空格;不动中文标点与拉丁字符之间、也不动半角括号与中文之间。

脚本用私用区字符遮蔽围栏代码、行内代码、`$$…$$` 公式、链接与图片目标、HTML 标签、裸 URL、引用式链接定义与 YAML front matter 之后再改。

验证:改动前后各自抹掉全部空白与两种圆括号后**逐字节相同**;左右括号总数守恒;逐行比对无一行新增连续多空格;SUMMARY 目录名与文件标题的一致数改动前后不变。`check_project_rules`、`check_emphasis`、根 `format_checker`、`validate_codeblocks`、`scan_secrets` 全绿,测试套失败集合与改动前一致。
今天这批 style 提交把两项排版统一到位,但**没有任何东西拦住下一段正文漂回去**:`check_project_rules.py` 看围栏与链接,`check_emphasis.py` 看加粗标记,工作区的 `format_checker.py` 看引号、围栏语言与标题空行——**行内排版此前一道门禁都没有**。漂移到什么程度是有实测的:同集群一本书的中英文边界曾有 45% 未空格、另一本的含中文括号是 50/50,而且都是在**同一个文件内**混用。

两条规则:

- **规则一**:汉字不得与 `[A-Za-z0-9]` 直接相邻。`Harness的定义`、`第4章` 违规。
- **规则二**:括号内容含任何中文时必须用全角。`(用户命名空间映射)` 违规;**纯拉丁内容的半角括号一律合法**——`新建一个层 (Layer)`、`项目(Agent)的经验` 都不报,因为它们的内容里没有中文。

**刻意不检查**中文标点与拉丁字符之间的空格(`(如 UI、监控服务)`)——全集群一致不加,全角标点本身自带视觉留白。

**负向验证**(AGENTS.md 核心约定 4:不可能失败的检查不构成证据):把本书扫改前(`ef262553~1`)的内容原样喂给这道门禁,报出 规则一 **28 处**、规则二 **338 处**;喂当前内容,报 **0 处**。

文件自带 9 个用例,其中 5 个专门钉住「不该报的别报」和「该报的必须报」:纯拉丁括号不报、行内代码/链接目标/裸 URL/HTML 属性/行间公式不报,而 `<span>中文abc</span>` 的**标签之间的文字**要报、`定价 $25/$125 per M tokens` 这种成对的 `$` **不得**被当成公式把中间的正文遮蔽掉、`| <1% | … | >400ms |` 这种表格行**不得**被当成一个 HTML 标签。后两条是扫改时真踩过的坑,掩码写松了会静默漏报。

占位符取私用区字符(既非汉字也非拉丁字符也非数字),且每条掩码正则都排除该区间——否则贪婪的 `\S+` 会把前一条规则刚放下的占位符一起吞掉。
供应链测试把 action 的 **SHA 按字面钉死**:

    self.assertIn("actions/attest-build-provenance@0f67c3f... # v4.1.1", ...)

Dependabot 升级这个 action 时改的正是被断言的那一行,于是断言必然失败、`build` 红、
PR `mergeStateStatus=BLOCKED`。这是**结构性死锁**:该 action 的任何一次升级都会打挂
钉死它的测试,PR 永远合不进来。**全集群 7 个仓库钉的是同一个 SHA,6 个 PR 同时卡在这里**
(agentic #13、aifbs #8、ai_security #17、blockchain #176、ceg #16、docker_practice #578)。
反证很干净:另外 4 个断言写成 `assertIn("actions/attest-build-provenance@")` 的仓库
(claude_guide / peg / fde / llm_internals)**同一个升级早就合进去了**。

断言真正要守的是「release job 必须用 SHA 钉住这个 action」——**这个意图不需要记住是哪个
SHA**。改为 `assertRegex(..., r"actions/attest-build-provenance@[0-9a-f]{40} # v\d")`:
仍然强制 40 位 SHA + `# vN` 溯源注释,**比那 4 个仓库现用的宽松写法更严**,但升级不再撞墙。
同批把 aifbs / ai_security 里 `dependabot/fetch-metadata@25dd0e3... # v3.1.0` 的同款钉死
一并改掉(未升级该 action,只解除钉死)。

同时在主干上直接升掉 Dependabot 提的两个版本,**SHA 逐个用 GitHub API 解引用核过**
(`browser-actions/setup-chrome` 的 tag 是 annotated tag,要走 `git/tags` 再取
`.object.sha`,直接读 `git/ref/tags` 会拿到 tag 对象的 SHA 而误判):

- `actions/attest-build-provenance` 0f67c3f… v4.1.1 → 4d10147… v4.2.2
- `browser-actions/setup-chrome` 2e1d749… v2.1.2 → 48ad923… v2.2.0(blockchain_guide 已在 2.2.0)

**负向验证**(AGENTS.md 核心约定 4,逐条证明新断言没被改成永真):把 pin 换成浮动 tag
`@v4` → FAIL;保留 SHA 但删掉 `# vN` 注释 → FAIL;还原 → OK。七个仓库各跑一遍,
结果一致。而「换一个合法 SHA 应当通过」这一条由本提交自身证明——pin 从 v4.1.1 换到
v4.2.2,测试全绿。

各仓库 `check_project_rules`、`unittest discover -s tests -p 'test_*.py'`、
`git diff --check` 均通过;docker_practice 另跑 `npm test` 通过。
Bumps the dependencies group with 1 update: [softprops/action-gh-release](https://github.com/softprops/action-gh-release).


Updates `softprops/action-gh-release` from 3.0.2 to 3.0.3
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](softprops/action-gh-release@3d0d988...efb3536)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
…cies-6512b1d693

chore(deps): bump softprops/action-gh-release from 3.0.2 to 3.0.3 in the dependencies group
安全(四条,前三条都是书自己打自己):

- 18.5 的自定义 seccomp 示例用 `"defaultAction": "SCMP_ACT_ALLOW"` 写了一个
  blocklist。但 `--security-opt seccomp=<file>` 是**整体替换**默认 profile
  而非叠加(官方 seccomp 文档:容器"uses the default profile unless you
  override it"),而默认 profile 恰恰是 deny-by-default 的 allowlist——本节
  第 11 行自己就这么写的。净效果是:挡住了 chmod,却把默认拦下的那 ~44 个
  高危调用(unshare / keyctl / add_key / bpf / perf_event_open / init_module
  / reboot 等)全部放行,**比不加这个参数更危险**,而读者会以为自己加固了。
  改为从官方 default.json 派生,并在示例前置醒目警告。
- 7.6 把 `docker run -e DB_PASSWORD=xxx` 标成「✅ 正确」。它只是把泄漏路径
  从镜像换到了别处:进 shell history、进宿主机进程 argv(同机任何用户 ps
  可见)、并永久固化在 `docker inspect` 的 Config.Env 里。而本书 11.8:102
  明写「不要把数据库密码写入 compose.yaml、.env、**命令行** 或 Git」,
  16.3:251 也要求「避免把真实口令写入命令历史」。改为标注 -e 的泄漏面,
  并给出 --env-file / Compose secrets 的正确做法。
- 6.2 教读者起一个**无认证、无 TLS** 的 registry,且引导把它绑到内网地址、
  开 insecure-registries,全节零安全提示。同网段任意主机可匿名 push,即
  镜像投毒。而 18.3:17 用最强语气写着「绝不能在没有任何安全控制的情况下
  强行开启」。示例改为绑 127.0.0.1,并在两处补上风险说明与指向 6.3 的出口。
- 18.6 的 CI 示例里,Trivy 那条注释写着「务必使用不可变的 commit SHA 引用,
  而非可变标签」(引的是 2026-03-19 Trivy Actions 76 个 tag 被劫持事件),
  可同一 workflow 里另外九条 uses 全是可变标签,包括持有凭据的
  docker/login-action。补一段说明点破这一点。(本仓库自己的
  .github/workflows/ 确实全部按 SHA 钉死,已实测确认后才写进书里。)

版本时效(对 GitHub releases / endoflife.date / docs.docker.com 逐条核实):

- 5.1 与 6.3 说 `ubuntu:24.04`「是最新 LTS」——最新 LTS 已是 26.04
  (2026-04-23 发布);而本书 3.1 早就列出了 26.04,属书内不一致。更糟的是
  5.1 建议可替换为 `ubuntu:20.04`,而 3.1:33 正警告 20.04 已不在支持列表。
- 3.1 的支持列表里的 Ubuntu 25.10 已被 Docker 官方安装文档移除(该版本本身
  也已于 2026-07-01 EOL),删去。
- 4.6 拿 `alpine:3.20` 当"锁定版本以确保可重现性"的范例,而 3.20 已于
  2026-04 EOL;改为 3.24。
- 3.2 的「Debian Bullseye 11 将于 2026 年 8 月底结束长期支持」时态已过期。
- kind v0.31.0 → v0.33.0(落后两个小版本);etcd v3.5.29 → v3.5.33。
- 10_buildx/README 推荐「Docker v28 及以上」,而紧接着的段落就在用
  「自 Docker Engine 29 起」立论;改为 v29。
章节里写着「完整的文件如下」,仓库里配套的实际文件却已经和正文对不上。
这类漂移比正文里的笔误更隐蔽——读者按目录跑起来的是另一份东西。

- `11_compose/demo/django/Dockerfile` 用的是 `ENV PYTHONUNBUFFERED 1` 这种
  空格旧写法,而 7.6 节写着绝对规则:「已被官方弃用……`docker buildx build
  --check` 会直接把它标出来。**一律用等号**」。这是全仓唯一一处违反(已扫过
  全部 md 与 11 个 Dockerfile)。同文件还有三处与 11.6 节不符:`FROM python:3`
  (章节是 3.12-slim)、`pip install` 缺 `--no-cache-dir`(章节专门列表讲了
  它的作用)、requirements 用 psycopg2 而章节表格写着「推荐使用 psycopg 3」。
  按 11.6 同步,现与章节代码块逐字节一致。
- `07_dockerfile/multistage_example/laravel/Dockerfile` 与 7.18 节差三处,
  两处有实质后果:`chmod -R 777 storage`(章节是 `chown www-data` + `775`)
  —— 而 18.5 节正拿 `777` 当"全员读写权限"的风险范例;以及 `COPY composer.json`
  漏掉了 `composer.lock`,正好破坏该章论证的可复现性。第三处 nginx 标签
  1.30 与全书 20 处 1.28 不一致(且章节自己的注释就推荐 1.28-alpine)。
  同步后与章节仅差章节独有的那行说明性注释。
- `11_compose/demo/wordpress/docker-compose.yml` 的 wordpress 服务**完全没有
  `volumes:`**,而 11.8 节专门加粗警告过「`volumes` 配置很重要!没有它,每次
  容器重启数据都会丢失」并声明了 `wp_data`。该 demo 恰好演示了章节警告的那个
  故障。另外还缺 `wp_net`、两个 container_name、`WORDPRESS_DB_NAME` 与
  `uploads.ini` 挂载。按章节同步,并补上章节 11.8.3 已给出内容的 `uploads.ini`
  (否则挂载会指向不存在的文件)。同步后 YAML 实测可解析,volumes 为
  db_data + wp_data。

`secrets/` 目录未补:它被 .gitignore 有意排除,demo 的 README 就是教读者
自己创建的,属于设计而非缺失。
- 18.1 完整演示了如何开启 `userns-remap`,全程没有任何代价提示。但官方文档
  明写「The containerd image store is not available when using user namespace
  remapping」,而本书基线是 Docker Engine v29.x——该版本新装默认就是
  containerd image store,12.4 节还把它标为「强烈推荐(现代默认)」。照 18.1
  做完,读者会在毫不知情的情况下退回旧存储后端,失去 OCI image index、多架构
  镜像与构建溯源。补上这条互斥关系并交叉引用 12.4。
- 5 章说「生产环境建议指定确切版本号(如 nginx:1.28.0)而非仅大版本号」,
  7 章说「使用主或次版本号而非完整版本号(patch)」并把 patch 版本列入
  「避免」。两条都以全书建议的口吻写成,方向正好相反。它们其实面向不同环节:
  Dockerfile 的**基础镜像**用主/次版本号,才能在重新构建时自动带上安全补丁;
  **部署**一个服务镜像则应钉死确切版本,让部署可复现、升级是显式动作。
  两处各补一句限定范围并互相指引,而不是删掉其中一条。
跑不起来的五处:

- 11.5:604 的 Compose 示例里 `db:` 顶在第 0 列,是**顶级键而不是服务**。
  实测 yaml 解析结果是 `{'services': None, 'db': {...}}`,`docker compose up`
  直接报错。同文件其他服务示例都缩进正确。
- 10.3 的 `docker manifest create` 把 `--amend` 当成每个 manifest 的前缀写了
  两遍。upstream `docker/cli` 里它是 `BoolVarP(&opts.amend, "amend", "a", ...)`,
  不取值;用法是 `create MANIFEST_LIST MANIFEST [MANIFEST...]`。照抄不仅参数
  错位,还会打开"修改已有列表"的语义。改为直接跟位置参数。
- 21.7:288 `apk add postgresql-contrib pg-stat-monitor`。核对 Alpine v3.23 的
  APKINDEX(427828 行,直接解包官方索引):这两个包名都**不存在**,Alpine 只
  提供带主版本号的 `postgresql16-contrib` 等;`apk add` 遇到未知包会中止,
  该 Dockerfile 卡在第二步。改为 postgresql16-contrib 并去掉 pg-stat-monitor,
  同时注明原因。
- 19.3:528 用 `pip install --user` 装到 `/root/.local`,运行阶段却 `USER nobody`。
  Debian 的 `/root` 是 0700,nobody 无法进入,容器起来就找不到依赖。改为装到
  `/opt/pydeps`(PYTHONUSERBASE)并同步 COPY 与 PATH。
- 11.8:202 的 `WORDPRESS_REDIS_HOST` 不被官方镜像识别——`wp-config-docker.php`
  只映射固定的一组 WORDPRESS_* 变量,其中没有 Redis 项;Redis Object Cache
  插件读的是 PHP 常量 `WP_REDIS_HOST`。改为经 `WORDPRESS_CONFIG_EXTRA` 注入。

违反本书自己规则的四处:

- 全书 21 处 `FROM ... as`(13 处在正文、8 处在 Dockerfile)用小写 as,会被
  `docker buildx build --check` 的 FromAsCasing 报
  `'as' and 'FROM' keywords' casing do not match`——而本书正是拿 `--check`
  当准绳(7.6:16、10.2:23),且自己另有 12 处已经写成大写 AS。统一为 AS,
  并把 7.17:174 那句"可以使用 `as` 来为某一阶段命名"一并改掉并附上检查项链接。
- 6.3:137 建议"可替换为 `httpd:latest`",而同文件 22 行之后就写着"不要使用
  `latest`,以保证部署的可重复性"。删去该建议。
- 7.6:253 写"多个 ENV 会创建多层",而 4.5:5 明确区分过:只改元数据的指令
  不新增文件系统层。ENV 正属于后者。改为"多出几条构建历史记录"。

核验方式的一点说明:查 Alpine 包是否存在时,我先用网页搜索接口做了一遍,
结果四个包名全部"命中"(包括本不该存在的那个)——那个页面会把查询词回显。
改用官方 APKINDEX 解包比对后才得到可信结论,两种方法交叉确认后才动手。
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

⤵️ pull merge-conflict Resolve conflicts manually

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant