Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
- 记录须点名被改 note、逐句判改写句,缺一不算;算即按 SKILL.md 三条件带红入队,门禁不改。
- 真正设计分叉照旧进决策箱,席内复核 ⛔ 不替代维护者裁定。
- 外部评审链降为可选事后审计,非放行前提。
- 契约复核的适用面、资格与归属见 `references/contract-review.md`。
- 默认分支 push 即触发对外部署的仓,只在验证层存在时才合并;无验证层 ⛔ 不合并。
- 验证层 = 部署以 CI 为闸、发布后探测线上面、失败自动回滚并立卡。
- 该类仓的落地判据是已发布且探测通过,⛔ 不是 MERGED;跟到发布为止。
Expand Down Expand Up @@ -81,6 +80,7 @@
- main-red 约定①:p0 fix-forward 允许跳队,限 p0、机械、根因已核实的止血 PR。
- 也可按既有 governed 例外由维护者人工直合一行修复;仅限 main-red 修复。
- main-red 约定②:一个失败 check 只锚一张卡,先立者赢,后见者评论到锚卡,⛔ 不另立。
- main-red 约定③:main 全量电池红是所属车道首要工作;红未消,该车道 PR ⛔ 不挂队。

## C. 依赖前棒才能转绿的 PR:draft 停放 + 签名级预期红清单

Expand Down
6 changes: 3 additions & 3 deletions .claude/skills/pm-dispatch/references/platform-readings.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,9 +387,9 @@
- 本车道强制多是过宽的回忆:`dispatch-gates.mjs --tier PATH` 逐路径现推,路径线是下限非放行。
- 该脚本只答自己那棵树:姊妹仓路径回 absent from this tree,姊妹仓的档位与条款②只能手推。
- `os-verify-lock.sh` 只住 objectstack:objectui 无它,跑本仓副本读的是容器级锁,非该仓深度。
- required checks 的名单是每仓事实,objectstack 七个:
`TypeScript Type Check` · `Lint & Repo Gates` · `Test Core` · `Dogfood Regression Gate` ·
`Build Core` · `Temporal Conformance (live PG + MySQL)` · `Governed Surface Queue Guard`。
- required checks 的名单是每仓事实,objectstack 五个:
`TypeScript Type Check` · `Lint & Repo Gates` · `Test Core` ·
`Build Core` · `Governed Surface Queue Guard`。
- ⭐ 本表的 `mustName` 声明 `statesTheSet` ⇒ 必须逐字等于注册表,加注册行不跟改本表即红。
- ⇒ 门绿即名单逐字齐全的读数;⛔ 但数量词未钉:实测 七→六 照绿,只能手跟改。
- `in_progress` 不是过;advisory 门禁红进 main 是共享损伤,照样止血立单。
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/pm-dispatch/references/triage-duties.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
- 线程有 `Ruling:`/RULED 的卡再入箱,该行逐 id 各一句变化;缺行 ⇒ 拒入,`pm:retriage` 回立卡者。
- `type`(Bug/Feature/Task)是分诊固定产出,分诊席唯一权威生产;立单者可预填,分诊校正。
- 判据:违背已声明契约 ⇒ `Bug`,扩大接受集/公开面越出已声明 ⇒ `Feature`,其余 ⇒ `Task`。
- `Bug` 无具名落点或复现路径不入 `pm:queue`(标记补复现)。
- ⛔ 不回填存量 backlog 的 type;新卡即时打、存量卡下次碰到补。
- 定级按北极星「优先级」1–3 条;首行 `Path: <定义项> | <清单项 id/缺项> | P<路步> | none`。
- 定义项取北极星「做出来的是什么」或仪器卡的车队决定;无则关;`none` 非分诊缺口。
Expand Down Expand Up @@ -96,7 +95,8 @@
- 代裁人工地板(恒交维护者):功能新增;ADR;协议/公开契约变化;破坏性或难回滚动作。
- 人工地板同含:安全/权限边界;门禁削弱(降阈/删必查/抬上限/跳测);新增运行时第三方依赖;
- 花费/配额/舰队形态/默认模型档位;新增必需门禁/hook/棘轮(卡具名所守契约与预期捕获)。
- 唯一例外:`platform-readings.md` 增量抬上限到落地行数,免决策卡,记 `ruledRaises` 引常设裁决。
- 例外一:`platform-readings.md` 增量抬上限到落地行数,免决策卡,记 `ruledRaises` 引常设裁决。
- 例外二:一小时内两次红未触包的门禁,任一席位先翻 report-only,裁定随后。
- 条件:席位验收评论逐条核实、去重计数(候选/落地/已有/拒收)、一事一行、不计重排。
- 代裁车道只覆盖代码整理(行为不变的重构)与 bug 修复。
- 机械边界测试:改动扩大接受集或公开面 ⇒ 人工;拉回已声明契约 ⇒ 代裁车道。
Expand Down
82 changes: 56 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,28 @@ concurrency:
group: ci-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

# ── THE TWO SPEEDS (#22998) ──────────────────────────────────────────────────
# A `pull_request` or `merge_group` run carries the FAST SET: `Build Core`,
# `Test Core` (affected-only) and `Build Docs`. `main`'s hourly `schedule` run,
# its `push` runs and an on-demand `workflow_dispatch` carry the FULL BATTERY:
# the fast set plus the four jobs below that spell
#
# github.event_name != 'pull_request' && github.event_name != 'merge_group'
#
# in their `if:` — `temporal-conformance`, `dogfood` (three shards),
# `dogfood-verify` and `console-pin`. Nothing is deleted: the jobs moved off the
# per-PR path. A red in one of them is a main-red for the owning lane, and the
# publish lane reads the full battery before its approval (release.yml's
# `full-battery` job, `scripts/ci/scheduled-full-run.mjs --release-gate`).
#
# Spelled as a DENY-list of the two per-PR events on purpose — the filter
# contract's half 1 below, applied to events: a trigger added to `on:` later
# RUNS these jobs, and the failure direction of a wrong guess is a slower PR,
# never a full battery that silently stopped running. `dogfood-gate` keeps
# `if: always()` (check:shard-attestation pins it): with both legs skipped it
# counts zero expected attestations and concludes success, exactly as on a
# filter-skipped diff.

jobs:
filter:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -276,9 +298,10 @@ jobs:
# map that objectui's OBJECTSTACK_SPEC_DIST hook and the probe derivation
# both resolve entry by entry, and the tsup entry list that decides which
# built file each entry points at. A head that moves them runs this gate
# before it enqueues. #20695 moved the migration registries to a new
# entry, skipped this job on every push, and went red first in the merge
# queue, where a check outside the required set cannot stop a merge.
# on the push that lands it (the job is full-battery only since #22998;
# see THE TWO SPEEDS above). #20695 moved the migration registries to a
# new entry, skipped this job on every push, and went red first in the
# merge queue, where a check outside the required set cannot stop a merge.
# ⛔ Not packages/spec/**: the rest of the spec is bundled CONTENT, and
# its lag behind a cached dist is the ruled cache design (#9667, #9706;
# see scripts/check-console-injection.mjs).
Expand Down Expand Up @@ -1564,15 +1587,16 @@ jobs:
# The job also carries the non-SQL half of that axis (core, formula,
# driver-memory, driver-mongodb, service-analytics) — the other backends the
# temporal conformance matrix holds to one standard. Its NAME still says
# "live PG + MySQL" on purpose: the name IS the required check, so renaming
# it would silently drop the gate wherever branch protection lists it — the
# same trap the dogfood shards note below. `check:required-contexts` pins
# this literal, parenthetical and all (#6865).
# "live PG + MySQL" on purpose: it was a required check until #22998 moved
# the job to the full battery, and its readers (the hourly run's red card,
# merge-queue triage history, every instruction that names it) find it by
# that literal — renaming it is a contract change, not a tidy-up.
temporal-conformance:
name: Temporal Conformance (live PG + MySQL)
needs: filter
# See THE FILTER CONTRACT on the filter job's outputs (#4928).
if: ${{ !cancelled() && needs.filter.outputs.core != 'false' }}
# See THE FILTER CONTRACT on the filter job's outputs (#4928). Full battery
# only: see THE TWO SPEEDS above `jobs:` (#22998).
if: ${{ !cancelled() && needs.filter.outputs.core != 'false' && github.event_name != 'pull_request' && github.event_name != 'merge_group' }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
Expand Down Expand Up @@ -1904,13 +1928,14 @@ jobs:
# own in-process app; a single 4-vCPU runner needed ~7½ minutes for the
# lot, and at 2 shards each half still ran ~7 min — the longest pole left
# once Test Core went 3-way (#4859). vitest partitions the file list
# deterministically across shards; all shards must pass. Branch protection
# requires only the bare "Dogfood Regression Gate" context, carried by the
# dogfood-gate job below — the shard count can change without touching it.
# deterministically across shards; all shards must pass. The bare
# "Dogfood Regression Gate" context is carried by the dogfood-gate job
# below — the shard count can change without touching it.
name: Dogfood Regression Gate (${{ matrix.shard }}/3)
needs: filter
# See THE FILTER CONTRACT on the filter job's outputs (#4928).
if: ${{ !cancelled() && needs.filter.outputs.core != 'false' }}
# See THE FILTER CONTRACT on the filter job's outputs (#4928). Full battery
# only: see THE TWO SPEEDS above `jobs:` (#22998).
if: ${{ !cancelled() && needs.filter.outputs.core != 'false' && github.event_name != 'pull_request' && github.event_name != 'merge_group' }}
runs-on: ubuntu-latest
# Backstop only — the stall guard on the test step is the primary detector
# for a #4250-style hang (see Test Core). 30 min is ~6× a shard (~5 min;
Expand Down Expand Up @@ -2135,8 +2160,9 @@ jobs:
dogfood-verify:
name: Dogfood Verify CLI
needs: filter
# See THE FILTER CONTRACT on the filter job's outputs (#4928).
if: ${{ !cancelled() && needs.filter.outputs.core != 'false' }}
# See THE FILTER CONTRACT on the filter job's outputs (#4928). Full battery
# only: see THE TWO SPEEDS above `jobs:` (#22998).
if: ${{ !cancelled() && needs.filter.outputs.core != 'false' && github.event_name != 'pull_request' && github.event_name != 'merge_group' }}
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
Expand Down Expand Up @@ -2225,22 +2251,25 @@ jobs:
overwrite: true

dogfood-gate:
# Stable required-check name for a SHARDED job (#3622 follow-up).
# Stable check name for a SHARDED job (#3622 follow-up). Since #22998 its
# legs run in the full battery only (THE TWO SPEEDS above `jobs:`) and the
# context leaves the required set; on a PR or queue build both legs skip
# and this gate counts zero expected attestations. The history below is
# why the name and the `if: always()` stay.
#
# Branch protection requires the context "Dogfood Regression Gate". Once
# Branch protection required the context "Dogfood Regression Gate". Once
# the job became a 2-way matrix its checks publish as "Dogfood Regression
# Gate (1/2)" / "(2/2)" — the bare context could never appear again, so
# EVERY pull request in the repo sat permanently BLOCKED (mergeable, all
# checks green, merge button dead). #3622's own comment called for updating
# branch protection; keeping the contract HERE instead means a future
# shard-count change cannot deadlock the repo a second time — and since
# #6865 the bare name is pinned by `check:required-contexts` rather than
# only described here, so a rename fails in the ESLint job instead of in
# the queue.
# shard-count change cannot deadlock the repo a second time. From #6865
# until #22998 the bare name was pinned by `check:required-contexts`; it
# left that registry with the required set, so a rename now falsifies the
# prose that names it without reddening anything — treat it as contract.
#
# Also aggregates dogfood-verify (the CLI pass that used to ride shard 1),
# so the one required context still covers everything it covered before
# the split.
# so the one context still covers everything it covered before the split.
#
# `if: always()` + attestation counting so a legitimately skipped matrix
# (the `filter` job says no core paths changed) still satisfies the gate —
Expand Down Expand Up @@ -2784,8 +2813,9 @@ jobs:
name: Console Pin Gate
needs: filter
# See THE FILTER CONTRACT on the filter job's outputs (#4928). `console`,
# not `core` — each job keeps its own filter output.
if: ${{ !cancelled() && needs.filter.outputs.console != 'false' }}
# not `core` — each job keeps its own filter output. Full battery only: see
# THE TWO SPEEDS above `jobs:` (#22998).
if: ${{ !cancelled() && needs.filter.outputs.console != 'false' && github.event_name != 'pull_request' && github.event_name != 'merge_group' }}
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
Expand Down
73 changes: 71 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ name: Release
# still waiting for a version that is
# already on npm. Mints nothing; never
# touches a job that has started.
# → `full-battery` (#22998) reads the newest full-battery
# runs of CI and Lint & Type Check at
# or before the version commit;
# `publish` needs it, so a red battery
# queues no approval prompt at all.
# workflow_dispatch → `publish` the repair lane. Takes no version;
# (WITHOUT audits main the same way, minus the
# `refresh_version_pr`) push range (a dispatch is one human
Expand Down Expand Up @@ -981,7 +986,7 @@ jobs:
# below reads this output, and IT stops dead on the `release`
# environment until a required reviewer approves it.
echo "publish-pending=true" >> "$GITHUB_OUTPUT"
echo "::notice::main carries @objectstack/cli@${version}, which is NOT on npm; this ${EVENT} queues its publish on the version commit ${version_commit}. A deployment is queued and is waiting for a maintainer to approve the 'release' environment."
echo "::notice::main carries @objectstack/cli@${version}, which is NOT on npm; this ${EVENT} queues its publish on the version commit ${version_commit}. Once the full-battery job reads the battery at or before that commit green, a deployment is queued and waits for a maintainer to approve the 'release' environment."
{
echo "## Release ${version} is waiting for your approval"
echo
Expand All @@ -991,6 +996,8 @@ jobs:
echo "The **Publish ${version} to npm** job below is held at the \`release\`"
echo "environment gate. Nothing has been checked out, built or published —"
echo "GitHub holds the whole job until a required reviewer approves it."
echo "It starts only after **Full battery green at the version commit** finds"
echo "the newest full-battery runs at or before \`${version_commit}\` green."
echo
echo "**Review this before approving:** approving publishes the tree of the"
echo "version commit \`${version_commit}\` — the commit that brought main to"
Expand Down Expand Up @@ -1364,6 +1371,65 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/release-pending-publish.mjs sweep --workflow release.yml

# ══════════════════════════════════════════════════════════════════════════
# PUSH LANE 4 — the full battery, read before the approval exists (#22998).
# Reads; never publishes, never approves, never cancels.
# ══════════════════════════════════════════════════════════════════════════
full-battery:
name: Full battery green at the version commit
# A PR or queue build carries ci.yml's FAST SET since #22998; the dogfood,
# temporal and console-pin legs run only on main's push, hourly and
# dispatch runs. So "tested before a release" is read HERE, as a gate: the
# newest completed `schedule` or `push` runs of `CI` and `Lint & Type
# Check` whose head is the version commit or a first-parent ancestor of it
# must both have concluded `success` (a `cancelled` run carries no
# verdict). The selection rule and its pins live in
# scripts/ci/scheduled-full-run.mjs (HALF 3), whose --self-test lint.yml
# runs; this job only calls it.
#
# `publish` needs this job and its `if:` opens with `success()`, so a
# refusal here skips `publish` before any deployment is queued: no
# approval prompt appears for a version whose battery is red. Recovery is
# the ordinary one — fix or re-run the red run at or before the version
# commit, then the repair dispatch re-audits and queues the publish.
#
# Same predicate as `publish`, verbatim, so the read happens exactly when
# a publish would be queued and on no other event; a skipped gate reads as
# a skipped `needs` to `publish`, whose `success()` is then false — the
# same fail-closed direction.
needs: [release-integrity]
if: >-
success() &&
(needs.release-integrity.outputs.publish-pending == 'true' ||
(github.event_name == 'workflow_dispatch' && inputs.force &&
!inputs.refresh_version_pr))
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
# `actions: read` is the run listings of ci.yml and lint.yml; `contents:
# read` is the checkout.
actions: read
contents: read
steps:
# `fetch-depth: 0`: "at or before the version commit" is a first-parent
# walk from it, and the script refuses a shallow clone rather than read a
# window that stops at the graft.
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '22'

- name: Read the full battery at or before the version commit
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION_COMMIT: ${{ needs.release-integrity.outputs.version-commit }}
run: node scripts/ci/scheduled-full-run.mjs --release-gate --release-commit "$VERSION_COMMIT"

# ══════════════════════════════════════════════════════════════════════════
# HUMAN LANE — the ONLY job in this repository that publishes.
# ══════════════════════════════════════════════════════════════════════════
Expand All @@ -1376,7 +1442,10 @@ jobs:
# version in that failure's log was @object-ui/console@17.5.0, the vendored
# objectui build, while the repo was publishing 17.1.0.
name: Publish ${{ needs.release-integrity.outputs.cli-version }} to npm (awaiting approval)
needs: [release-integrity]
# `full-battery` (#22998): the publish lane refuses, before the approval
# prompt exists, a version whose full battery at or before the version
# commit is not green. `success()` below is what makes that refusal bind.
needs: [release-integrity, full-battery]
# ⛔ THE barrier is now the environment, not the trigger (ADR-0125 D3).
#
# An environment with NO protection rules passes AUTOMATICALLY and
Expand Down
Loading
Loading