Repository navigation
docs(testing): pin which layer runs every suite, and check it (#749) - #755
Conversation
Nothing said which test belonged to which gate, so a suite could run everywhere or nowhere. `docs/testing-layers.json` now maps every test file to L1 (commit), L2 (merge) or `none` with a reason, and gives every workflow in `.github/workflows/` a row with its kind, its layer and whether it gates a merge. `docs/testing-layers.md` explains the three layers and generates its tables from the manifest. The two kinds of row are both enforced. A suite row owns test paths; a workflow row is checked against the workflow file that exists, which is the row a test-path check could never verify. Within a workflow row the kind says what it is: `gate` runs the suites or judges an entry, `workflow-only` is a layer with no test file of its own (`docker`, the compat matrix, `live-e2e`, `label-hygiene`), and `not-a-layer` is a workflow that runs no tests, with the reason written out. `.github/scripts/testing-layers.mjs --check` fails on a test file or a workflow nobody classified, and on a doc that no longer matches the manifest; the `package` job runs it, so a new `*.test.mjs` cannot land unclassified.
195d524 to
8b559c1
Compare
|
Verdict: PASS at 8b559c1 The two required reds hold on the real manifest, not only in the fixtures, and the workflow-only row is checked against the workflow file as asked. CI at this tip is 9/9 green and Two non-blocking notes, neither about this diff's behaviour:
The count moved from the body's 307 to 309 at this tip: the rebase onto Ran:
Not run: the whole |
…hinkFlowLab#739) (ThinkFlowLab#760) Written-by: Dev2 Fixes ThinkFlowLab#739 Tip SHA: f61f459 ## Reproduce A merged pull request keeps no trace of the verdict that let it in. `agent-pr` stays on the list, and the conclusion lives only in a comment thread: to answer "was this reviewed?" you open the pull request and scroll. On `main` today there is no `pass` label at all (`.github/labels.json` has 12 entries), so `gh pr list --label pass` answers nothing, and after a branch is deleted the review record is unreadable from the list. ## What changed - `.github/labels.json`: a `pass` label (`#006B75`, "Reviewer PASS verdict at the merged head; set by pass-label, never removed"), so `sync-labels.mjs` creates it. The count assertion in `sync-labels.test.mjs` moves from 12 to 13 and `pass` joins the names the test requires. - `.github/scripts/pass-label.mjs` (new): `verdictOf(body, { writers, association })` reads a conclusion with `parseReport` from `review-gate.mjs` — the parser the gates already share — and returns it only when the verdict is `PASS`, the comment's `author_association` is `OWNER`/`MEMBER`/`COLLABORATOR`, and the `Written-by:` name is in the policy's `writers`. A body whose first line is `Correction from …` never counts, whatever `Verdict:` it quotes. `planComment` decides one event; `planBackfill` decides the merged ones from raw API pages; `main` runs either mode against `gh`. - `.github/workflows/pass-label.yml` (new): on `issue_comment` (created) it labels the pull request the conclusion was posted on, and `workflow_dispatch` runs `--backfill`. The comment body is only parsed and never run; the code that runs is checked out from `main`, so an untrusted comment cannot bring code with it. Permissions are `contents: read`, `issues: write`, `pull-requests: write`. - `docs/testing-layers.json` + the generated `docs/testing-layers.md`: `pass-label.yml` gets its workflow row (`workflow-only`, gates nothing), so the layer check ThinkFlowLab#755 added stays honest about the new workflow. - The label is **only ever added**. A later INC on a newer tip, or a moved tip, does not remove it — the label records that a PASS was given, and `hold` stays the one manual stop. A test asserts the script contains no removal call at all. ## What I ran At this tip `f61f4595b5f3c06183623ac30d3e029b8c136ba7` (base `e15d14c3`, 7 files, +303/−2): - `node --test .github/scripts/*.test.mjs` → **226 tests, 226 pass, 0 fail** (baseline before this diff: 219). - `node --test .github/scripts/pass-label.test.mjs` → **6/6**: a PASS labels; INC, `Correction from …`, a non-member association and an unknown `Written-by:` name never label; a `Replaces:` PASS labels like any PASS; a second run adds nothing; backfill picks the merged-already-on-a-PASS one and leaves the INC-only, already-labelled, still-open and PASS-at-another-tip rows alone; a 12-character tip names the head. - `node .github/scripts/testing-layers.mjs --check` → `310 test file(s) in 12 workflow(s)`, all classified (the new workflow and the new test file are both listed). - `node scripts/ds-bot-patches.mjs check` → three ok (untouched by this diff). - `docs/testing-layers.md` regenerated with `--write`. ## Red without the source change - Delete the `Correction` guard in `verdictOf` → the correction case fails, because `parseReport` finds the `Verdict: PASS` the correction quotes. - Drop the `writer`/`writers` check → the unknown-`Written-by:` case fails (and a passer-by with `association: NONE` still fails on its own rule). - Drop the `already labelled` check in `planComment` → the "second run adds nothing" case fails. - Drop the `namesHead` check in `planBackfill` → the PASS-at-another-tip row is labelled too, so the backfill case sees `[11, 15]` instead of `[11]`. - Add a `DELETE` call to the script → the "closed record" case fails. ## Not run - The two workflows themselves have never executed: `pass-label.yml` does not exist on `main` yet. The first trigger is the next PASS comment after merge; `workflow_dispatch` runs the backfill, which I did **not** run from here — labelling 40+ merge records is the workflow's job, not a by-hand write. - `actionlint` is not installed on this box; `package` runs the real one on this PR. - `npm run verify` as a whole; nothing under `core/`, `plugins/` or `patches/` changes. ## Risk **T3** (`.github/**` and `docs/testing-layers.json`), so green `tests-ok` at the tip plus a PASS at this tip, then the queue. The write is a single `POST /issues/<n>/labels` with one label, from a workflow that has `issues: write` and nothing else. Three things could go wrong and each is bounded: a mistake labels a pull request `pass`; a pass-through of untrusted text would at worst add a label (it cannot remove anything, and it never gates a merge — `merge-gate` reads the conclusion itself at the entry); and the backfill could label a pull request whose merged head carried a PASS for the wrong reason — the tip check reduces that to a PASS that names the merged head, which is the record the label is supposed to keep. If the label turns out to be unwanted on an old pull request, removing it is a human action, like `hold`.
Written-by: Dev2
Fixes #749
Tip SHA: 8b559c1
Reproduce
Nothing in the repository said which test belongs to which gate, so the answer to "is this covered before it merges?" was only readable from the YAML: a new
*.test.mjsthat no workflow globs runs nowhere and nobody notices, and a new workflow that gates nothing is indistinguishable from one that does. Onmain, addmisc/example.test.js(or.github/workflows/fresh-gate.yml) and nothing fails; with this PR the check exits 1 and names the file.What changed
docs/testing-layers.json(new): the manifest. Three layer definitions (L1 commit, L2 merge, L3 self-test) with what each must catch, what it must not depend on, its budget and its command; then the two kinds of row:core,plugins/social,plugins/tree,plugins/timeline, the repo scripts,interop, each with the layer that runs it and the glob it owns; plusnonerows with a reason forpatches/ds-bot/files/**(generated copies, compared byds-bot-patches check),ds-bot/test/**(pristine upstream, never run directly) anddev/live-e2e/test/**(offline unit tests for the live harness that no CI gate runs today)..github/workflows/, each with akind, its layer and whether it gates a merge. The kind is the acceptance's second kind of row:gateruns the suites or judges a queue entry (test.yml,merge-gate.yml);workflow-onlyis a layer that carries no test file of its own —docker.yml(L2),live-e2e.yml(L3),nightly.yml,label-hygiene.yml;not-a-layeris a workflow that runs no tests and gates nothing (review-gate,risk-tier,controller,audit-merges,release), with the reason written out.docs/testing-layers.md(new): the three layers in prose (what each must catch, must not depend on, budget, command, what runs it), the rule that a case may only enter a layer that can run it, an L3 pointer todocs/self-test.md— Self-test case catalog with stable ids — to land as docs/self-test.md (current cases, gaps, planned) #742 owns that file and this PR neither creates nor rewrites it — and the two generated tables..github/scripts/testing-layers.mjs(new):--checkand--write. It fails on a test file no suite row covers, on a workflow with no row, on a row naming a workflow that does not exist, on an unknown layer or workflow kind, on agaterow without a role, on aworkflow-onlyrow that says neither what it guarantees nor why it is not a layer, on anonerow without a reason, and on a doc whose generated block no longer matches the manifest. Workflow-only rows are validated against the workflow file itself, which is exactly the row a test-path check could never verify..github/scripts/testing-layers.test.mjs(new): 7 cases — the real repository classifies everything; dropping theplugins/timelinerow names its test file; dropping thedockerrow namesdocker.yml; a new unclassified test file and a new workflow are both named; anonerow without a reason, an unknown layer, an unknown workflow kind, aworkflow-onlyrow with no trigger and a stale doc are reported; the doc's tables equalrenderTables(manifest); the glob matcher covers one segment, many segments and a trailing**..github/workflows/test.yml: one step inpackage—node .github/scripts/testing-layers.mjs --check. No new required status check and no new workflow:packagealready aggregates intotests-ok, so L2 stays exactly the checksmainrequires today.What I ran
At this tip
8b559c11f9675a0c236c1551023f1d9bfc819c5c(base1bda9b2a, 5 files, +651/−0):node .github/scripts/testing-layers.mjs --check→307 test file(s) in 11 workflow(s)/every test file and every workflow is classified, exit 0.node --test .github/scripts/*.test.mjs→ 220 tests, 220 pass, 0 fail. Baseline without the new test file in the same tree: 213/213.node --test .github/scripts/testing-layers.test.mjs→ 7/7.arch-docs: OK (5 changed file(s));changelog.mjs check→ 23 fragments well formed (no fragment here: nothing a user sees changes); all 11 workflows parse (js-yaml), including the editedtest.yml.195d5240) had already run the newpackagestep in CI:packageandmerge-gategreen there, so the check does run in the job as wired.Red without the source change
Both required reds run against the real manifest and repository, not only in the fixture, and both are re-run on this tip:
plugins/timelinesuite row → exit 1, and all 11 test files of that suite are named (unclassified test file: plugins/timeline/test/alerts.test.js, …).dockerworkflow row → exit 1,unclassified workflow: .github/workflows/docker.yml— the workflow-only row that owns no test path at all.misc/*.test.js, a new.github/workflows/fresh-gate.yml, an unknown workflow kind and aworkflow-onlyrow with no trigger, so a regression failspackageeven if the manifest is edited but the check stops detecting.Not run
actionlintis not installed on this box; the YAML parse and the existingpackagelint step stand in for it, and CI runs the realactionlinton this PR.packagestep itself only runs in CI — locally I ran the identical command by hand.npm run verifyas a whole and thecore/pluginssuites: this diff touches nocore/,plugins/orpatches/file, so those suites cannot be affected;ds-bot-patches checkis likewise untouched.Risk
T3:
.github/**is T3 in.github/agent/risk-rules.json, and this adds a step to thepackagejob. Greentests-okat the tip plus a PASS at this tip, then the queue.What the change can break: the new
packagestep fails the job when the manifest and the repository disagree, which is the point — but a false positive would block every pull request, so the check is narrow (it names the file, it does not judge content) and its own 7 cases pin both directions. Nothing else intest.ymlchanges, no permissions change, no new workflow, no new required check, and the manifest is data: deleting a row makes the check red rather than making a suite silently disappear.The one judgement a reviewer should look at:
dev/live-e2e/test/**is classifiednonerather than L1. Those cases are offline (a fake adapter, no model), so they belong in L1, but no CI gate runs them today and moving them into L1 is out of scope here. The row's reason says exactly that, and the L3 workflow row (live-e2e.yml) is separate.