Repository navigation
Commit e3ab0aa
ci(docker): publish an on-demand public framework build base image (#31454)
<!-- claude-projects-attribution: {"github_login":"os-zhuang"} -->
_Requested by **Sam** · [project
thread](https://claude.ai/code/project/chan_01GsBogozeZG5sdpAARrN7HP?thread=cmsg_01GsBogozeZG5sdpAARrN7HP1HcuuzFq3zR8mqTyyMaHCW)_
Part of #31441. This PR adds the workflow and the image definition. The
card's other half stays open after merge: the first dispatch for a named
commit on `main`, setting the new package Public once (GitHub creates a
container package private), and the digest and build time read from that
run.
## 维护者速读
- **改了什么**:新增 `framework-build-base.yml` 和
`docker/framework-build-base/`:按需把 main 上某个框架提交打成公开镜像
`ghcr.io/objectstack-ai/framework-build-base:SHA`,里面是装好依赖、建好全部包和 console
的框架源码树 `/repo/objectstack`。
- **为什么改**:cloud 三个 Dockerfile 现在每次冷缓存或升 pin
都在私有仓计费分钟里从头建框架;这个镜像在公开仓(免费分钟)建一次,cloud 卡 11b(cloud#3792)再改成 `FROM` 它。
- **风险与回滚**:只新增文件,不碰现有 workflow 和运行时镜像;镜像公开,所以构建上下文排除 `.git`、checkout
不留凭据、检查脚本断言镜像里没有 `.git`。回滚 = revert 本 PR,已推的标签留在 ghcr 不影响任何东西。
- **先看**:`.github/workflows/framework-build-base.yml` 的 `publish` 任务(只认
main 上的提交、标签已存在就不重建、匿名读回);`docker/framework-build-base/Dockerfile` 的两个
RUN(参数与 cloud Dockerfile 框架阶段逐项相同);`check-image.sh`。
- **两分钟验证**:打开本 PR 的 `Framework Build Base Image` 检查,作业摘要里有 dry run
的构建耗时和镜像读数(node/pnpm 版本、console 构建标识、镜像大小),什么都没推。
- **需要你做的**:审批本 PR(`.github/workflows/**` 是 Tier H 受管面);合并后的第一次 dispatch
和把 ghcr 包设为 Public(一次,不可撤回)写在 Verification 里。
## What changes
- `.github/workflows/framework-build-base.yml` (new). Three triggers,
none per commit:
- `workflow_dispatch` with `sha` (a full 40-hex commit on `main`) and an
optional `rebuild` boolean;
- `repository_dispatch`, event type `framework-build-base`, with
`client_payload.sha`: the hook a downstream pin move can call later;
- `pull_request` touching the workflow or
`docker/framework-build-base/**`: the dry run. Job `verify` builds the
PR's base commit with the PR's Dockerfile, loads it into the runner,
looks inside, and pushes nothing. Its permissions are `contents: read`
only.
- Job `publish` (the only job with `packages: write`) runs only on a
dispatch from `main` or a `repository_dispatch`. It:
1. refuses a requested commit that is not 40-hex lowercase, or not on
`main` (the compare API must answer `ahead` or `identical`);
2. reads the registry: a tag that already exists reports its digest and
builds nothing, unless `rebuild: true`;
3. checks out the image definition from this run's `main` and the
requested commit as the build context, both with `persist-credentials:
false`;
4. builds `linux/amd64` and pushes
`ghcr.io/objectstack-ai/framework-build-base:SHA` (no provenance or SBOM
manifests, so the tag is one plain image manifest);
5. logs out of ghcr.io and pulls the pushed digest anonymously, then
runs `check-image.sh` on it;
6. writes the commit, tag, digest, build wall seconds, the
anonymous-read outcome and the image readings to the job summary, with a
ready `FROM ...:SHA@DIGEST` line.
- `docker/framework-build-base/Dockerfile` (new). The framework stage of
cloud's `apps/cloud`, `apps/objectos` and `apps/objectos-ee` Dockerfiles
at cloud `ac69aac`, with the same arguments: `node:22-slim`, `python3
make g++ ca-certificates git`, `CI=true`, `OS_SKIP_DTS=1`, corepack
`pnpm@10.31.0`, `PNPM_HOME=/pnpm`, store `/pnpm/store` in a cache mount,
the in-tree authorable-surface anchor assert, `pnpm install
--frozen-lockfile --prefer-offline --prod=false` against
registry.npmjs.org, `turbo run build --filter='!@objectstack/docs'
--filter='!./ui/**' --concurrency=1` at a 12 GB heap, then the typed
Console `turbo run build:dist --filter=@objectstack/console
--concurrency=1` with `VITE_APP_VERSION` = first 8 hex of the commit,
then `rm -rf ui`. The tree lands at `/repo/objectstack`, the path
cloud's `link:` dependencies resolve to.
- `docker/framework-build-base/Dockerfile.dockerignore` (new): `.git`,
`node_modules`, `.pnpm-store`, `.turbo`, the artifact-only list the
three cloud ignore files share.
- `docker/framework-build-base/check-image.sh` (new): runs inside the
image with no network and fails when there is a `.git` anywhere under
`/repo`, the workspace is not pnpm-installed, `spec` or `core` has no
`dist`, the Console `index.html` is missing, `ui/` is still there, no
Console asset carries the commit's 8-hex build identity, or
`OS_SKIP_DTS` is not `1`. It prints node and pnpm versions, counts, and
the image size.
## Why
Cloud pins the framework by commit, usually between releases, and links
it as a workspace by path, so the existing runtime image
(`ghcr.io/objectstack-ai/objectstack`, an npm install of a released CLI)
cannot serve it. Each of cloud's three image builds therefore installs
and builds the whole framework and the Console itself, cold on every pin
move or cache eviction, in a private repository's billed minutes. This
image does that once per commit in this public repository. Cloud card
11b (objectstack-ai/cloud#3792, blocked on this card) switches the three
Dockerfiles to start `FROM` it.
## Tests
- Local gates derived by `node scripts/pm/dispatch-gates.mjs --commands`
at `b91dc73d` for this change set: 35 commands, all exit 0
(closing-keyword parity, self-test wiring, step collectors, workflow
step-name quoting, workflow status functions, required contexts,
stall-guard budget and headroom, pnpm acquisition, node version, NUL
bytes and the rest). `check:pm-dispatch-gates` runs separately; its
result is in the card's report.
- `bash -n` on `check-image.sh`; the workflow parses as YAML with jobs
`verify`, `publish`.
- The real build is this PR's own `verify` run (the `pull_request`
trigger matches this PR's paths). Its build wall and image readings are
in that run's job summary and in the card's report.
## Acceptance notes
- **One Console for all three downstream images.** The self-managed
image built its Console with three `VITE_SENTRY_*` levers (empty DSN,
kill switch off, PII default off); the other two did not. This image
builds with the levers on. On this tree the Console no longer reads any
of the three (client error reporting is runtime configuration,
`ui/packages/app-shell/src/observability/sentry.ts`), so the levers
change no behaviour; they stay so a build-time DSN can never ship
silently in the customer artefact.
- **Registry.** cloud's `apps/cloud` and `apps/objectos` builders set
`npm_config_registry` and `COREPACK_NPM_REGISTRY` to npmmirror as image
ENV, and `apps/objectos` installs the framework from it. This image
installs from registry.npmjs.org and sets no registry ENV. The lockfile
pins integrity, so the installed tree is the same; a downstream stage
that wants the mirror for its own install sets it after `FROM`.
- **`.gitignore`.** Two of the three cloud ignore files also exclude
`**/.gitignore`; the self-managed one does not, and the cloud file
records that the line moves no turbo hash. This image keeps
`.gitignore`, as the self-managed image does.
- **Trust.** `publish`'s `if:` (main, or a repository_dispatch) is the
policy, not a security boundary: anyone who can push a branch could edit
it there. The commit check is what keeps a branch head or a fork's pull
ref out of a published image.
- **Dispatching it.** AGENTS.md Prime Directive 15 reserves dispatching
"any other publish-capable workflow" to the maintainer, and the fleet
relay's `workflow_dispatch` op allows only closed, fixed inputs per
file, so it could not carry a commit anyway. The first dispatch is the
maintainer's click unless a ruling says otherwise. A `workflow_dispatch`
trigger also only exists for this file once it is on `main`, so nothing
could be dispatched from this branch.
- **Budget.** `timeout-minutes: 60` on both jobs, with no reading of
this workflow on record yet; the same stage runs about 20 minutes cold
inside cloud's image builds on the same runner class. The reading from
this PR's `verify` run should replace the note.
## Verification
- This PR's `Framework Build Base Image / Build framework build base
(dry run, no push)` check: job summary shows the build wall and the
readings.
- After merge: Actions, Framework Build Base Image, Run workflow on
`main` with a commit (for example cloud's current `.objectstack-sha`,
`98583cd692c6a67891617529d2f7aec4963859b8`). The summary prints the
digest. Then, logged out: `docker pull
ghcr.io/objectstack-ai/framework-build-base:SHA` succeeds once the
package is Public.
- A digest is obtained from that run's summary, or at any time with
`docker buildx imagetools inspect
ghcr.io/objectstack-ai/framework-build-base:SHA`, or by dispatching the
same commit again (an existing tag reports its digest and builds
nothing).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QUs3BuMyARAhKn8scCUz3t)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d53131b commit e3ab0aa
5 files changed
Lines changed: 539 additions & 2 deletions
File tree
- .github/workflows
- docker/framework-build-base
- scripts/pm
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
0 commit comments