Skip to content

Commit d206d08

Browse files
tooling(pm): .github/workflows/** joins the governed register, scoped to objectstack, objectui and cloud by a per-row repository key (#23048) (#23129)
Fixes #23048 Clause-②: no Ruled B2 (ruling record 6135970276, maintainer 「同意」 2026-10-13T01:17Z, on the decision analysis 6134774406): the governed register gains a per-row repository scope, and `.github/workflows/**` joins it as a Tier H row scoped to `objectstack`, `objectui` and `cloud`. `objectos` and `hotcrm` are not governed by the row. ## What changed **`scripts/pm/check-governed-merges.mjs` — the register and its matchers** - `GOVERNED_SURFACES` rows may carry a `repos` key naming the `GOVERNED_REPOS` ids they govern; a row with none governs every repository (the default). The six existing rows are untouched and keep their tiers. The new row: `{ id: 'workflows', prefix: '.github/workflows/', glob: '.github/workflows/**', tier: GOVERNED_TIER_H, repos: ['objectstack', 'objectui', 'cloud'], what: 'CI, deploy and release workflows: …' }`. - `governedPathsIn(paths, repo)`, `governedTierFor(paths, repo)`, `testVerdict(paths, { repo, … })` and `classifyCommit(commit, paths, repo, size)` REQUIRE the repository and refuse a call without one (`assertGovernedRepo` throws a `TypeError` naming the caller, the ids the register knows and the fix). Never a silent "all": a scoped row answers differently per repository. - New helpers: `repoIdForSlug(slug)`, `surfaceRepos(row)`, `governedSurfacesFor(repo)`, `checkoutRepoFor(origin, mode)`, `checkoutRepoNote`. - Where each CLI mode gets its repository: the sweep iterates `GOVERNED_REPOS` (unchanged); `--pr owner/repo#N` resolves the repository it names, and a slug outside the register is refused (exit 1) rather than answered "not governed"; `--test` and `--branch` answer for the checkout at `--root` — its `origin` resolved to a register id, the same "this checkout's own repository" a bare `--pr N` already means — and print `ℹ️ judged for REPO (SLUG, the checkout at --root)` on stderr. A checkout whose origin is not a governed repository is refused. No `--repo` flag: the closed-argv ruling in the file's header keeps one spelling, and `--root` already exists. - The verdict carries `repo`; `surfacesChecked` counts the rows that govern it; the head line reads `(7 surfaces govern objectstack)` instead of `(6 surfaces, repo-agnostic)`. The register docblock's "repo-agnostic" sentence is rewritten (default every repository, a row may be scoped), and a header section records the ruling. - Self-test: a new battery "the per-row repository scope (ruled B2, 2026-10-13)" (23 cases; roster floor 32 → 33) pins the six default rows, the workflows row's exact scope, scope ⊆ roster, objectstack/objectui/cloud → GOVERNED Tier H, objectos/hotcrm → NOT governed (the default rows still govern them), near misses (`.github/ISSUE_TEMPLATE/**`, `.github/workflowsx/`, `packages/x/.github/workflows/`), `surfacesChecked` 7 vs 6, every front door refusing a missing or unknown repository with the caller named, `repoIdForSlug`, `checkoutRepoFor` in both directions and the head words. The existing register pins move with the row (seven ids in order; Tier H list gains `workflows`). The two CLI fixture repositories gain an `origin` naming objectstack, which is what `--test`/`--branch` now read. **`scripts/pm/check-governed-queue-guard.mjs` — every leg passes its repository** - `guardRepository(env)` resolves the repository the workflow runs in from `GITHUB_REPOSITORY` (unset = this file's own); a repository the register does not know is `EXIT_CANNOT_RUN` with the roster named. `main()` resolves it BEFORE the diff is read and threads it through `liftGeneratedExceptions`, `runGuard` and `runSizeGuard`; the reader's slug is the same reading. - `decomposeGovernedWork(rows, repo)`, `guardVerdict({ repo, … })`, `runGuard({ repo, … })`, `sizeReading(pr, size, repo, approval)`, `sizeGuardVerdict({ repo, … })`, `runSizeGuard({ repo, … })`, `liftGeneratedExceptions(root, base, rows, notes, recompute, repo)` — all refuse a missing repository. Verdict objects carry `repo`; the CLEAR words say `(7 surfaces govern objectstack)`. - Self-test: a new battery "the per-row repository scope (ruling B2, 2026-10-13)" (15 cases; floor 24 → 25): a workflow path in this queue decomposes to one Tier H entry and is REFUSED unapproved / cleared on an authorized approval; the same path in objectui and cloud is governed and in objectos/hotcrm is not; the sibling `.github/` paths are clear; every builder, both orchestrators and the lift reject without a repository; `guardRepository` in all three directions. The source-text pin on the size leg now reads `testVerdict([], { repo, size, approval })`. - The guard has NO `--test` mode (it reads the event payload only); the card's `--test .github/workflows/lint.yml` reading is the merges predicate's, below. **`scripts/pm/check-half-states.mjs` — the patrol binds its swept board** - `loadGovernedRegister(load, slug = OWNER_REPO)` resolves the swept board's register id through `repoIdForSlug` and binds the matcher once (`matcher(paths)` = `governedPathsIn(paths, repo)`), exposing `repo`; a board outside `GOVERNED_REPOS` is `available: false` with the board named (NOT MEASURED on H43/H48), never judged against another repository's rows. The H43/H48 call sites are unchanged. Four new self-test cases drive the loader with explicit boards (cloud → governed, objectos → not, foreign → NOT MEASURED, and this run's binding equals its board). **`scripts/pm/check-governed-prose.mjs`** — demands the globs of the rows that govern `SELF_REPO_ID` (`governedSurfacesFor`), since the instruction files it reads are this repository's; the success line names the repository. **`scripts/pm/record-recognisers.mjs`** — its cross-tool pin drives the guard for this repository (`GUARD_REPO`, read from the register's `SELF_REPO_ID`); two call sites. Not on the claim's declared surface: it is a caller of the changed signature, so it is on the ruling's "(3) every caller passes its repository". ## Prose regions touched (the card's acceptance item) - `AGENTS.md` rule 14, lines 266–268 (the enumeration `check:pm-governed-prose` pins): `.github/workflows/**` is named as a code span; paid by deleting "the file you are reading is one" → "this file is one" and "on a single path hit" → "on one path hit"; "The" moved to the next line. Same eight lines, no line bought. - `AGENTS.md` rule 14, line 279 (the Tier H parenthetical): `(人合: every register row except .claude/**)` — the explicit Tier H list is replaced by its complement so that the register, not this paragraph, holds the tier column (Tier S is already stated as all of `.claude/**` on line 282–283). - `.claude/skills/pm-dispatch/SKILL.md` line 184 「governed 面统一定义」: `.github/workflows/**` appended; paid by removing the spaces around `+` (「(全量)」 kept). 120 bytes, at the cap. - `.claude/skills/pm-dispatch/SKILL.md` line 185 (the adjacent line of the same definition): 「`GOVERNED_REPOS` 各仓同治理待遇」 → 「`GOVERNED_REPOS` 同治理、行可限仓」, so the skill no longer says every repository receives every row; paid by removing the spaces around `+`. 120 bytes. In-place fix under the four conditions (same defect class as the card, mechanical, no other claim holds the file, same gate family). - Line budget: `AGENTS.md` 1116 → 1116 lines; `SKILL.md` 319 → 319 lines (both at their ceilings). Edited-line widths (content bytes): AGENTS.md 266: 117 → 114; 267: 116 → 116; 268: 116 → 120; 279: 117 → 81. SKILL.md 184: 102 → 120; 185: 119 → 120. - Not touched: `references/lanes/skills.md:17`, `references/state-machine.md:9`, `scripts/invoked-as.mjs:28` — their `--test` calls keep working because `--test` answers for the checkout it runs in. `scripts/pm/dispatch-gates*.mjs` (frozen) enumerate no surfaces; no workflow file is edited. ## Acceptance readings (all on bd5ad0e) - `node scripts/pm/check-governed-merges.mjs --pr 23043` → exit 3, `governed-surface predicate: 1 of 1 path(s) hit the register (7 surfaces govern objectstack).` / `⛔ GOVERNED` / `⚖️ landing tier: H(人合)` / `.github/workflows/** ×1`. - `node scripts/pm/check-governed-merges.mjs --test .github/workflows/lint.yml` (from this checkout) → exit 3, GOVERNED, Tier H; stderr `ℹ️ judged for objectstack (objectstack-ai/objectstack, the checkout at --root)`. - Same `--test` with `--root` at a checkout whose origin is `objectstack-ai/objectos` → exit 0, `0 of 1 path(s) hit the register (6 surfaces govern objectos)`, `✅ NOT governed`; at `objectstack-ai/cloud` → exit 3, GOVERNED Tier H, `(7 surfaces govern cloud)`; at `/home/user/objectui` (origin objectstack-ai/objectui) → exit 3, GOVERNED; at a checkout with origin `someone/fork` → exit 1, `❌ --test answers for the repository the checkout at --root belongs to … names someone/fork, which is not a governed repository`. - `governedPathsIn(['.github/workflows/lint.yml'])` with no repository → `TypeError: governedPathsIn wants the repository the paths belong to, as a GOVERNED_REPOS id (objectstack, objectui, cloud, objectos, hotcrm); it got null …` (pinned in the self-test). - `pnpm check:pm-governed-prose` → exit 0: `✓ check-governed-prose: 2 instruction surface(s) name all 7 registered governed surfaces that govern objectstack (docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md · docs/NORTH-STAR.md · .github/workflows/**) and claim no others.` - `node scripts/pm/check-governed-queue-guard.mjs --self-test` → exit 0, 312 cases pass. `pnpm check:pm-governed-merges` → exit 0, 500 assertions. `pnpm check:pm-half-states` → exit 0, 4916 cases. `pnpm check:pm-record-recognisers` → exit 0, 136 cases. `pnpm check:pm-skill-ratchet` → exit 0. ## Gates - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths, the real changeset at bd5ad0e) derives 54 families: the dispatch's 53 plus `pnpm check:pm-record-recognisers`. All 54 ran alone in the foreground, exit codes read from the shell status; all exit 0 (`pnpm --filter @objectstack/lint run check:doc-formula-expressions` first answered exit 3 PREREQUISITE NOT MET — the lint and formula packages were unbuilt — and exit 0 after `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under the shared verification lock; `pnpm check:pm-dispatch-gates` ran via nohup and was waited for: `✓ dispatch-gates self-test (fast tier): 1833 cases pass; 6 slow section(s) deferred`). - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RAN_FILE` (lines annotated `:: exit N`) → `✓ dispatch-gates --ran: 54 derived famil(ies) accounted for — 54 run, 0 NOT-MEASURED (a DERIVED zero — all 54 recorded an exit code and none of them is 3)`. - No published package is touched (repo-root `scripts/pm/**` and governed prose), so `skip-changeset` is the declaration; no changeset. ## Reverse verification Ablation on the committed tree through `scripts/ablation-replace.mjs`: deleting `repos: Object.freeze(['objectstack', 'objectui', 'cloud']), ` from the workflows row (anchor x1 → x0, blob ad718b3 → 46ec4a7ba233) turned the merges self-test red with 5 failures — `the-workflows-row-is-scoped-to-objectstack-objectui-and-cloud-exactly`, `an-objectos-workflow-path-is-NOT-governed`, `a-hotcrm-workflow-path-is-NOT-governed`, `surfacesChecked-counts-the-rows-that-govern-the-repository-not-the-table`, `the-verdict-head-names-the-repository-and-counts-its-rows` (`7 surfaces govern objectos`). Restored by `git checkout HEAD -- …`: blob == HEAD (ad718b3), `git diff HEAD` empty, anchor x1 again. Direction observed: red, as expected. ## 维护者速读(草稿) **改了什么**:治理面登记表每一行可以标注「适用于哪些仓库」,没标的行默认五仓通用(六条旧行一字未动);新增一行把 `.github/workflows/**`(CI、部署、发布工作流)列为 Tier H 受管面,只覆盖 objectstack、objectui、cloud。读登记表的每个工具(落地前判定、队列守卫、半态巡检、规则文本校验)现在都必须说清自己问的是哪个仓,不说就报错,绝不默认「全部」。`AGENTS.md` 规则 14 与 pm-dispatch 技能的「governed 面统一定义」行同行改写,两份文件行数不变。 **为什么改**:您裁的「两仓加治理行」要求两个公开仓的工作流改动先等授权批准;原登记表对五仓一视同仁,直接加行会把 objectos、hotcrm 也管进去,而那两个仓没人裁过。B2 让表上写的恰好等于裁定的范围,cloud 同列是因为 cloud 自己的治理表已有同一行,中央审计与之一致。 **风险与代价(含回滚)**:工作流 PR 从此在三仓都要等授权批准(本轮十个 CI PR 实际已如此),objectos、hotcrm 照旧绿灯即落。工具内部签名加了必填的仓参数,漏传即响亮报错而非静默判错;五个脚本的自测与 54 个门禁族全绿,反向消融证实自测能变红。回滚 = revert 本 PR 一个 commit 序列;登记表无持久化状态。 **席位意见**:(留空,由席位定稿) **你要做的**:本 PR 含 `AGENTS.md`(Tier H)与 `.claude/**`(Tier S),保持 draft;请以 `GOVERNED_APPROVERS` 账号给出一次 APPROVED review,之后由认领席位落地。回一个字即可。 ## Acceptance notes - The queue guard has no `--test` flag (confirmed: it reads `GITHUB_EVENT_PATH` and exits 1 without it); the card's `--test` reading belongs to `check-governed-merges.mjs`, where it answers GOVERNED, and the guard's own battery pins the workflow case in its queue. - The PM's suggested `--repo ID` override on `--test`/`--branch` was not taken: the file's closed-argv section records a ruling that there is no `--repo` (one spelling, `--pr owner/repo#N`), and `--root` already names the checkout. The checkout's origin is the repository, printed on stderr. - `scripts/pm/record-recognisers.mjs` was edited beyond the claim's declared surface (two call sites of the changed signature); declared here and in the report. - Observation, not filed: `.github/workflows/governed-surface-guard.yml` has no `paths:` filter by design (its header says so), so no workflow edit was needed; `dispatch-gates.mjs` mentions a register row only in a comment. `check-governed-merges.mjs`'s `parsePullTarget` still accepts any `owner/repo#N`; `--pr` now refuses a slug outside the register before any read. - Harness note: the harness-written `Co-Authored-By` trailer carrying a model name was refused by the pre-push hook on the first push; the commits carry the repository's model-free pair. --- _Generated by [Claude Code](https://claude.ai/code/session_012dGTuJen61ECNHXYXKChoC)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8807206 commit d206d08

7 files changed

Lines changed: 603 additions & 218 deletions

File tree

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
181181

182182
- **ACCEPT 之后的路径分叉**:翻 ready / 挂 auto-merge / 入队前先取一次 PR 的路径面。
183183
- 路径面用 `get_files` 取,⛔ 不看报告自述;动手之前先分,不是事后对照。
184-
- governed 面统一定义:`docs/adr/**` + `.claude/**`(全量)+ `skills/**` + `docs/NORTH-STAR.md`。
185-
- governed 面同含 `AGENTS.md` + `CLAUDE.md`;`GOVERNED_REPOS` 各仓同治理待遇,执行席恒随落地仓车道。
184+
- governed 面统一定义:`docs/adr/**`+`.claude/**`(全量)+`skills/**`+`docs/NORTH-STAR.md`+`.github/workflows/**`。
185+
- governed 面同含 `AGENTS.md`+`CLAUDE.md`;`GOVERNED_REPOS` 同治理、行可限仓,执行席恒随落地仓车道。
186186
- 路径面命中 Tier H ⇒ ACCEPT 换终局四件套,混合 diff ⛔ 不按比例判;要拆让 dev 单独开 PR。
187187
- 改动 >3000 行(含生成物)同换终局四件套,⛔ 无 Tier S 例外;读数 = PR additions+deletions。
188188
- ① 复核结论照常写在 issue 上;技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席。

‎AGENTS.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,9 @@ localStorage / auth gotchas.
263263
14. **⛔ A governed surface lands only the way its tier allows — Tier H by the maintainer's hand or by an authorized
264264
approval, Tier S by the owning seat on a contract-tier review of record; before that record no AI seat merges,
265265
queues, or arms auto-merge on a PR whose diff touches one.** The governed surfaces are `docs/adr/**`,
266-
`docs/NORTH-STAR.md`, `.claude/**` (agents, hooks and settings — not only skills), `skills/**`, `AGENTS.md` and
267-
`CLAUDE.md` — the file you are reading is one — and a mixed diff is governed whole on a single path hit. The
268-
register is the `GOVERNED_SURFACES` table in `scripts/pm/check-governed-merges.mjs`, each row carrying its tier;
266+
`docs/NORTH-STAR.md`, `.claude/**` (agents, hooks and settings — not only skills), `skills/**`, `AGENTS.md`,
267+
`CLAUDE.md` — this file is one — and `.github/workflows/**`; a mixed diff is governed whole on one path hit.
268+
The register is the `GOVERNED_SURFACES` table in `scripts/pm/check-governed-merges.mjs`, each row carrying its tier;
269269
adding a surface is an edit *there*, never here, and `pnpm check:pm-governed-prose` reds per-PR when this
270270
paragraph names fewer surfaces than the register — or more. When it reds, name the surface here. A fork PR
271271
(head repo ≠ base repo) is a proposal, never a delivery, whatever its paths: no AI seat readies, queues, arms
@@ -276,7 +276,7 @@ localStorage / auth gotchas.
276276
auto-merge, ⛔ never flip it out of draft to make any of those possible — judged on the PR's **file list**, not
277277
its description; a **mixed diff is not a proportion question**, one path hit is enough; to land the rest, split off
278278
the governed files. **The landing is tiered by the register; one Tier H path makes the whole PR Tier H.** **Tier H**
279-
(人合: `docs/adr/**`, `docs/NORTH-STAR.md`, `skills/**`, `AGENTS.md`, `CLAUDE.md`): those four lift only for an
279+
(人合: every register row except `.claude/**`): those four lift only for an
280280
authorized APPROVED review by an account in `GOVERNED_APPROVERS`, on ANY commit and not dismissed; that word is
281281
spent once per PR — the OWNING seat then lands it, later pushes included; the director seat requests the word as
282282
ONE batch of at most five rows, and the click stays the maintainer's. **Tier S** (席内达档复核落地: all of

0 commit comments

Comments
 (0)