From eb9876f96aee954a30118382e95507599dc356dd Mon Sep 17 00:00:00 2001 From: PerishCode Date: Fri, 24 Jul 2026 15:45:53 +0800 Subject: [PATCH 1/9] feat(ci): promote the certain-exempt docs core to merge-queue trust Split the exempt surface into a certain-tier core (docs/, apps/landing-page/, .vscode/, .idea/, .github/ISSUE_TEMPLATE/, LICENSE, CODEOWNERS) and the medium residue (global markdown regex, workflow exacts, nix). A merge-queue group confined to the core now drops to the unconditional floor lanes instead of running everything; replaying the last 398 first-parent merges puts that at 12.3% of queue traffic. The promotion is backed by a new floor-lane guard check, "certain-exempt surface consumption": no skippable-lane source may reference a certain-exempt path (dot-relative literals flagged everywhere; bare repo-relative literals flagged outside test fixtures). guard.ts gains --list-checks as the machine registry, and the rule-table invariant test now requires every certain rule's guard field to resolve to a live check. --- e2e/tests/scripts/scopes.test.ts | 93 +++++++++- scripts/check-certain-exempt-consumption.ts | 183 ++++++++++++++++++++ scripts/guard.ts | 13 +- scripts/scopes.ts | 75 ++++++-- 4 files changed, 339 insertions(+), 25 deletions(-) create mode 100644 scripts/check-certain-exempt-consumption.ts diff --git a/e2e/tests/scripts/scopes.test.ts b/e2e/tests/scripts/scopes.test.ts index c96cf2c7711..71aa2ea0632 100644 --- a/e2e/tests/scripts/scopes.test.ts +++ b/e2e/tests/scripts/scopes.test.ts @@ -423,15 +423,26 @@ const GOLDEN_CASES: readonly GoldenCase[] = [ expected: FULL_PLAN, }, { - // While the certain rule set is empty, every queued file sits below the - // merge-queue trust threshold and escalates: the queue stays full. The - // first certain-rule promotion is the deliberate behavior change that - // makes this case diverge. - name: "merge_group docs-only group still runs everything at the certain threshold", + // Root markdown stays medium-tier (the global md regex is not promotable: + // its safety depends on other rules covering runtime-markdown directories), + // so one README.md in the group escalates and keeps the queue full even + // though docs/ itself is certain-tier. + name: "merge_group group with root markdown still runs everything at the certain threshold", context: { eventName: "merge_group" }, files: ["README.md", "docs/architecture.md"], expected: FULL_PLAN, }, + { + // The first certain-tier promotion: a group confined to the certain-exempt + // core (docs/, landing-page, editor configs, LICENSE/CODEOWNERS) drops to + // the unconditional floor lanes instead of running everything. Guarded by + // the "certain-exempt surface consumption" guard check; methodology in + // specs/current/ci.md. + name: "merge_group certain-exempt core group drops to the floor lanes", + context: { eventName: "merge_group" }, + files: ["docs/architecture.md", "docs/nested/guide.mdx", "apps/landing-page/src/pages/index.astro", "LICENSE", ".github/CODEOWNERS"], + expected: expectedPlan({ ciMode: "full" }), + }, { name: "merge_group mixed group runs everything at the certain threshold", context: { eventName: "merge_group" }, @@ -482,18 +493,84 @@ test("rule ids are unique", async () => { assert.deepEqual(ids, [...new Set(ids)]); }); -test("certain rules must name their enforcing guard", async () => { +test("certain rules must name a guard that resolves to a real guard check", async () => { const { scopeRules } = await import("../../../scripts/scopes.ts"); + // guard.ts must run through tsx (its check modules use .js-suffixed TS-ESM + // specifiers), so go through the root guard script exactly like CI does. + const guardCheckNames = new Set( + execFileSync("pnpm", ["--silent", "guard", "--list-checks"], { cwd: repoRoot, encoding: "utf8" }) + .split("\n") + .filter(Boolean), + ); for (const rule of scopeRules) { if (rule.confidence === "certain") { assert.ok( - rule.guard != null && rule.guard.length > 0, - `rule ${rule.id} is "certain" but names no guard; promotion requires the check that keeps its boundary invariant true`, + rule.guard != null && guardCheckNames.has(rule.guard), + `rule ${rule.id} is "certain" but its guard ${JSON.stringify(rule.guard)} does not resolve to a scripts/guard.ts check; promotion requires the live check that keeps its boundary invariant true`, ); } } }); +test("certain-exempt markdown matches only the certain rule (no medium co-match neutralizes the promotion)", async () => { + const { scopeRules, matchesRuleMatch } = await import("../../../scripts/scopes.ts"); + for (const file of ["docs/architecture.md", "docs/nested/guide.mdx", "apps/landing-page/README.md"]) { + const matched = scopeRules.filter((rule) => matchesRuleMatch(file, rule.match)).map((rule) => rule.id); + assert.deepEqual(matched, ["certain-exempt-surface"], file); + } +}); + +test("merge-queue threshold trusts the certain-exempt core without escalation", async () => { + const { evaluateScopeOutputs, SCOPE_EFFECTS } = await import("../../../scripts/scopes.ts"); + const atQueue = evaluateScopeOutputs(["docs/architecture.md"], "certain", { + deriveWorkspaceValidationFromTestScopes: true, + }); + assert.deepEqual( + Object.values(atQueue.outputs), + SCOPE_EFFECTS.map(() => false), + ); + assert.deepEqual(atQueue.decisions[0], { + file: "docs/architecture.md", + matchedRules: ["certain-exempt-surface"], + escalated: false, + }); +}); + +test("the consumption guard flags repo-resolving literals and tolerates test fixtures", async () => { + const { collectCertainExemptConsumptionFromSource, isFixtureTolerantPath } = await import( + "../../../scripts/check-certain-exempt-consumption.ts" + ); + + // Dot-relative resolution into docs/ is consumption anywhere, tests included. + const relative = collectCertainExemptConsumptionFromSource( + "apps/daemon/tests/example.test.ts", + `const spec = readFileSync("../../../docs/spec.md", "utf8");`, + ); + assert.deepEqual(relative.map((violation) => violation.literal), ["../../../docs/spec.md"]); + + // Bare repo-relative literals are consumption in non-test source... + const bareInSource = collectCertainExemptConsumptionFromSource( + "tools/example/src/config.ts", + `const changelog = "docs/CHANGELOG";`, + ); + assert.deepEqual(bareInSource.map((violation) => violation.literal), ["docs/CHANGELOG"]); + + // ...but fixture data in tests (sandboxed project paths, never repo docs/). + assert.ok(isFixtureTolerantPath("apps/daemon/tests/example.test.ts")); + const bareInTest = collectCertainExemptConsumptionFromSource( + "apps/daemon/tests/example.test.ts", + `await writeProjectFile("docs/empty.md", "");`, + ); + assert.deepEqual(bareInTest, []); + + // Prose that merely mentions a docs path does not start with the prefix. + const prose = collectCertainExemptConsumptionFromSource( + "apps/web/src/copy.ts", + `const footer = "Spec: docs/skills-protocol.md covers the adapter surface.";`, + ); + assert.deepEqual(prose, []); +}); + test("the rule table classifies every file: no path escapes both fallbacks", async () => { const { scopeRules, matchesRuleMatch } = await import("../../../scripts/scopes.ts"); const samples = [ diff --git a/scripts/check-certain-exempt-consumption.ts b/scripts/check-certain-exempt-consumption.ts new file mode 100644 index 00000000000..f5b4d0ae6dc --- /dev/null +++ b/scripts/check-certain-exempt-consumption.ts @@ -0,0 +1,183 @@ +import { readFile, readdir } from "node:fs/promises"; +import path from "node:path"; +import ts from "typescript"; + +import { CERTAIN_EXEMPT_EXACT, CERTAIN_EXEMPT_PREFIXES } from "./scopes.ts"; + +// Guard for the certain-tier exempt core in `scripts/scopes.ts` (rule +// `certain-exempt-surface`; methodology in `specs/current/ci.md`). +// +// Boundary invariant: no source that a *skippable* merge-gate lane executes may +// consume a certain-exempt file. If that held false, a docs-only change could +// invalidate a lane the promoted rule tells the merge queue to skip. +// +// What "consumption" means here, at two precision levels: +// - a dot-relative literal (`../../docs/...`) that resolves from the file's +// repo location into the certain-exempt surface: flagged everywhere — it can +// only mean the repository surface. +// - a bare repo-relative literal (`docs/CHANGELOG`): flagged only in non-test +// sources. In tests, bare `docs/...` strings are overwhelmingly project +// fixture paths inside sandboxed workspaces (and Vitest's cwd is the package +// dir, so a bare relative read could not reach repository docs/ anyway); +// real repo consumption in source code is a repo-root-resolved config +// default, which this level catches. +// Template literals with substitutions are not statically resolvable and are +// out of scope. +// +// Deliberately outside the checked surface: +// - root `scripts/` — floor-owned code. Preflight and workspace unit tests are +// unconditionally armed on every plan, so floor checks may read the exempt +// surface (product neutrality validates docs/ prose on every run). +// - `apps/landing-page/` — it IS the exempt surface; landing-page CI owns it. + +const repoRoot = path.resolve(import.meta.dirname, ".."); + +const checkedRoots = ["apps", "packages", "tools", "e2e"] as const; + +const skippedDirectoryNames = new Set([ + ".astro", + ".next", + ".od-data", + "dist", + "node_modules", + "out", + "reports", + "test-results", + "vendor", +]); + +const skippedRepositoryPrefixes = ["apps/landing-page/"]; + +const checkedExtensions = new Set([".ts", ".tsx"]); + +// File-level exceptions. Every entry must explain why the reference is not +// gate-lane consumption of exempt-file *content*; revisit the entry if the +// file's relationship to the exempt surface changes. +const allowedConsumers = new Map([ + [ + "tools/release/src/release-note/prepare.ts", + "docs/CHANGELOG feeds release-note preparation, which runs only in release workflows; @open-design/tools-release tests run in no ci.yml lane", + ], + [ + "e2e/tests/scripts/scopes.test.ts", + "behavior fixtures for this very check: source snippets passed to the collector as data, never resolved or opened", + ], +]); + +type ConsumptionViolation = { + filePath: string; + lineNumber: number; + literal: string; +}; + +function toRepositoryPath(filePath: string): string { + return path.relative(repoRoot, filePath).split(path.sep).join("/"); +} + +function landsInCertainExemptSurface(repositoryPath: string): boolean { + return ( + CERTAIN_EXEMPT_PREFIXES.some((prefix) => repositoryPath.startsWith(prefix)) || + (CERTAIN_EXEMPT_EXACT as readonly string[]).includes(repositoryPath) + ); +} + +export function isFixtureTolerantPath(repositoryPath: string): boolean { + return ( + /\.test\.tsx?$/.test(repositoryPath) || + repositoryPath.includes("/tests/") || + repositoryPath.startsWith("e2e/") + ); +} + +function literalConsumesCertainExemptSurface(fromRepositoryPath: string, literal: string): boolean { + if (literal.startsWith("./") || literal.startsWith("../")) { + const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(fromRepositoryPath), literal)); + return landsInCertainExemptSurface(resolved); + } + if (isFixtureTolerantPath(fromRepositoryPath)) return false; + return landsInCertainExemptSurface(literal); +} + +export function collectCertainExemptConsumptionFromSource( + repositoryPath: string, + source: string, +): ConsumptionViolation[] { + const sourceFile = ts.createSourceFile( + repositoryPath, + source, + ts.ScriptTarget.Latest, + true, + repositoryPath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS, + ); + const violations: ConsumptionViolation[] = []; + + const visit = (node: ts.Node): void => { + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) { + if (literalConsumesCertainExemptSurface(repositoryPath, node.text)) { + violations.push({ + filePath: repositoryPath, + lineNumber: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1, + literal: node.text, + }); + } + } + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return violations; +} + +async function collectCheckedFiles(directory: string): Promise { + const entries = await readdir(directory, { withFileTypes: true }); + const files: string[] = []; + + for (const entry of entries) { + const fullPath = path.join(directory, entry.name); + const repositoryPath = toRepositoryPath(fullPath); + + if (entry.isDirectory()) { + if ( + skippedDirectoryNames.has(entry.name) || + entry.name.startsWith(".next-") || + skippedRepositoryPrefixes.some((prefix) => `${repositoryPath}/`.startsWith(prefix)) + ) { + continue; + } + files.push(...(await collectCheckedFiles(fullPath))); + continue; + } + + if (entry.isFile() && checkedExtensions.has(path.extname(entry.name))) { + files.push(repositoryPath); + } + } + + return files; +} + +export async function checkCertainExemptConsumption(): Promise { + const violations: ConsumptionViolation[] = []; + + for (const root of checkedRoots) { + for (const repositoryPath of await collectCheckedFiles(path.join(repoRoot, root))) { + if (allowedConsumers.has(repositoryPath)) continue; + const source = await readFile(path.join(repoRoot, repositoryPath), "utf8"); + violations.push(...collectCertainExemptConsumptionFromSource(repositoryPath, source)); + } + } + + if (violations.length > 0) { + console.error("Certain-exempt surface consumption found in gate-lane sources:"); + for (const violation of violations) { + console.error(`- ${violation.filePath}:${violation.lineNumber} \`${violation.literal}\``); + } + console.error( + "Certain-tier exempt files must stay unconsumed by skippable merge-gate lanes (see specs/current/ci.md). Move the dependency, or add a justified allowlist entry in scripts/check-certain-exempt-consumption.ts.", + ); + return false; + } + + console.log("Certain-exempt consumption check passed: gate-lane sources do not read the certain-exempt surface."); + return true; +} diff --git a/scripts/guard.ts b/scripts/guard.ts index ff1b2aad532..1ac1c9f5c47 100644 --- a/scripts/guard.ts +++ b/scripts/guard.ts @@ -3,6 +3,7 @@ import path from "node:path"; import { pathToFileURL } from "node:url"; import ts from "typescript"; +import { checkCertainExemptConsumption } from "./check-certain-exempt-consumption.ts"; import { checkCrossAppImports } from "./check-cross-app-imports.ts"; import { checkTsNocheckImports } from "./check-ts-nocheck-imports.ts"; import { checkDesignSystemManifests } from "./check-design-system-manifests.ts"; @@ -1322,6 +1323,7 @@ async function checkCiTopology(): Promise { const checks: GuardCheck[] = [ { name: "residual JavaScript", run: checkResidualJavaScript }, + { name: "certain-exempt surface consumption", run: checkCertainExemptConsumption }, { name: "package dependency specs", run: checkPackageDependencySpecs }, { name: "product neutrality", run: checkProductNeutrality }, { name: "cross-app imports", run: checkCrossAppImports }, @@ -1365,6 +1367,13 @@ async function runChecks(): Promise { } const isMain = process.argv[1] ? import.meta.url === pathToFileURL(process.argv[1]).href : false; -if (isMain && !(await runChecks())) { - process.exitCode = 1; +if (isMain) { + // `--list-checks` is the machine-readable registry of guard check names; the + // scope rule-table invariant test resolves `certain` rules' guard fields + // against it so a renamed or deleted guard fails CI. + if (process.argv[2] === "--list-checks") { + for (const check of checks) console.log(check.name); + } else if (!(await runChecks())) { + process.exitCode = 1; + } } diff --git a/scripts/scopes.ts b/scripts/scopes.ts index f1b40204994..58c7a09208e 100644 --- a/scripts/scopes.ts +++ b/scripts/scopes.ts @@ -20,6 +20,10 @@ import { uiP0CiMatrix, visualCiMatrix } from "../e2e/lib/playwright/suites.ts"; // "certain" requires a `guard` naming the check that keeps its boundary // invariant true, and is a deliberate, reviewed behavior change. // - manual full runs believe nothing and arm everything. +// +// The promotion methodology — evidence requirements, guard placement rules, +// and the replay recipes behind every confidence change — lives in +// `specs/current/ci.md`. Read it before editing confidence or guard fields. // --------------------------------------------------------------------------- type CiMode = "hot" | "full"; @@ -95,20 +99,37 @@ type GitHubEvent = { // Rule table // --------------------------------------------------------------------------- -const EXEMPT_PREFIXES = [ +// Certain-tier exempt core: surfaces whose "cannot affect gate validation" +// invariant is definitional and enforced by the named guard check. Floor lanes +// (preflight and workspace unit tests, both unconditionally armed) still run +// for these files, so floor-owned checks that read them — e.g. product +// neutrality over docs/ — keep validating them on every plan. +export const CERTAIN_EXEMPT_PREFIXES = [ ".vscode/", ".idea/", "docs/", "apps/landing-page/", - "nix/", ".github/ISSUE_TEMPLATE/", ] as const; -const EXEMPT_EXACT = [ - "LICENSE", +// LICENSE and CODEOWNERS are GitHub/legal metadata; the consumption guard scan +// proves no gate-lane source reads them. +export const CERTAIN_EXEMPT_EXACT = ["LICENSE", ".github/CODEOWNERS"] as const; + +const CERTAIN_EXEMPT_SURFACE: RuleMatch = { + prefixes: CERTAIN_EXEMPT_PREFIXES, + exact: CERTAIN_EXEMPT_EXACT, +}; + +// Medium-tier exempt residue. The global markdown regex stays medium on +// purpose: its safety at certain would depend on every other rule continuing +// to cover each directory where markdown is runtime content (skills/, craft/, +// design-templates/, ...) — a cross-rule invariant no local guard can keep. +const MEDIUM_EXEMPT_PREFIXES = ["nix/"] as const; + +const MEDIUM_EXEMPT_EXACT = [ ".gitignore", ".editorconfig", - ".github/CODEOWNERS", "flake.nix", "flake.lock", ".github/workflows/landing-page-ci.yml", @@ -124,16 +145,32 @@ const EXEMPT_EXACT = [ const EXEMPT_REGEXES = [/\.(?:md|mdx|txt)$/] as const; +// Union of both exempt tiers; this is what the fail-closed fallbacks exclude. const EXEMPT_SURFACE: RuleMatch = { - prefixes: EXEMPT_PREFIXES, - exact: EXEMPT_EXACT, + prefixes: [...CERTAIN_EXEMPT_PREFIXES, ...MEDIUM_EXEMPT_PREFIXES], + exact: [...CERTAIN_EXEMPT_EXACT, ...MEDIUM_EXEMPT_EXACT], regexes: EXEMPT_REGEXES, }; export const scopeRules: readonly ScopeRule[] = [ { + id: "certain-exempt-surface", + match: CERTAIN_EXEMPT_SURFACE, + effects: [], + confidence: "certain", + guard: "certain-exempt surface consumption", + }, + { + // excludeWhen keeps this rule off certain-core files: a `docs/*.md` file + // must match only the certain rule, or min-confidence co-matching would + // silently neutralize the promotion. id: "exempt-surface", - match: EXEMPT_SURFACE, + match: { + prefixes: MEDIUM_EXEMPT_PREFIXES, + exact: MEDIUM_EXEMPT_EXACT, + regexes: EXEMPT_REGEXES, + excludeWhen: CERTAIN_EXEMPT_SURFACE, + }, effects: [], confidence: "medium", }, @@ -290,8 +327,14 @@ export const scopeRules: readonly ScopeRule[] = [ id: "ui-critical-fallback", match: { excludeWhen: { - prefixes: [...EXEMPT_PREFIXES, "apps/desktop/", "apps/packaged/", "tools/pack/"], - exact: EXEMPT_EXACT, + prefixes: [ + ...CERTAIN_EXEMPT_PREFIXES, + ...MEDIUM_EXEMPT_PREFIXES, + "apps/desktop/", + "apps/packaged/", + "tools/pack/", + ], + exact: [...CERTAIN_EXEMPT_EXACT, ...MEDIUM_EXEMPT_EXACT], regexes: EXEMPT_REGEXES, }, }, @@ -526,11 +569,13 @@ function createEnvScopePlan(): PlanWithTrace { if (eventName === "merge_group") { // The merge queue evaluates the whole queued group's union diff at the - // "certain" threshold. While the certain rule set is empty every file - // escalates and the plan stays full, so this path is behavior-preserving; - // its trace's ifTrustAll shadow column is the evidence stream for future - // promotions. Resolution anomalies fail open to the full plan: queue - // throughput must never depend on this evaluation succeeding. + // "certain" threshold. Files matching only certain-tier rules contribute + // their claimed radius (for the certain-exempt core: none — a pure-docs + // group drops to the unconditional floor lanes); every other file + // escalates and keeps the plan full. The trace's ifTrustAll shadow column + // is the evidence stream for future promotions. Resolution anomalies fail + // open to the full plan: queue throughput must never depend on this + // evaluation succeeding. let files: string[]; try { files = changedMergeGroupFiles(); From 5d0f1a85fb1bd4c4a0069ab8a802442ca9be9ee9 Mon Sep 17 00:00:00 2001 From: PerishCode Date: Fri, 24 Jul 2026 15:45:53 +0800 Subject: [PATCH 2/9] docs(ci): record the scope confidence methodology in specs/current/ci.md The living framework for confidence-tier changes: medium-tier cheap loop vs certain-tier deliberate loop, the three-sentence promotion discipline, guard strength levels and floor placement, evidence recipes (replay, offline plan, queue trace extraction), tooling graduation criteria, and the open questions deliberately left undecided. Indexed from AGENTS.md and .github/AGENTS.md required reading. --- .github/AGENTS.md | 1 + AGENTS.md | 2 +- specs/current/ci.md | 154 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 specs/current/ci.md diff --git a/.github/AGENTS.md b/.github/AGENTS.md index cb3038e7df7..d7aca334194 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -12,6 +12,7 @@ Before changing GitHub automation, read the current versions of: - `.github/workflows/report.atom.yml` - `.github/scripts/handoff.py` - `scripts/scopes.ts` +- `specs/current/ci.md` when changing scope rules, confidence tiers, or guards - `e2e/tests/packaged-smoke-workflow.test.ts` - `scripts/approve-fork-pr-workflows.ts` and `e2e/tests/scripts/approve-fork-pr-workflows.test.ts` when touching fork PR approval behavior diff --git a/AGENTS.md b/AGENTS.md index fa9db76ee7c..0af847f60c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ This file is the single source of truth for agents entering this repository. Rea - Contribution and environment: `CONTRIBUTING.md`, `docs/i18n/CONTRIBUTING.zh-CN.md`. - Architecture and protocols: `docs/architecture.md`, `docs/skills-protocol.md`, `docs/agent-adapters.md`, `docs/modes.md`. - Historical product baseline: `docs/spec.md`, `docs/roadmap.md` (both explicitly archived; do not treat their dated decisions as current behavior). -- References and current plans: `docs/references.md`, `docs/code-review-guidelines.md`, `specs/current/maintainability-roadmap.md`. +- References and current plans: `docs/references.md`, `docs/code-review-guidelines.md`, `specs/current/maintainability-roadmap.md`, `specs/current/ci.md` (CI scope confidence methodology — required before changing confidence or guard fields in `scripts/scopes.ts`). - Directory-level agent guidance: `.github/AGENTS.md`, `apps/AGENTS.md`, `packages/AGENTS.md`, `tools/AGENTS.md`, `e2e/AGENTS.md`. - Packaged auto-update architecture and high-confidence local harness: read `tools/pack/AGENTS.md` section "Packaged auto-update architecture and harness" before touching packaged updater code, release-channel identity, installer behavior, or updater UI. diff --git a/specs/current/ci.md b/specs/current/ci.md new file mode 100644 index 00000000000..3e95f487616 --- /dev/null +++ b/specs/current/ci.md @@ -0,0 +1,154 @@ +# CI scope confidence methodology + +This is the living framework for how trust in CI scope rules evolves. It owns +confidence-tier changes in `scripts/scopes.ts` (promotions, demotions, guard +requirements) and the evidence recipes behind them. Workflow topology and the +capability/handoff architecture stay owned by `.github/AGENTS.md`; do not +restate them here. + +## The model in three paragraphs + +Every changed file is classified by the additive rule table in +`scripts/scopes.ts`: effects union across matched rules, confidence is the +minimum across matched rules. Each evaluation context brings a trust threshold: +PR and manual-hot runs believe `medium`, the merge queue believes only +`certain`, manual-full runs believe nothing. A file below threshold — or +matching no rule — escalates fail-closed to the full radius. + +Two floors never move: `run_preflight` and `run_workspace_unit_tests` are +unconditionally true in every plan. Preflight runs `pnpm guard` and the +workspace typecheck, so guard checks execute on every run of every plan — +including runs where a certain-tier rule skipped everything else. + +The error cost is asymmetric by tier. A wrong `medium` rule under-arms a PR +run and gets caught by the merge queue's stricter threshold — cost: one queue +bounce. A wrong `certain` rule lets an invalid change reach `main` with no +automatic detection behind it. That asymmetry is why the two tiers have +different iteration rules below. + +## Medium-tier iteration (cheap loop) + +Adding or refining a `medium` rule needs: the rule-table diff, updated goldens +in `e2e/tests/scripts/scopes.test.ts`, and a tonnage estimate from the replay +recipe. The queue backstops mistakes. Do not add speculative rules for +surfaces nobody touches; candidates come from measurement, not from reading +the rule table for imperfections (measured imperfection lists and +frequency-weighted tonnage lists barely intersect). + +## Certain-tier promotion (deliberate loop) + +A promotion PR must be statable in three sentences: which rule, what guard, +how much tonnage. Anything that cannot fit that statement is riding along and +must be split out. + +Requirements: + +1. **A defensible core.** Promote the subset of the surface whose boundary + invariant is local and checkable. Split the rule if needed. Example: the + global `*.md` regex is permanently medium because its safety depends on + *other* rules covering every runtime-markdown directory — a cross-rule + invariant no local guard can keep. +2. **A guard that resolves.** The rule's `guard` field must name a live + `scripts/guard.ts` check (`pnpm --silent guard --list-checks` is the + registry; the rule-table invariant test enforces resolution). Guards for + certain rules must run in a floor lane — `pnpm guard` in preflight + qualifies — so the check that justifies skipping always itself runs. +3. **Evidence proportional to the guard's strength.** Guard invariants come in + three strengths: *definitional* (the surface cannot enter build or runtime + by construction — e.g. docs), *structural* (an import-graph boundary), and + *behavioral* (a topology test). Definitional promotions may rely on replay + evidence alone. Structural and behavioral promotions additionally require + shadow evidence from real queue runs (the `ifTrustAll` column in the scope + decision trace); the required window is an open question until the first + such proposal exists. +4. **Goldens updated, divergence pinned.** The golden that changes is the + proof of the behavior change; the goldens that do not change are the proof + of its containment. + +Demotion is not yet codified (open question), with one hard rule already in +force: if a guard check is deleted or renamed, the rule-table invariant test +fails CI — a certain rule can never silently outlive its guard. + +## Promotion #1 (2026-07): the certain-exempt core + +Rule `certain-exempt-surface`: prefixes `docs/`, `apps/landing-page/`, +`.vscode/`, `.idea/`, `.github/ISSUE_TEMPLATE/` plus exacts `LICENSE`, +`.github/CODEOWNERS`. Guard: `certain-exempt surface consumption` +(`scripts/check-certain-exempt-consumption.ts`) — no skippable-lane source may +reference a certain-exempt path; floor-owned code (root `scripts/`) is exempt +from the scan because floor lanes always run and may validate docs content +(product neutrality does). + +Evidence, from replaying the 398 first-parent merges before the promotion: + +- 56 merges (14.1%) touched only the exempt surface; under the promoted core + 49 (12.3%) stay pure. The 7 lost are mostly root markdown (`README.md`), + deliberately left medium. +- Each pure-core queue run drops from the full lane set to the two floors. +- Known true consumer found and allowlisted with rationale: + `tools/release/src/release-note/prepare.ts` reads `docs/CHANGELOG`, which + executes only in release workflows; `@open-design/tools-release` tests run + in no `ci.yml` lane. + +## Evidence recipes + +Design rule: shell only fetches file lists and extracts logs; every scope +judgment goes through `scripts/scopes.ts plan`. Never reimplement rule +semantics in a pipeline. + +Replay recent merges through the evaluator (candidate tonnage): + +```bash +git log --first-parent -400 --pretty=%H origin/main | while read -r sha; do + git diff-tree -r --name-only --no-commit-id "$sha^" "$sha" | + node --experimental-strip-types scripts/scopes.ts plan \ + --context merge-queue --files-from - | + node -e 'const d=JSON.parse(require("fs").readFileSync(0,"utf8")); + console.log(d.trace.escalations.length === 0 ? "PURE" : "ESCALATED")' +done | sort | uniq -c +``` + +Classify one change set offline (PR-side view, prints `{ plan, trace }`): + +```bash +node --experimental-strip-types scripts/scopes.ts plan --context pr \ + --files apps/web/src/App.tsx docs/architecture.md +``` + +Pull the shadow column from a real queue run (the promotion evidence stream +for structural/behavioral proposals; prefer job logs — do not rely on +artifacts): + +```bash +gh run view --log | sed -n '/scope decision trace:/,/^}/p' +``` + +Each recipe's sanity check: the replay loop must print only `PURE`/`ESCALATED` +counts; `plan` must print JSON with a `trace.threshold` matching the context. + +## Tooling graduation + +These recipes stay recipes. A recipe graduates into a checked-in script only +when a promotion proposal needs evidence beyond the CI log retention window, +or the recipe is being re-run often enough that copy errors have actually +bitten. Infrastructure here follows the same rule as confidence: earned by +evidence, not provisioned in advance. + +## Open questions + +- Shadow-evidence window for the first structural promotion (define N when + that proposal exists). +- Demotion policy beyond the guard-resolution hard rule. +- Floor conditionalization: pure certain-core changes still pay preflight + + workspace unit tests; skipping the floors is the natural second certain-tier + proposal and must confront the "docs/ can hide a `.js` file from guard" + class of questions. +- Queue batching discount: the 12.3% figure assumes single-PR queue groups; a + mixed group loses the benefit file-by-file. Check real `merge_group` traces + once a few have accumulated. +- Adjacent medium-tier gaps (each is its own small PR): `e2e/tests/**` arms no + e2e Vitest lane (and atom-workflow edits therefore skip the topology tests + on PR runs — the queue is currently their only pre-main execution); + `mocks/**` is fallback-classified into Playwright lanes instead of the + daemon tests that consume it; the dispatch-hot branch never re-derives + workspace validation (pinned asymmetry in the goldens). From b56bc206b9bf458673934283daa26960f6fc38a6 Mon Sep 17 00:00:00 2001 From: Looper Date: Fri, 24 Jul 2026 16:11:41 +0800 Subject: [PATCH 3/9] fix(ci): narrow certain-exempt fixture handling Generated-By: looper 0.11.0 (runner=fixer, agent=codex) --- e2e/tests/scripts/scopes.test.ts | 29 +++++--- scripts/check-certain-exempt-consumption.ts | 78 ++++++++++++++++----- 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/e2e/tests/scripts/scopes.test.ts b/e2e/tests/scripts/scopes.test.ts index 71aa2ea0632..a9274d31c03 100644 --- a/e2e/tests/scripts/scopes.test.ts +++ b/e2e/tests/scripts/scopes.test.ts @@ -536,8 +536,8 @@ test("merge-queue threshold trusts the certain-exempt core without escalation", }); }); -test("the consumption guard flags repo-resolving literals and tolerates test fixtures", async () => { - const { collectCertainExemptConsumptionFromSource, isFixtureTolerantPath } = await import( +test("the consumption guard distinguishes repo readers from sandbox fixture writers", async () => { + const { collectCertainExemptConsumptionFromSource } = await import( "../../../scripts/check-certain-exempt-consumption.ts" ); @@ -555,13 +555,26 @@ test("the consumption guard flags repo-resolving literals and tolerates test fix ); assert.deepEqual(bareInSource.map((violation) => violation.literal), ["docs/CHANGELOG"]); - // ...but fixture data in tests (sandboxed project paths, never repo docs/). - assert.ok(isFixtureTolerantPath("apps/daemon/tests/example.test.ts")); - const bareInTest = collectCertainExemptConsumptionFromSource( - "apps/daemon/tests/example.test.ts", - `await writeProjectFile("docs/empty.md", "");`, + // ...and in tests when a repo-root helper reads them. + const repoReadInTest = collectCertainExemptConsumptionFromSource( + "apps/daemon/tests/runtimes/trae-cli.test.ts", + `await readRepoFile("docs/agent-adapters.md");`, ); - assert.deepEqual(bareInTest, []); + assert.deepEqual(repoReadInTest.map((violation) => violation.literal), [ + "docs/agent-adapters.md", + ]); + + // Known project writers resolve the same-looking path inside a sandbox. + for (const source of [ + `await writeProjectFile("docs/empty.md", "");`, + `await fixture.writeProjectFile("docs/empty.md", "");`, + ]) { + const fixtureWrite = collectCertainExemptConsumptionFromSource( + "apps/daemon/tests/example.test.ts", + source, + ); + assert.deepEqual(fixtureWrite, []); + } // Prose that merely mentions a docs path does not start with the prefix. const prose = collectCertainExemptConsumptionFromSource( diff --git a/scripts/check-certain-exempt-consumption.ts b/scripts/check-certain-exempt-consumption.ts index f5b4d0ae6dc..2e84ff3a95f 100644 --- a/scripts/check-certain-exempt-consumption.ts +++ b/scripts/check-certain-exempt-consumption.ts @@ -15,12 +15,10 @@ import { CERTAIN_EXEMPT_EXACT, CERTAIN_EXEMPT_PREFIXES } from "./scopes.ts"; // - a dot-relative literal (`../../docs/...`) that resolves from the file's // repo location into the certain-exempt surface: flagged everywhere — it can // only mean the repository surface. -// - a bare repo-relative literal (`docs/CHANGELOG`): flagged only in non-test -// sources. In tests, bare `docs/...` strings are overwhelmingly project -// fixture paths inside sandboxed workspaces (and Vitest's cwd is the package -// dir, so a bare relative read could not reach repository docs/ anyway); -// real repo consumption in source code is a repo-root-resolved config -// default, which this level catches. +// - a bare repo-relative literal (`docs/CHANGELOG`): flagged everywhere unless +// it is an argument to a known sandbox-fixture writer. Test files can also +// contain repo-root helpers, so exempting them wholesale would hide real +// consumption. // Template literals with substitutions are not statically resolvable and are // out of scope. // @@ -50,14 +48,55 @@ const skippedRepositoryPrefixes = ["apps/landing-page/"]; const checkedExtensions = new Set([".ts", ".tsx"]); +// These helpers interpret their path argument inside a temporary project +// workspace. Keep this list narrow: repo-root readers used by tests must remain +// visible to the guard. +const sandboxFixtureWriterNames = new Set(["writeProjectFile"]); + // File-level exceptions. Every entry must explain why the reference is not // gate-lane consumption of exempt-file *content*; revisit the entry if the // file's relationship to the exempt surface changes. const allowedConsumers = new Map([ + [ + "apps/daemon/tests/claude-design-import.test.ts", + "archive-entry and extracted-project fixture paths; every docs/ literal names data inside a temporary project", + ], + [ + "apps/daemon/tests/design-systems/file-score.test.ts", + "path-classification fixture strings; the test does not open the editor configuration files", + ], + [ + "apps/daemon/tests/project-classifiers.test.ts", + "file-kind classifier input; the LICENSE literal is never resolved or opened", + ], + [ + "apps/daemon/tests/runtimes/trae-cli.test.ts", + "reads docs/agent-adapters.md from the repository, but this test file is not selected by any ci.yml daemon-test lane", + ], + [ + "apps/web/tests/components/ChatPane.imported-folder-artifacts.test.tsx", + "imported-project artifact fixture paths rendered from in-memory test data", + ], + [ + "apps/web/tests/components/file-viewer-markdown-copy.test.tsx", + "project-relative markdown path inputs used to test URL construction, not repository reads", + ], + [ + "apps/web/tests/utils/inlineMentions.test.ts", + "in-memory mention parser fixture paths; no filesystem access occurs", + ], [ "tools/release/src/release-note/prepare.ts", "docs/CHANGELOG feeds release-note preparation, which runs only in release workflows; @open-design/tools-release tests run in no ci.yml lane", ], + [ + "e2e/tests/packaged-smoke-workflow.test.ts", + "scope-planner and workflow-text assertion fixtures; certain-exempt literals are passed as data and never opened", + ], + [ + "e2e/tests/scripts/product-neutrality.test.ts", + "virtual source path passed to the product-neutrality collector; it does not access the repository file", + ], [ "e2e/tests/scripts/scopes.test.ts", "behavior fixtures for this very check: source snippets passed to the collector as data, never resolved or opened", @@ -81,23 +120,27 @@ function landsInCertainExemptSurface(repositoryPath: string): boolean { ); } -export function isFixtureTolerantPath(repositoryPath: string): boolean { - return ( - /\.test\.tsx?$/.test(repositoryPath) || - repositoryPath.includes("/tests/") || - repositoryPath.startsWith("e2e/") - ); -} - function literalConsumesCertainExemptSurface(fromRepositoryPath: string, literal: string): boolean { if (literal.startsWith("./") || literal.startsWith("../")) { const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(fromRepositoryPath), literal)); return landsInCertainExemptSurface(resolved); } - if (isFixtureTolerantPath(fromRepositoryPath)) return false; return landsInCertainExemptSurface(literal); } +function isSandboxFixtureWriterArgument(node: ts.StringLiteral | ts.NoSubstitutionTemplateLiteral): boolean { + const call = node.parent; + if (!ts.isCallExpression(call) || !call.arguments.includes(node)) return false; + + const callee = call.expression; + const name = ts.isIdentifier(callee) + ? callee.text + : ts.isPropertyAccessExpression(callee) + ? callee.name.text + : undefined; + return name !== undefined && sandboxFixtureWriterNames.has(name); +} + export function collectCertainExemptConsumptionFromSource( repositoryPath: string, source: string, @@ -113,7 +156,10 @@ export function collectCertainExemptConsumptionFromSource( const visit = (node: ts.Node): void => { if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) { - if (literalConsumesCertainExemptSurface(repositoryPath, node.text)) { + if ( + !isSandboxFixtureWriterArgument(node) && + literalConsumesCertainExemptSurface(repositoryPath, node.text) + ) { violations.push({ filePath: repositoryPath, lineNumber: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1, From 723faeab6f5af715424a77604aa9f82ad015ae49 Mon Sep 17 00:00:00 2001 From: PerishCode Date: Fri, 24 Jul 2026 16:26:16 +0800 Subject: [PATCH 4/9] fix(ci): pin the trae-cli consumption exception to the daemon-lane wiring The review round established that apps/daemon/tests/runtimes/trae-cli.test.ts genuinely reads docs/agent-adapters.md. Its exemption from the consumption guard was justified by a wiring fact (the ci.yml daemon lane runs only project-watchers.test.ts) stated as prose, which would rot silently if the lane ever widened. Make the exception conditional on that exact ci.yml needle: when the daemon lane changes, the exemption disappears and the guard reports the read, forcing reclassification instead of trusting a stale rationale. --- scripts/check-certain-exempt-consumption.ts | 28 +++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/scripts/check-certain-exempt-consumption.ts b/scripts/check-certain-exempt-consumption.ts index 2e84ff3a95f..6f74046a532 100644 --- a/scripts/check-certain-exempt-consumption.ts +++ b/scripts/check-certain-exempt-consumption.ts @@ -69,10 +69,11 @@ const allowedConsumers = new Map([ "apps/daemon/tests/project-classifiers.test.ts", "file-kind classifier input; the LICENSE literal is never resolved or opened", ], - [ - "apps/daemon/tests/runtimes/trae-cli.test.ts", - "reads docs/agent-adapters.md from the repository, but this test file is not selected by any ci.yml daemon-test lane", - ], + // NOTE: apps/daemon/tests/runtimes/trae-cli.test.ts is a conditional + // exception added in checkCertainExemptConsumption(), not here: it genuinely + // reads docs/agent-adapters.md, and its exemption holds only while the + // ci.yml daemon lane runs nothing but project-watchers.test.ts. See + // DAEMON_LANE_SINGLE_FILE_NEEDLE below. [ "apps/web/tests/components/ChatPane.imported-folder-artifacts.test.tsx", "imported-project artifact fixture paths rendered from in-memory test data", @@ -103,6 +104,18 @@ const allowedConsumers = new Map([ ], ]); +// apps/daemon/tests/runtimes/trae-cli.test.ts genuinely consumes repository +// docs (docs/agent-adapters.md coverage assertion). It is tolerable only +// because the ci.yml daemon lane currently executes a single test file that is +// not it. That wiring fact is this needle; if the daemon lane ever widens, the +// exception silently disappears and the guard reports the read, forcing the +// reclassification (docs → daemon scope rule, or relocating the consistency +// test to e2e/tests/) instead of letting the entry rot. +const DAEMON_LANE_SINGLE_FILE_NEEDLE = + "run: pnpm --filter @open-design/daemon exec vitest run -c vitest.config.ts tests/project-watchers.test.ts"; + +const CONDITIONAL_CONSUMER = "apps/daemon/tests/runtimes/trae-cli.test.ts"; + type ConsumptionViolation = { filePath: string; lineNumber: number; @@ -205,9 +218,14 @@ async function collectCheckedFiles(directory: string): Promise { export async function checkCertainExemptConsumption(): Promise { const violations: ConsumptionViolation[] = []; + const ciWorkflow = await readFile(path.join(repoRoot, ".github/workflows/ci.yml"), "utf8"); + const conditionallyAllowed = ciWorkflow.includes(DAEMON_LANE_SINGLE_FILE_NEEDLE) + ? new Set([CONDITIONAL_CONSUMER]) + : new Set(); + for (const root of checkedRoots) { for (const repositoryPath of await collectCheckedFiles(path.join(repoRoot, root))) { - if (allowedConsumers.has(repositoryPath)) continue; + if (allowedConsumers.has(repositoryPath) || conditionallyAllowed.has(repositoryPath)) continue; const source = await readFile(path.join(repoRoot, repositoryPath), "utf8"); violations.push(...collectCertainExemptConsumptionFromSource(repositoryPath, source)); } From 47f62d8763928d1a7786a98d0d7d1ddaf9c57ae8 Mon Sep 17 00:00:00 2001 From: PerishCode Date: Fri, 24 Jul 2026 16:28:48 +0800 Subject: [PATCH 5/9] docs(ci): codify exception-precondition binding in the methodology Backfill the pattern the first review round produced: guard allowlist entries justified by local definitional facts may stay prose, but entries justified by remote mutable facts (lane wiring, gate topology) must be conditional on a checked precondition so the exception dies loudly with its premise. Records the trae-cli/daemon-lane needle as the worked example. --- specs/current/ci.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/specs/current/ci.md b/specs/current/ci.md index 3e95f487616..54c96dca41c 100644 --- a/specs/current/ci.md +++ b/specs/current/ci.md @@ -64,10 +64,28 @@ Requirements: 4. **Goldens updated, divergence pinned.** The golden that changes is the proof of the behavior change; the goldens that do not change are the proof of its containment. +5. **Exceptions bind to checkable preconditions.** Every guard allowlist entry + is a claim, and claims split by what justifies them. A *local, definitional* + fact ("this string is passed as data to a pure function, never opened") may + stay prose — it can only be falsified by editing the allowlisted file + itself, which puts the entry in front of a reviewer. A *remote, mutable* + fact ("that lane doesn't run this file", "that workflow is outside the + gate") must not be trusted as prose: the guard verifies the fact and drops + the exception the moment it stops holding, so the failure mode is a loud + guard report at the change that broke the premise — not a rationale that + rotted silently years earlier. Worked example: the consumption guard + tolerates `apps/daemon/tests/runtimes/trae-cli.test.ts` reading + `docs/agent-adapters.md` only while `ci.yml`'s daemon lane still runs + nothing but `project-watchers.test.ts` — the exception is conditional on + that exact needle (`DAEMON_LANE_SINGLE_FILE_NEEDLE` in + `scripts/check-certain-exempt-consumption.ts`); widening the lane revives + the violation and forces reclassification. Demotion is not yet codified (open question), with one hard rule already in force: if a guard check is deleted or renamed, the rule-table invariant test -fails CI — a certain rule can never silently outlive its guard. +fails CI — a certain rule can never silently outlive its guard. Rule five is +the same principle one level down: an exception can never silently outlive +its premise. ## Promotion #1 (2026-07): the certain-exempt core From 3682778686b5aaa02ea1eccc3e68cff6ac1fa6ff Mon Sep 17 00:00:00 2001 From: Looper Date: Fri, 24 Jul 2026 16:51:34 +0800 Subject: [PATCH 6/9] fix(ci): fold static paths in exempt consumption guard Generated-By: looper 0.11.0 (runner=fixer, agent=codex) --- e2e/tests/scripts/scopes.test.ts | 30 +++++++- scripts/check-certain-exempt-consumption.ts | 76 +++++++++++++++++---- 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/e2e/tests/scripts/scopes.test.ts b/e2e/tests/scripts/scopes.test.ts index a9274d31c03..931b99368a1 100644 --- a/e2e/tests/scripts/scopes.test.ts +++ b/e2e/tests/scripts/scopes.test.ts @@ -536,7 +536,7 @@ test("merge-queue threshold trusts the certain-exempt core without escalation", }); }); -test("the consumption guard distinguishes repo readers from sandbox fixture writers", async () => { +test("the consumption guard folds repository paths while allowing sandbox fixture writers", async () => { const { collectCertainExemptConsumptionFromSource } = await import( "../../../scripts/check-certain-exempt-consumption.ts" ); @@ -564,10 +564,36 @@ test("the consumption guard distinguishes repo readers from sandbox fixture writ "docs/agent-adapters.md", ]); - // Known project writers resolve the same-looking path inside a sandbox. + const splitJoin = collectCertainExemptConsumptionFromSource( + "apps/daemon/src/example.ts", + `await readFile(path.join(repoRoot, "docs", "agent-adapters.md"));`, + ); + assert.deepEqual(splitJoin.map((violation) => violation.literal), [ + `path.join(repoRoot, "docs", "agent-adapters.md")`, + ]); + + const splitResolve = collectCertainExemptConsumptionFromSource( + "apps/daemon/src/example.ts", + `await readFile(path.resolve("docs", "agent-adapters.md"));`, + ); + assert.deepEqual(splitResolve.map((violation) => violation.literal), [ + `path.resolve("docs", "agent-adapters.md")`, + ]); + + const interpolatedPrefix = collectCertainExemptConsumptionFromSource( + "apps/daemon/src/example.ts", + "await readFile(`docs/${adapter}.md`);", + ); + assert.deepEqual(interpolatedPrefix.map((violation) => violation.literal), [ + "`docs/${adapter}.md`", + ]); + + // Known project writers resolve the same-looking paths inside a sandbox. for (const source of [ `await writeProjectFile("docs/empty.md", "");`, `await fixture.writeProjectFile("docs/empty.md", "");`, + `await writeProjectFile(path.join("docs", "empty.md"), "");`, + "await writeProjectFile(`docs/${name}.md`, \"\");", ]) { const fixtureWrite = collectCertainExemptConsumptionFromSource( "apps/daemon/tests/example.test.ts", diff --git a/scripts/check-certain-exempt-consumption.ts b/scripts/check-certain-exempt-consumption.ts index 6f74046a532..1c9662daa5f 100644 --- a/scripts/check-certain-exempt-consumption.ts +++ b/scripts/check-certain-exempt-consumption.ts @@ -19,8 +19,9 @@ import { CERTAIN_EXEMPT_EXACT, CERTAIN_EXEMPT_PREFIXES } from "./scopes.ts"; // it is an argument to a known sandbox-fixture writer. Test files can also // contain repo-root helpers, so exempting them wholesale would hide real // consumption. -// Template literals with substitutions are not statically resolvable and are -// out of scope. +// - path.join/path.resolve expressions made from static segments (optionally +// anchored at repoRoot), and template literals whose static prefix already +// enters an exempt directory: flagged as the same repository dependency. // // Deliberately outside the checked surface: // - root `scripts/` — floor-owned code. Preflight and workspace unit tests are @@ -141,7 +142,7 @@ function literalConsumesCertainExemptSurface(fromRepositoryPath: string, literal return landsInCertainExemptSurface(literal); } -function isSandboxFixtureWriterArgument(node: ts.StringLiteral | ts.NoSubstitutionTemplateLiteral): boolean { +function isSandboxFixtureWriterArgument(node: ts.Expression): boolean { const call = node.parent; if (!ts.isCallExpression(call) || !call.arguments.includes(node)) return false; @@ -154,6 +155,51 @@ function isSandboxFixtureWriterArgument(node: ts.StringLiteral | ts.NoSubstituti return name !== undefined && sandboxFixtureWriterNames.has(name); } +type StaticPath = { + path: string; + display: string; + prefixOnly: boolean; +}; + +function staticPathFromExpression(node: ts.Expression, sourceFile: ts.SourceFile): StaticPath | undefined { + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) { + return { path: node.text, display: node.text, prefixOnly: false }; + } + + if (ts.isTemplateExpression(node)) { + const prefix = node.head.text; + if (!CERTAIN_EXEMPT_PREFIXES.some((exemptPrefix) => prefix.startsWith(exemptPrefix))) { + return undefined; + } + return { path: prefix, display: node.getText(sourceFile), prefixOnly: true }; + } + + if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) return undefined; + const callee = node.expression; + if ( + !ts.isIdentifier(callee.expression) || + callee.expression.text !== "path" || + (callee.name.text !== "join" && callee.name.text !== "resolve") + ) { + return undefined; + } + + const segments: string[] = []; + for (const [index, argument] of node.arguments.entries()) { + if (index === 0 && ts.isIdentifier(argument) && argument.text === "repoRoot") continue; + const segment = staticPathFromExpression(argument, sourceFile); + if (segment === undefined || segment.prefixOnly) return undefined; + segments.push(segment.path); + } + if (segments.length === 0) return undefined; + + return { + path: path.posix.join(...segments), + display: node.getText(sourceFile), + prefixOnly: false, + }; +} + export function collectCertainExemptConsumptionFromSource( repositoryPath: string, source: string, @@ -168,16 +214,20 @@ export function collectCertainExemptConsumptionFromSource( const violations: ConsumptionViolation[] = []; const visit = (node: ts.Node): void => { - if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) { - if ( - !isSandboxFixtureWriterArgument(node) && - literalConsumesCertainExemptSurface(repositoryPath, node.text) - ) { - violations.push({ - filePath: repositoryPath, - lineNumber: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1, - literal: node.text, - }); + if (ts.isExpression(node)) { + const candidate = staticPathFromExpression(node, sourceFile); + if (candidate !== undefined && !isSandboxFixtureWriterArgument(node)) { + const consumes = candidate.prefixOnly + ? CERTAIN_EXEMPT_PREFIXES.some((prefix) => candidate.path.startsWith(prefix)) + : literalConsumesCertainExemptSurface(repositoryPath, candidate.path); + if (consumes) { + violations.push({ + filePath: repositoryPath, + lineNumber: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1, + literal: candidate.display, + }); + return; + } } } ts.forEachChild(node, visit); From 6849e86178f83c037b2c86acba0dc97aaafce02a Mon Sep 17 00:00:00 2001 From: Looper Date: Fri, 24 Jul 2026 16:58:00 +0800 Subject: [PATCH 7/9] fix(ci): require exclusive daemon test wiring Generated-By: looper 0.11.0 (runner=fixer, agent=codex) --- e2e/tests/scripts/scopes.test.ts | 27 +++++++++++++++++++ scripts/check-certain-exempt-consumption.ts | 30 ++++++++++++++++----- specs/current/ci.md | 2 +- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/e2e/tests/scripts/scopes.test.ts b/e2e/tests/scripts/scopes.test.ts index 931b99368a1..3655e201bb4 100644 --- a/e2e/tests/scripts/scopes.test.ts +++ b/e2e/tests/scripts/scopes.test.ts @@ -610,6 +610,33 @@ test("the consumption guard folds repository paths while allowing sandbox fixtur assert.deepEqual(prose, []); }); +test("the consumption guard requires the narrow daemon test command to be exclusive", async () => { + const { daemonTestInvocationsFromWorkflow, workflowRunsOnlyAllowedDaemonTest } = await import( + "../../../scripts/check-certain-exempt-consumption.ts" + ); + const narrowCommand = + "pnpm --filter @open-design/daemon exec vitest run -c vitest.config.ts tests/project-watchers.test.ts"; + const narrowOnly = ` +jobs: + daemon_tests: + steps: + - name: Daemon workspace tests + run: ${narrowCommand} +`; + assert.deepEqual(daemonTestInvocationsFromWorkflow(narrowOnly), [narrowCommand]); + assert.equal(workflowRunsOnlyAllowedDaemonTest(narrowOnly), true); + + const narrowPlusBroader = `${narrowOnly} + - name: Full daemon suite + run: pnpm --filter @open-design/daemon test +`; + assert.deepEqual(daemonTestInvocationsFromWorkflow(narrowPlusBroader), [ + narrowCommand, + "pnpm --filter @open-design/daemon test", + ]); + assert.equal(workflowRunsOnlyAllowedDaemonTest(narrowPlusBroader), false); +}); + test("the rule table classifies every file: no path escapes both fallbacks", async () => { const { scopeRules, matchesRuleMatch } = await import("../../../scripts/scopes.ts"); const samples = [ diff --git a/scripts/check-certain-exempt-consumption.ts b/scripts/check-certain-exempt-consumption.ts index 1c9662daa5f..93430cef275 100644 --- a/scripts/check-certain-exempt-consumption.ts +++ b/scripts/check-certain-exempt-consumption.ts @@ -74,7 +74,7 @@ const allowedConsumers = new Map([ // exception added in checkCertainExemptConsumption(), not here: it genuinely // reads docs/agent-adapters.md, and its exemption holds only while the // ci.yml daemon lane runs nothing but project-watchers.test.ts. See - // DAEMON_LANE_SINGLE_FILE_NEEDLE below. + // DAEMON_LANE_ALLOWED_TEST_COMMAND below. [ "apps/web/tests/components/ChatPane.imported-folder-artifacts.test.tsx", "imported-project artifact fixture paths rendered from in-memory test data", @@ -107,16 +107,32 @@ const allowedConsumers = new Map([ // apps/daemon/tests/runtimes/trae-cli.test.ts genuinely consumes repository // docs (docs/agent-adapters.md coverage assertion). It is tolerable only -// because the ci.yml daemon lane currently executes a single test file that is -// not it. That wiring fact is this needle; if the daemon lane ever widens, the -// exception silently disappears and the guard reports the read, forcing the +// because ci.yml currently executes exactly one daemon test command, targeting +// a single test file that is not it. If the daemon test inventory ever widens, +// the exception disappears and the guard reports the read, forcing // reclassification (docs → daemon scope rule, or relocating the consistency // test to e2e/tests/) instead of letting the entry rot. -const DAEMON_LANE_SINGLE_FILE_NEEDLE = - "run: pnpm --filter @open-design/daemon exec vitest run -c vitest.config.ts tests/project-watchers.test.ts"; +const DAEMON_LANE_ALLOWED_TEST_COMMAND = + "pnpm --filter @open-design/daemon exec vitest run -c vitest.config.ts tests/project-watchers.test.ts"; const CONDITIONAL_CONSUMER = "apps/daemon/tests/runtimes/trae-cli.test.ts"; +export function daemonTestInvocationsFromWorkflow(workflow: string): string[] { + return workflow + .split("\n") + .map((line) => line.trim()) + .filter((line) => !line.startsWith("#")) + .map((line) => line.replace(/^run:\s*/, "")) + .filter((line) => + /\bpnpm\s+--filter\s+@open-design\/daemon\s+(?:test\b|(?:exec\s+)?vitest\b)/.test(line), + ); +} + +export function workflowRunsOnlyAllowedDaemonTest(workflow: string): boolean { + const invocations = daemonTestInvocationsFromWorkflow(workflow); + return invocations.length === 1 && invocations[0] === DAEMON_LANE_ALLOWED_TEST_COMMAND; +} + type ConsumptionViolation = { filePath: string; lineNumber: number; @@ -269,7 +285,7 @@ export async function checkCertainExemptConsumption(): Promise { const violations: ConsumptionViolation[] = []; const ciWorkflow = await readFile(path.join(repoRoot, ".github/workflows/ci.yml"), "utf8"); - const conditionallyAllowed = ciWorkflow.includes(DAEMON_LANE_SINGLE_FILE_NEEDLE) + const conditionallyAllowed = workflowRunsOnlyAllowedDaemonTest(ciWorkflow) ? new Set([CONDITIONAL_CONSUMER]) : new Set(); diff --git a/specs/current/ci.md b/specs/current/ci.md index 54c96dca41c..95c90afd4e1 100644 --- a/specs/current/ci.md +++ b/specs/current/ci.md @@ -77,7 +77,7 @@ Requirements: tolerates `apps/daemon/tests/runtimes/trae-cli.test.ts` reading `docs/agent-adapters.md` only while `ci.yml`'s daemon lane still runs nothing but `project-watchers.test.ts` — the exception is conditional on - that exact needle (`DAEMON_LANE_SINGLE_FILE_NEEDLE` in + that exclusive invocation check (`workflowRunsOnlyAllowedDaemonTest` in `scripts/check-certain-exempt-consumption.ts`); widening the lane revives the violation and forces reclassification. From 7852a1e2212db5a5422a523cf7bc5ceaef94fa83 Mon Sep 17 00:00:00 2001 From: Looper Date: Fri, 24 Jul 2026 17:12:36 +0800 Subject: [PATCH 8/9] fix(ci): recognize daemon pnpm selector forms Generated-By: looper 0.11.0 (runner=fixer, agent=codex) --- e2e/tests/scripts/scopes.test.ts | 22 ++++++++++++++------- scripts/check-certain-exempt-consumption.ts | 12 ++++++++--- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/e2e/tests/scripts/scopes.test.ts b/e2e/tests/scripts/scopes.test.ts index 3655e201bb4..09de1a7d3b7 100644 --- a/e2e/tests/scripts/scopes.test.ts +++ b/e2e/tests/scripts/scopes.test.ts @@ -626,15 +626,23 @@ jobs: assert.deepEqual(daemonTestInvocationsFromWorkflow(narrowOnly), [narrowCommand]); assert.equal(workflowRunsOnlyAllowedDaemonTest(narrowOnly), true); - const narrowPlusBroader = `${narrowOnly} + for (const broaderCommand of [ + "pnpm --filter @open-design/daemon test", + "pnpm -F @open-design/daemon test", + "pnpm --filter=@open-design/daemon test", + "pnpm --dir apps/daemon test", + "pnpm -C apps/daemon test", + ]) { + const narrowPlusBroader = `${narrowOnly} - name: Full daemon suite - run: pnpm --filter @open-design/daemon test + run: ${broaderCommand} `; - assert.deepEqual(daemonTestInvocationsFromWorkflow(narrowPlusBroader), [ - narrowCommand, - "pnpm --filter @open-design/daemon test", - ]); - assert.equal(workflowRunsOnlyAllowedDaemonTest(narrowPlusBroader), false); + assert.deepEqual(daemonTestInvocationsFromWorkflow(narrowPlusBroader), [ + narrowCommand, + "pnpm --filter @open-design/daemon test", + ]); + assert.equal(workflowRunsOnlyAllowedDaemonTest(narrowPlusBroader), false); + } }); test("the rule table classifies every file: no path escapes both fallbacks", async () => { diff --git a/scripts/check-certain-exempt-consumption.ts b/scripts/check-certain-exempt-consumption.ts index 93430cef275..3afc4eb711a 100644 --- a/scripts/check-certain-exempt-consumption.ts +++ b/scripts/check-certain-exempt-consumption.ts @@ -123,9 +123,15 @@ export function daemonTestInvocationsFromWorkflow(workflow: string): string[] { .map((line) => line.trim()) .filter((line) => !line.startsWith("#")) .map((line) => line.replace(/^run:\s*/, "")) - .filter((line) => - /\bpnpm\s+--filter\s+@open-design\/daemon\s+(?:test\b|(?:exec\s+)?vitest\b)/.test(line), - ); + .map((line) => { + const invocation = line.match( + /\bpnpm\s+(?:(?:--filter(?:=|\s+)|-F(?:=|\s+))@open-design\/daemon|(?:--dir(?:=|\s+)|-C(?:=|\s+))apps\/daemon)\s+((?:test\b|(?:exec\s+)?vitest\b).*)/, + ); + return invocation === null + ? undefined + : `pnpm --filter @open-design/daemon ${invocation[1]}`; + }) + .filter((line): line is string => line !== undefined); } export function workflowRunsOnlyAllowedDaemonTest(workflow: string): boolean { From 5935905f6294622e17ac1175eeb60d7f07285ca4 Mon Sep 17 00:00:00 2001 From: Looper Date: Fri, 24 Jul 2026 17:34:46 +0800 Subject: [PATCH 9/9] fix(ci): tokenize daemon workflow test commands Generated-By: looper 0.11.0 (runner=fixer, agent=codex) --- e2e/tests/scripts/scopes.test.ts | 8 +- scripts/check-certain-exempt-consumption.ts | 114 +++++++++++++++++--- 2 files changed, 107 insertions(+), 15 deletions(-) diff --git a/e2e/tests/scripts/scopes.test.ts b/e2e/tests/scripts/scopes.test.ts index 09de1a7d3b7..900a745a29c 100644 --- a/e2e/tests/scripts/scopes.test.ts +++ b/e2e/tests/scripts/scopes.test.ts @@ -632,14 +632,20 @@ jobs: "pnpm --filter=@open-design/daemon test", "pnpm --dir apps/daemon test", "pnpm -C apps/daemon test", + "pnpm --filter @open-design/daemon run test", + "pnpm --silent --filter @open-design/daemon test", + "pnpm -r --filter @open-design/daemon test", ]) { const narrowPlusBroader = `${narrowOnly} - name: Full daemon suite run: ${broaderCommand} `; + const expectedBroaderInvocation = broaderCommand.includes("run test") + ? "pnpm --filter @open-design/daemon run test" + : "pnpm --filter @open-design/daemon test"; assert.deepEqual(daemonTestInvocationsFromWorkflow(narrowPlusBroader), [ narrowCommand, - "pnpm --filter @open-design/daemon test", + expectedBroaderInvocation, ]); assert.equal(workflowRunsOnlyAllowedDaemonTest(narrowPlusBroader), false); } diff --git a/scripts/check-certain-exempt-consumption.ts b/scripts/check-certain-exempt-consumption.ts index 3afc4eb711a..299fe03627a 100644 --- a/scripts/check-certain-exempt-consumption.ts +++ b/scripts/check-certain-exempt-consumption.ts @@ -117,21 +117,107 @@ const DAEMON_LANE_ALLOWED_TEST_COMMAND = const CONDITIONAL_CONSUMER = "apps/daemon/tests/runtimes/trae-cli.test.ts"; +function workflowRunBodies(workflow: string): string[] { + const runBodies: string[] = []; + const lines = workflow.split("\n"); + + for (let index = 0; index < lines.length; index += 1) { + const match = lines[index]?.match(/^(\s*)(?:-\s+)?run:\s*(.*)$/); + if (match === null || match === undefined) continue; + const indentation = match[1]?.length ?? 0; + const value = match[2]?.trim() ?? ""; + if (!/^[|>][+-]?$/.test(value)) { + runBodies.push(value); + continue; + } + + const bodyLines: string[] = []; + while (index + 1 < lines.length) { + const nextLine = lines[index + 1] ?? ""; + const nextIndentation = nextLine.match(/^\s*/)?.[0].length ?? 0; + if (nextLine.trim() !== "" && nextIndentation <= indentation) break; + index += 1; + bodyLines.push(nextLine.slice(Math.min(nextIndentation, indentation + 2))); + } + runBodies.push(bodyLines.join(value.startsWith(">") ? " " : "\n")); + } + + return runBodies; +} + +function shellCommands(body: string): string[][] { + const commands: string[][] = []; + let command: string[] = []; + let token = ""; + let quote: "'" | '"' | undefined; + let escaped = false; + + const finishToken = (): void => { + if (token !== "") command.push(token); + token = ""; + }; + const finishCommand = (): void => { + finishToken(); + if (command.length > 0) commands.push(command); + command = []; + }; + + for (const character of body) { + if (escaped) { + token += character; + escaped = false; + } else if (character === "\\" && quote !== "'") { + escaped = true; + } else if (quote !== undefined) { + if (character === quote) quote = undefined; + else token += character; + } else if (character === "'" || character === '"') { + quote = character; + } else if (/\s/.test(character)) { + finishToken(); + if (character === "\n") finishCommand(); + } else if (character === ";" || character === "|" || character === "&") { + finishCommand(); + } else { + token += character; + } + } + finishCommand(); + return commands; +} + export function daemonTestInvocationsFromWorkflow(workflow: string): string[] { - return workflow - .split("\n") - .map((line) => line.trim()) - .filter((line) => !line.startsWith("#")) - .map((line) => line.replace(/^run:\s*/, "")) - .map((line) => { - const invocation = line.match( - /\bpnpm\s+(?:(?:--filter(?:=|\s+)|-F(?:=|\s+))@open-design\/daemon|(?:--dir(?:=|\s+)|-C(?:=|\s+))apps\/daemon)\s+((?:test\b|(?:exec\s+)?vitest\b).*)/, - ); - return invocation === null - ? undefined - : `pnpm --filter @open-design/daemon ${invocation[1]}`; - }) - .filter((line): line is string => line !== undefined); + const invocations: string[] = []; + + for (const tokens of workflowRunBodies(workflow).flatMap(shellCommands)) { + const pnpmIndex = tokens.indexOf("pnpm"); + if (pnpmIndex === -1) continue; + const pnpmTokens = tokens.slice(pnpmIndex + 1); + const selectsDaemon = pnpmTokens.some( + (token, index) => + ((token === "--filter" || token === "-F") && pnpmTokens[index + 1] === "@open-design/daemon") || + token === "--filter=@open-design/daemon" || + token === "-F=@open-design/daemon" || + ((token === "--dir" || token === "-C") && pnpmTokens[index + 1] === "apps/daemon") || + token === "--dir=apps/daemon" || + token === "-C=apps/daemon", + ); + if (!selectsDaemon) continue; + + const testIndex = pnpmTokens.findIndex( + (token, index) => + token === "test" || + token === "vitest" || + (token === "exec" && pnpmTokens[index + 1] === "vitest") || + (token === "run" && pnpmTokens[index + 1] === "test"), + ); + if (testIndex === -1) continue; + invocations.push( + `pnpm --filter @open-design/daemon ${pnpmTokens.slice(testIndex).join(" ")}`, + ); + } + + return invocations; } export function workflowRunsOnlyAllowedDaemonTest(workflow: string): boolean {