diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index 08b8dfdf585..1ed96e09bda 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -135,9 +135,8 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报 - `Blocked-by:` 行是机器可 grep 的反向索引,一遍读喂三个职责,⛔ 只做第一件。 - 三职责:上游关单放回解锁卡;按解锁扇出排序;在合并后的 ref 上重验回队卡文件面。 - 一个标签存在当且仅当有具名读者;`pm:blocking` 即其推导缓存,读者以词表脚本注为权威。 -- 跨仓解锁判据是消费方可安装,⛔ 不是上游已合并。 +- 跨仓解锁判据是消费方可安装,⛔ 不是上游已合并;探针:装上的包是否接受新键/新行为。 - 以发布包消费上游的仓,解锁放回前先验修复已进可安装的发行版。 -- 安装面探针:装上的包是否接受新键/新行为。 - 未发版 ⇒ 转 `pm:on-hold` + `Restart-when:` 加消费方安装面判据,⛔ 不回 `pm:queue`。 - 该 hold 评论预写唤醒后的派发形状;pin 消费的仓不适用:pin 移动即可安装,走 pin 滞后读数。 - 状态变更不过夜:标签无评论、assignee 无认领评论、结论只在 chat,都是半状态。 @@ -183,6 +182,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报 - 路径面用 `get_files` 取,⛔ 不看报告自述;动手之前先分,不是事后对照。 - governed 面统一定义:`docs/adr/**`+`.claude/**`(全量)+`skills/**`+`docs/NORTH-STAR.md`+`.github/workflows/**`。 - governed 面同含 `AGENTS.md`+`CLAUDE.md`;`GOVERNED_REPOS` 同治理、行可限仓,执行席恒随落地仓车道。 +- 另含 `ui/docs/adr/**`+`ui/.claude/**`+`ui/skills/**`+`ui/AGENTS.md`+`ui/CLAUDE.md`+`ui/.github/workflows/**`。 - 路径面命中 Tier H ⇒ ACCEPT 换终局四件套,混合 diff ⛔ 不按比例判;要拆让 dev 单独开 PR。 - 改动 >3000 行(含生成物)同换终局四件套,⛔ 无 Tier S 例外;读数 = PR additions+deletions。 - ① 席位记录一条落 PR,两层同欠:Tier H 置顶速读五段,其下 `## 席位核对`;卡只留指针行。 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 15d3fffd682..7b2f7b4d7b8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -46,6 +46,15 @@ /docs/NORTH-STAR.md @os-zhuang @hotlong /.github/workflows/ @os-zhuang @hotlong +# Tier H governed surfaces of the nested `ui/` workspace (#25712) — the same +# register rows, under `ui/`; `ui/.claude/` is Tier H, unlike the root tree +/ui/docs/adr/ @os-zhuang @hotlong +/ui/.claude/ @os-zhuang @hotlong +/ui/skills/ @os-zhuang @hotlong +/ui/AGENTS.md @os-zhuang @hotlong +/ui/CLAUDE.md @os-zhuang @hotlong +/ui/.github/workflows/ @os-zhuang @hotlong + # The enforcement chain /.github/CODEOWNERS @os-zhuang @hotlong /scripts/pm/check-governed-merges.mjs @os-zhuang @hotlong diff --git a/AGENTS.md b/AGENTS.md index 42da7cb4065..236b9f88845 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -265,12 +265,12 @@ localStorage / auth gotchas. approval, Tier S by the owning seat on a contract-tier review of record; before that record no AI seat merges, queues, or arms auto-merge on a PR whose diff touches one.** The governed surfaces are `docs/adr/**`, `docs/NORTH-STAR.md`, `.claude/**` (agents, hooks and settings — not only skills), `skills/**`, `AGENTS.md`, - `CLAUDE.md` — this file is one — and `.github/workflows/**`; a mixed diff is governed whole on one path hit. - The register is the `GOVERNED_SURFACES` table in `scripts/pm/check-governed-merges.mjs`, each row carrying its tier; - adding a surface is an edit *there*, never here, and `pnpm check:pm-governed-prose` reds per-PR when this - paragraph names fewer surfaces than the register — or more. When it reds, name the surface here. A fork PR - (head repo ≠ base repo) is a proposal, never a delivery, whatever its paths: no AI seat readies, queues, arms - auto-merge on or approves it; the owning seat adopts the diff onto an internal branch and lands that. + `CLAUDE.md` and `.github/workflows/**`, and under `ui/` `ui/docs/adr/**`, `ui/.claude/**`, `ui/skills/**`, + `ui/AGENTS.md`, `ui/CLAUDE.md` and `ui/.github/workflows/**`, all Tier H. The register is `GOVERNED_SURFACES` in + `scripts/pm/check-governed-merges.mjs`, each row carrying its tier; adding a surface is an edit *there*, never here, + and `pnpm check:pm-governed-prose` reds per-PR when this paragraph names fewer surfaces than the register — or + more. A fork PR (head repo ≠ base repo) is a proposal, never a delivery, whatever its paths: no AI seat readies, + queues, arms auto-merge on or approves it; the owning seat adopts the diff onto an internal branch and lands that. **Authoring stays open to every seat** — drafting, pushing, opening and revising the PR. What is reserved is the **landing**: on a PR whose diff touches a governed surface ⛔ never merge, ⛔ never queue, ⛔ never arm diff --git a/scripts/pm/check-governed-merges.mjs b/scripts/pm/check-governed-merges.mjs index e4ab4a832f4..bf60b5dc3d4 100644 --- a/scripts/pm/check-governed-merges.mjs +++ b/scripts/pm/check-governed-merges.mjs @@ -1036,7 +1036,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ '⭐ the FORK predicate: head repo ≠ base repo is a proposal, never a delivery': 6, '⭐ the argv is CLOSED: a flag the mode does not read, or a second repository beside a bare --pr, is refused': 22, 'the per-row repository scope (ruled B2, 2026-10-13)': 20, - 'the nested `ui/` workspace rows, through a register handed in (#25712)': 15, + 'the nested `ui/` workspace rows, on the register (#25712)': 16, }); // DELETING an entry silences that battery's floor exactly as effectively as @@ -1187,6 +1187,15 @@ export const HUMAN_MERGE_LINE_THRESHOLD = 3000; * The `claude-tree` row's Tier S holds in objectstack alone: the tier is the * landing the repository's own queue guard enforces, and only objectstack's * has a Tier S arm (#25199). + * + * The six `ui-*` rows are objectui's own register, carried under `ui/` now + * that objectui's tree is this repository's `ui/` workspace (#25677, #25712): + * every root row but the North Star, prefixed, scoped to objectstack (no other + * repository has a `ui/` tree), and all Tier H — `ui/.claude/**` included, + * which stays Tier H until it folds into the root `.claude/**` (maintainer + * ruling 2026-10-25, director item 16). A root row never reaches into `ui/` + * and a `ui-*` row never reaches the root: prefixes and exact paths both + * anchor at the repository root. */ export const GOVERNED_SURFACES = Object.freeze([ Object.freeze({ id: 'adr', prefix: 'docs/adr/', glob: 'docs/adr/**', tier: GOVERNED_TIER_H, what: 'architecture decision records' }), @@ -1196,6 +1205,12 @@ export const GOVERNED_SURFACES = Object.freeze([ Object.freeze({ id: 'claude-md', exact: 'CLAUDE.md', glob: 'CLAUDE.md', tier: GOVERNED_TIER_H, what: 'the repo-root Claude instruction file' }), Object.freeze({ id: 'north-star', exact: 'docs/NORTH-STAR.md', glob: 'docs/NORTH-STAR.md', tier: GOVERNED_TIER_H, what: "the maintainer's North Star — the law above AGENTS.md" }), Object.freeze({ id: 'workflows', prefix: '.github/workflows/', glob: '.github/workflows/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack', 'objectui', 'cloud']), what: 'CI, deploy and release workflows: what runs on every push, what deploys, what gates the queue' }), + Object.freeze({ id: 'ui-adr', prefix: 'ui/docs/adr/', glob: 'ui/docs/adr/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's architecture decision records" }), + Object.freeze({ id: 'ui-claude-tree', prefix: 'ui/.claude/', glob: 'ui/.claude/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's agent instruction tree" }), + Object.freeze({ id: 'ui-skills-catalog', prefix: 'ui/skills/', glob: 'ui/skills/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's published skills catalog" }), + Object.freeze({ id: 'ui-agents-md', exact: 'ui/AGENTS.md', glob: 'ui/AGENTS.md', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's agent instruction file" }), + Object.freeze({ id: 'ui-claude-md', exact: 'ui/CLAUDE.md', glob: 'ui/CLAUDE.md', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's Claude instruction file" }), + Object.freeze({ id: 'ui-workflows', prefix: 'ui/.github/workflows/', glob: 'ui/.github/workflows/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's workflow files" }), ]); /** @@ -4411,7 +4426,7 @@ async function selfTest() { // ── the governed predicate: the 2026-08-18 unified list, exactly ────────── battery('the governed predicate: the 2026-08-18 unified list, exactly'); const ids = (paths) => governedPathsIn(paths, REPO).map((s) => s.id); - assert('all-seven-surfaces-declared-in-order', GOVERNED_SURFACES.map((s) => s.id).join(',') === 'adr,claude-tree,skills-catalog,agents-md,claude-md,north-star,workflows', GOVERNED_SURFACES.map((s) => s.id).join(',')); + assert('all-thirteen-surfaces-declared-in-order', GOVERNED_SURFACES.map((s) => s.id).join(',') === 'adr,claude-tree,skills-catalog,agents-md,claude-md,north-star,workflows,ui-adr,ui-claude-tree,ui-skills-catalog,ui-agents-md,ui-claude-md,ui-workflows', GOVERNED_SURFACES.map((s) => s.id).join(',')); assert('adr-prefix', ids(['docs/adr/0001-x.md']).join() === 'adr'); assert('whole-claude-tree-not-only-skills', ids(['.claude/hooks/guard-main-checkout.sh', '.claude/agents/os-dev.md', '.claude/settings.json']).join() === 'claude-tree'); assert('published-skills-catalog-is-governed', ids(['skills/objectstack-ui/SKILL.md']).join() === 'skills-catalog'); @@ -4434,7 +4449,7 @@ async function selfTest() { battery('the per-row repository scope (ruled B2, 2026-10-13)'); const everyRepo = GOVERNED_REPOS.map((r) => r.id); const workflowsRow = GOVERNED_SURFACES.find((s) => s.id === 'workflows'); - assert('the-six-pre-existing-rows-declare-no-scope-and-so-govern-every-repository', GOVERNED_SURFACES.filter((s) => s.id !== 'workflows').every((s) => s.repos === undefined && surfaceRepos(s).join() === everyRepo.join()), JSON.stringify(GOVERNED_SURFACES.map((s) => [s.id, s.repos ?? null]))); + assert('the-six-pre-existing-rows-declare-no-scope-and-so-govern-every-repository', GOVERNED_SURFACES.filter((s) => s.id !== 'workflows' && !s.id.startsWith('ui-')).every((s) => s.repos === undefined && surfaceRepos(s).join() === everyRepo.join()), JSON.stringify(GOVERNED_SURFACES.map((s) => [s.id, s.repos ?? null]))); assert('the-workflows-row-is-scoped-to-objectstack-objectui-and-cloud-exactly', surfaceRepos(workflowsRow).join() === 'objectstack,objectui,cloud' && workflowsRow.tier === GOVERNED_TIER_H && workflowsRow.glob === '.github/workflows/**', JSON.stringify(workflowsRow)); assert('every-declared-scope-is-a-non-empty-duplicate-free-subset-of-GOVERNED_REPOS-ids', GOVERNED_SURFACES.every((s) => s.repos === undefined || (Array.isArray(s.repos) && s.repos.length > 0 && new Set(s.repos).size === s.repos.length && s.repos.every((id) => everyRepo.includes(id)))), JSON.stringify(GOVERNED_SURFACES.map((s) => [s.id, s.repos ?? null]))); assert('the-workflows-row-is-a-prefix-row-with-a-separator-so-it-reaches-dispatch-gates-as-written-and-declares-no-hint', workflowsRow.prefix === '.github/workflows/' && !ROOT_FILE_WATCH_HINTS.some((h) => h.startsWith('.github/'))); @@ -4446,7 +4461,7 @@ async function selfTest() { assert('a-hotcrm-workflow-path-is-NOT-governed', governedPathsIn([wf], 'hotcrm').length === 0 && testVerdict([wf], { repo: 'hotcrm' }).governed === false); assert('the-default-rows-still-govern-objectos-and-hotcrm-the-scope-narrows-one-row-not-the-register', ['objectos', 'hotcrm'].every((r) => governedPathsIn(['AGENTS.md', 'docs/adr/0001-x.md', '.claude/agents/os-dev.md'], r).map((s) => s.id).join() === 'adr,claude-tree,agents-md')); assert('the-near-misses-for-the-workflows-row-stay-out', ids(['.github/ISSUE_TEMPLATE/bug.md', '.github/workflows.bak/x.yml', '.github/workflowsx/y.yml', 'github/workflows/z.yml', '.github/CODEOWNERS', 'packages/x/.github/workflows/w.yml']).length === 0); - assert('surfacesChecked-counts-the-rows-that-govern-the-repository-not-the-table', testVerdict(['README.md'], { repo: 'objectstack' }).surfacesChecked === 7 && testVerdict(['README.md'], { repo: 'objectos' }).surfacesChecked === 6 && governedSurfacesFor('cloud').length === 7 && governedSurfacesFor('hotcrm').map((s) => s.id).join() === 'adr,claude-tree,skills-catalog,agents-md,claude-md,north-star'); + assert('surfacesChecked-counts-the-rows-that-govern-the-repository-not-the-table', testVerdict(['README.md'], { repo: 'objectstack' }).surfacesChecked === 13 && testVerdict(['README.md'], { repo: 'objectos' }).surfacesChecked === 6 && governedSurfacesFor('cloud').length === 7 && governedSurfacesFor('hotcrm').map((s) => s.id).join() === 'adr,claude-tree,skills-catalog,agents-md,claude-md,north-star'); assert('the-verdict-carries-the-repository-it-answered-for', testVerdict([wf], { repo: 'cloud' }).repo === 'cloud' && JSON.parse(JSON.stringify(testVerdict([wf], { repo: 'objectui' }))).repo === 'objectui'); const refuses = (fn) => { try { fn(); return null; } catch (error) { return error; } }; const noRepo = refuses(() => governedPathsIn([wf])); @@ -4468,39 +4483,31 @@ async function selfTest() { // ── the nested `ui/` workspace rows (#25712) ───────────────────────────── // // The objectui tree arrives as the `ui/` workspace of this repository, and - // its own register's six rows have to govern it here under that prefix. The - // rows land on the register in the PR that also names them in the two - // instruction surfaces `check-governed-prose.mjs` pins (both at their line - // ceiling); until then they are THIS fixture, read through the matchers' - // `register` seam, so the matchers are proven on them first. Both directions - // are pinned: each row's specimen is governed Tier H in objectstack, and a - // `ui/` source path, a nested copy of an instruction file and every other - // repository answer NOT governed. The last case pins today's register: it - // does not know `ui/` yet — the PR that adds the rows flips that case. - battery('the nested `ui/` workspace rows, through a register handed in (#25712)'); - const UI_ROWS = Object.freeze([ - Object.freeze({ id: 'ui-adr', prefix: 'ui/docs/adr/', glob: 'ui/docs/adr/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's architecture decision records" }), - Object.freeze({ id: 'ui-claude-tree', prefix: 'ui/.claude/', glob: 'ui/.claude/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's agent instruction tree" }), - Object.freeze({ id: 'ui-skills-catalog', prefix: 'ui/skills/', glob: 'ui/skills/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's published skills catalog" }), - Object.freeze({ id: 'ui-agents-md', exact: 'ui/AGENTS.md', glob: 'ui/AGENTS.md', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's agent instruction file" }), - Object.freeze({ id: 'ui-claude-md', exact: 'ui/CLAUDE.md', glob: 'ui/CLAUDE.md', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's Claude instruction file" }), - Object.freeze({ id: 'ui-workflows', prefix: 'ui/.github/workflows/', glob: 'ui/.github/workflows/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's workflow files" }), - ]); - const WITH_UI = Object.freeze([...GOVERNED_SURFACES, ...UI_ROWS]); - const uiIds = (paths, repo = REPO) => governedPathsIn(paths, repo, WITH_UI).map((s) => s.id); + // its own register's six rows govern it here under that prefix (see the + // register's header). #25712's first PR proved the matchers on these rows as + // a fixture handed in through the `register` seam, before they landed; they + // are the live rows now, so every case below reads the register itself, and + // the seam is held by the one register that does NOT carry them. Both + // directions are pinned: each row's specimen is governed Tier H in + // objectstack, and a `ui/` source path, a nested copy of an instruction file + // and every other repository answer NOT governed. + battery('the nested `ui/` workspace rows, on the register (#25712)'); + const UI_ROWS = GOVERNED_SURFACES.filter((s) => s.id.startsWith('ui-')); + const ROOT_ROWS = Object.freeze(GOVERNED_SURFACES.filter((s) => !s.id.startsWith('ui-'))); const UI_SPECIMENS = { 'ui-adr': 'ui/docs/adr/0001-master-detail-subform.md', 'ui-claude-tree': 'ui/.claude/hooks/guard-main-checkout.sh', 'ui-skills-catalog': 'ui/skills/objectui/SKILL.md', 'ui-agents-md': 'ui/AGENTS.md', 'ui-claude-md': 'ui/CLAUDE.md', 'ui-workflows': 'ui/.github/workflows/ci.yml' }; - assert('the-fixture-rows-are-well-formed-register-rows', UI_ROWS.every((r) => (r.prefix ? r.prefix.endsWith('/') && r.glob === `${r.prefix}**` && r.exact === undefined : r.glob === r.exact) && r.tier === GOVERNED_TIER_H && surfaceRepos(r).join() === 'objectstack') && new Set(WITH_UI.map((r) => r.id)).size === WITH_UI.length, JSON.stringify(UI_ROWS.map((r) => r.id))); - assert('the-fixture-mirrors-the-six-rows-objectui-governs-each-under-ui', UI_ROWS.map((r) => r.glob).join() === GOVERNED_SURFACES.filter((s) => s.id !== 'north-star').map((s) => `ui/${s.glob}`).join(), UI_ROWS.map((r) => r.glob).join()); + assert('the-ui-rows-are-six-well-formed-register-rows', UI_ROWS.length === 6 && UI_ROWS.every((r) => (r.prefix ? r.prefix.endsWith('/') && r.glob === `${r.prefix}**` && r.exact === undefined : r.glob === r.exact) && r.tier === GOVERNED_TIER_H && r.tierRepos === undefined && surfaceRepos(r).join() === 'objectstack') && new Set(GOVERNED_SURFACES.map((r) => r.id)).size === GOVERNED_SURFACES.length, JSON.stringify(UI_ROWS.map((r) => r.id))); + assert('the-ui-rows-mirror-the-six-rows-objectui-governs-each-under-ui', UI_ROWS.map((r) => r.glob).join() === ROOT_ROWS.filter((s) => s.id !== 'north-star').map((s) => `ui/${s.glob}`).join(), UI_ROWS.map((r) => r.glob).join()); for (const row of UI_ROWS) { - assert(`the-${row.id}-specimen-is-governed-by-its-own-row-alone`, uiIds([UI_SPECIMENS[row.id]]).join() === row.id, uiIds([UI_SPECIMENS[row.id]]).join()); + assert(`the-${row.id}-specimen-is-governed-by-its-own-row-alone`, ids([UI_SPECIMENS[row.id]]).join() === row.id, ids([UI_SPECIMENS[row.id]]).join()); } - assert('every-ui-row-lands-Tier-H-the-ui-claude-tree-included', Object.values(UI_SPECIMENS).every((p) => governedTierFor([p], REPO, WITH_UI) === GOVERNED_TIER_H) && governedTierFor([UI_SPECIMENS['ui-claude-tree'], '.claude/agents/os-dev.md'], REPO, WITH_UI) === GOVERNED_TIER_H); - assert('a-ui-source-path-is-NOT-governed', uiIds(['ui/packages/components/src/index.ts', 'ui/apps/console/src/App.tsx', 'ui/package.json', 'ui/.changeset/config.json']).length === 0); - assert('nested-copies-and-near-misses-stay-out', uiIds(['ui/examples/AGENTS.md', 'ui/packages/x/AGENTS.md', 'ui/packages/x/.github/workflows/w.yml', 'ui/.claude-x/y.md', 'ui/skillsx/a.md', 'ui/docs/adrs/z.md', 'ui/.github/CODEOWNERS', 'xui/AGENTS.md']).length === 0); - assert('the-root-rows-do-not-reach-into-ui-and-the-ui-rows-do-not-reach-the-root', uiIds(['ui/.github/workflows/ci.yml', '.github/workflows/lint.yml', 'ui/AGENTS.md', 'AGENTS.md']).join() === 'agents-md,workflows,ui-agents-md,ui-workflows'); - assert('the-ui-rows-govern-objectstack-alone', ['objectui', 'cloud', 'objectos', 'hotcrm'].every((r) => uiIds(Object.values(UI_SPECIMENS), r).length === 0)); - assert('a-register-handed-in-is-counted-by-the-same-reader', governedSurfacesFor(REPO, WITH_UI).length === governedSurfacesFor(REPO).length + UI_ROWS.length); - assert('today-the-register-does-not-know-ui-the-pr-adding-the-rows-flips-this', ids(Object.values(UI_SPECIMENS)).length === 0 && governedSurfacesFor(REPO).length === 7); + assert('every-ui-row-lands-Tier-H-the-ui-claude-tree-included', Object.values(UI_SPECIMENS).every((p) => governedTierFor([p], REPO) === GOVERNED_TIER_H) && governedTierFor([UI_SPECIMENS['ui-claude-tree'], '.claude/agents/os-dev.md'], REPO) === GOVERNED_TIER_H); + assert('--test-judges-a-ui-claude-path-Tier-H-and-a-ui-package-source-path-not-governed', testVerdict(['ui/.claude/x'], { repo: REPO }).tier === GOVERNED_TIER_H && testVerdict(['ui/packages/x/src/y.ts'], { repo: REPO }).governed === false && testVerdict(['ui/packages/x/src/y.ts'], { repo: REPO }).tier === null); + assert('a-ui-source-path-is-NOT-governed', ids(['ui/packages/components/src/index.ts', 'ui/apps/console/src/App.tsx', 'ui/package.json', 'ui/.changeset/config.json']).length === 0); + assert('nested-copies-and-near-misses-stay-out', ids(['ui/examples/AGENTS.md', 'ui/packages/x/AGENTS.md', 'ui/packages/x/.github/workflows/w.yml', 'ui/.claude-x/y.md', 'ui/skillsx/a.md', 'ui/docs/adrs/z.md', 'ui/.github/CODEOWNERS', 'xui/AGENTS.md']).length === 0); + assert('the-root-rows-do-not-reach-into-ui-and-the-ui-rows-do-not-reach-the-root', ids(['ui/.github/workflows/ci.yml', '.github/workflows/lint.yml', 'ui/AGENTS.md', 'AGENTS.md']).join() === 'agents-md,workflows,ui-agents-md,ui-workflows'); + assert('the-ui-rows-govern-objectstack-alone', ['objectui', 'cloud', 'objectos', 'hotcrm'].every((r) => governedPathsIn(Object.values(UI_SPECIMENS), r).length === 0) && governedSurfacesFor('objectui').length === 7); + assert('a-register-handed-in-is-counted-by-the-same-reader', governedSurfacesFor(REPO, ROOT_ROWS).length === governedSurfacesFor(REPO).length - UI_ROWS.length && governedPathsIn(Object.values(UI_SPECIMENS), REPO, ROOT_ROWS).length === 0); + assert('the-register-knows-ui-the-rows-are-live', ids(Object.values(UI_SPECIMENS)).length === UI_ROWS.length && governedSurfacesFor(REPO).length === 13); // ── the dispatch-gates declaration (#9979) ─────────────────────────────── // @@ -4539,7 +4546,7 @@ async function selfTest() { assert('every-row-carries-a-tier-and-it-is-H-or-S', GOVERNED_SURFACES.every((s) => s.tier === GOVERNED_TIER_H || s.tier === GOVERNED_TIER_S), JSON.stringify(GOVERNED_SURFACES.map((s) => [s.id, s.tier]))); assert('the-tier-values-are-distinct-single-letters-a-reader-greps', GOVERNED_TIER_H === 'H' && GOVERNED_TIER_S === 'S' && Object.keys(GOVERNED_TIERS).join() === 'H,S'); assert('Tier-S-is-exactly-the-whole-claude-tree-row', GOVERNED_SURFACES.filter((s) => s.tier === GOVERNED_TIER_S).map((s) => s.id).join() === 'claude-tree'); - assert('Tier-H-is-the-law-adr-north-star-skills-catalog-agents-md-claude-md-and-the-workflows-row', GOVERNED_SURFACES.filter((s) => s.tier === GOVERNED_TIER_H).map((s) => s.id).join() === 'adr,skills-catalog,agents-md,claude-md,north-star,workflows'); + assert('Tier-H-is-the-law-adr-north-star-skills-catalog-agents-md-claude-md-the-workflows-row-and-the-six-ui-rows', GOVERNED_SURFACES.filter((s) => s.tier === GOVERNED_TIER_H).map((s) => s.id).join() === 'adr,skills-catalog,agents-md,claude-md,north-star,workflows,ui-adr,ui-claude-tree,ui-skills-catalog,ui-agents-md,ui-claude-md,ui-workflows'); assert('settings-and-hooks-are-Tier-S-by-the-amendment', governedTierFor(['.claude/settings.json', '.claude/hooks/guard-main-checkout.sh'], REPO) === GOVERNED_TIER_S); assert('skills-agents-and-the-fact-layer-are-Tier-S', governedTierFor(['.claude/skills/pm-dispatch/SKILL.md', '.claude/agents/os-dev.md', '.claude/skills/pm-dispatch/references/contract-review.md'], REPO) === GOVERNED_TIER_S); assert('each-Tier-H-surface-answers-H-alone', ['docs/adr/0001-x.md', 'docs/NORTH-STAR.md', 'skills/objectstack-ui/SKILL.md', 'AGENTS.md', 'CLAUDE.md'].every((p) => governedTierFor([p], REPO) === GOVERNED_TIER_H)); @@ -6000,13 +6007,13 @@ async function selfTest() { const liftedHead = renderTestVerdict(liftedTest).split('\n')[0]; assert( '⭐ the-test-head-no-longer-reports-a-post-lift-zero-as-if-nothing-had-hit-the-register', - liftedHead === 'governed-surface predicate: 0 of 4 path(s) hit the register after 1 generated-artifact lift(s) (7 surfaces govern objectstack).', + liftedHead === 'governed-surface predicate: 0 of 4 path(s) hit the register after 1 generated-artifact lift(s) (13 surfaces govern objectstack).', liftedHead, ); const plainHead = renderTestVerdict(testVerdict(['packages/spec/src/ui/view.zod.ts'], { repo: REPO })).split('\n')[0]; assert( 'and-a-verdict-with-no-lift-keeps-its-head-line-byte-for-byte', - plainHead === 'governed-surface predicate: 0 of 1 path(s) hit the register (7 surfaces govern objectstack).', + plainHead === 'governed-surface predicate: 0 of 1 path(s) hit the register (13 surfaces govern objectstack).', plainHead, ); diff --git a/scripts/pm/check-governed-queue-guard.mjs b/scripts/pm/check-governed-queue-guard.mjs index c912f336d2c..f6686c05fae 100644 --- a/scripts/pm/check-governed-queue-guard.mjs +++ b/scripts/pm/check-governed-queue-guard.mjs @@ -2654,7 +2654,7 @@ export async function selfTest() { assert('an-unapproved-workflow-PR-is-REFUSED-at-the-queue-on-the-Tier-H-terminal', wfRefused.exitCode === EXIT_REFUSED_UNAPPROVED && renderGuardVerdict(wfRefused).includes(WF) && /landing tier: H/.test(renderGuardVerdict(wfRefused)), renderGuardVerdict(wfRefused)); assert('and-an-authorized-approval-on-the-head-clears-it', run('merge_group', [row(23043, [WF])], new Map([[23043, authorizedPass()]])).exitCode === EXIT_CLEAR); const wfClearText = renderGuardVerdict(run('merge_group', [row(3, ['.github/ISSUE_TEMPLATE/bug.md'])])); - assert('the-clear-words-count-the-rows-that-govern-THIS-repository-and-name-it', /7 surfaces govern objectstack/.test(wfClearText), wfClearText); + assert('the-clear-words-count-the-rows-that-govern-THIS-repository-and-name-it', /13 surfaces govern objectstack/.test(wfClearText), wfClearText); const threw = (fn) => { try { fn(); return null; } catch (error) { return error; } }; assert('⛔ decomposeGovernedWork-without-a-repository-is-an-ERROR-never-a-clear', threw(() => decomposeGovernedWork([row(1, [WF])])) instanceof TypeError && /decomposeGovernedWork wants the repository/.test(threw(() => decomposeGovernedWork([row(1, [WF])])).message)); const liftRejects = await liftGeneratedExceptions('/w', 'base', [], []).then(() => null, (error) => error); @@ -4640,7 +4640,7 @@ export async function selfTest() { const live = codeownersMirrorVerdict({ text: onDisk }); assert('⭐ the-file-on-disk-mirrors-the-register-and-the-approver-list', live.ok, live.problems.join(' | ')); assert('every-Tier-H-row-that-governs-this-repository-is-a-pattern-the-file-carries', live.tierHPatterns.length === governedSurfacesFor(REPO).filter((s) => s.tier === GOVERNED_TIER_H).length && live.tierHPatterns.every((p) => live.rows.some((r) => r.pattern === p)), JSON.stringify(live.tierHPatterns)); - assert('the-patterns-are-the-register-rows-root-anchored-prefix-or-exact', live.tierHPatterns.join() === '/docs/adr/,/skills/,/AGENTS.md,/CLAUDE.md,/docs/NORTH-STAR.md,/.github/workflows/', live.tierHPatterns.join()); + assert('the-patterns-are-the-register-rows-root-anchored-prefix-or-exact', live.tierHPatterns.join() === '/docs/adr/,/skills/,/AGENTS.md,/CLAUDE.md,/docs/NORTH-STAR.md,/.github/workflows/,/ui/docs/adr/,/ui/.claude/,/ui/skills/,/ui/AGENTS.md,/ui/CLAUDE.md,/ui/.github/workflows/', live.tierHPatterns.join()); assert('every-line-on-disk-names-every-approver-spelled-as-a-login', live.rows.every((r) => GOVERNED_APPROVERS.every((a) => r.owners.includes(`@${a}`))) && live.rows.length >= live.tierHPatterns.length, JSON.stringify(live.rows)); assert('the-parser-strips-comments-and-blank-lines-and-splits-owners', JSON.stringify(codeownersRows('# c\n\n/docs/adr/ @a @b # trailing\n /x.md @c\n')) === JSON.stringify([{ pattern: '/docs/adr/', owners: ['@a', '@b'], line: 3 }, { pattern: '/x.md', owners: ['@c'], line: 4 }])); const owners = GOVERNED_APPROVERS.map((a) => `@${a}`).join(' '); @@ -4658,19 +4658,16 @@ export async function selfTest() { assert('⛔ a-file-that-never-names-the-constant-is-a-problem', !silent.ok && silent.problems.length === 1 && silent.problems[0].includes('GOVERNED_APPROVERS'), silent.problems.join(' | ')); const other = codeownersMirrorVerdict({ text: onDisk, approvers: ['someone-else'] }); assert('⭐ the-approver-list-is-READ-not-restated-another-list-reds-the-file-on-disk', !other.ok && other.problems.length >= live.tierHPatterns.length, String(other.problems.length)); - // The nested `ui/` workspace rows (#25712), handed in through the register's seam: each lands - // Tier H, so each needs its root-anchored `/ui/…` line, and the file on disk carries none yet. - const uiRows = GOVERNED_SURFACES.filter((s) => s.id !== 'north-star').map((s) => Object.freeze({ - ...s, id: `ui-${s.id}`, ...(s.prefix ? { prefix: `ui/${s.prefix}` } : { exact: `ui/${s.exact}` }), glob: `ui/${s.glob}`, - tier: GOVERNED_TIER_H, tierRepos: undefined, repos: Object.freeze(['objectstack']), - })); - const withUi = [...GOVERNED_SURFACES, ...uiRows]; - const uiLive = codeownersMirrorVerdict({ text: onDisk, register: withUi }); - assert('⛔ with-the-ui-rows-handed-in-the-file-on-disk-is-short-one-line-per-ui-row', !uiLive.ok && uiLive.problems.length === uiRows.length && uiRows.every((r) => uiLive.problems.some((p) => p.includes(`\`${r.id}\``) && p.includes(`\`/${r.prefix ?? r.exact}\``))), uiLive.problems.join(' | ')); - assert('the-ui-patterns-are-root-anchored-under-ui-the-ui-claude-tree-included', uiLive.tierHPatterns.slice(live.tierHPatterns.length).join() === '/ui/docs/adr/,/ui/.claude/,/ui/skills/,/ui/AGENTS.md,/ui/CLAUDE.md,/ui/.github/workflows/', uiLive.tierHPatterns.join()); - const uiFull = `# mirrors GOVERNED_APPROVERS\n${uiLive.tierHPatterns.map((p) => `${p} ${owners}`).join('\n')}\n`; - assert('a-fixture-carrying-the-ui-lines-too-passes-against-the-ui-register', codeownersMirrorVerdict({ text: uiFull, register: withUi }).ok); - assert('the-ui-rows-govern-objectstack-alone-so-a-sibling-copy-needs-no-ui-line', codeownersMirrorVerdict({ text: full(), repo: 'objectui', register: withUi }).tierHPatterns.every((p) => !p.startsWith('/ui/'))); + // The nested `ui/` workspace rows (#25712) are live register rows: each lands Tier H, so each has its + // root-anchored `/ui/…` line on disk, and a file without those lines is short exactly one per row. + const uiRows = governedSurfacesFor(REPO).filter((s) => s.id.startsWith('ui-')); + const withoutUi = GOVERNED_SURFACES.filter((s) => !s.id.startsWith('ui-')); + assert('the-ui-patterns-are-root-anchored-under-ui-the-ui-claude-tree-included', uiRows.length === 6 && live.tierHPatterns.filter((p) => p.startsWith('/ui/')).join() === '/ui/docs/adr/,/ui/.claude/,/ui/skills/,/ui/AGENTS.md,/ui/CLAUDE.md,/ui/.github/workflows/', live.tierHPatterns.join()); + const noUiLines = codeownersMirrorVerdict({ text: onDisk.split('\n').filter((l) => !l.startsWith('/ui/')).join('\n') }); + assert('⛔ without-its-ui-lines-the-file-on-disk-is-short-one-line-per-ui-row', !noUiLines.ok && noUiLines.problems.length === uiRows.length && uiRows.every((r) => noUiLines.problems.some((p) => p.includes(`\`${r.id}\``) && p.includes(`\`/${r.prefix ?? r.exact}\``))), noUiLines.problems.join(' | ')); + const priorRegister = codeownersMirrorVerdict({ text: onDisk.split('\n').filter((l) => !l.startsWith('/ui/')).join('\n'), register: withoutUi }); + assert('the-register-seam-still-reads-a-register-handed-in-the-one-without-the-ui-rows-passes-the-file-without-their-lines', priorRegister.ok && priorRegister.tierHPatterns.every((p) => !p.startsWith('/ui/')), priorRegister.problems.join(' | ')); + assert('the-ui-rows-govern-objectstack-alone-so-a-sibling-copy-needs-no-ui-line', codeownersMirrorVerdict({ text: full(), repo: 'objectui' }).tierHPatterns.every((p) => !p.startsWith('/ui/'))); } // ── The floor: every declared battery RAN, and ran its cases (#13489) ────