mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #239] [MERGED] feat(groom): deny suites/*/cases/*.y*ml in the builder patch policy (BE-9612) #247
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#247
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/239
Author: @mattmillerai
Created: 8/27/2026
Status: ✅ Merged
Merged: 8/28/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-9612-groom-deny-dataset-of-record-paths📝 Commits (4)
916d87cfeat(groom): deny suites//cases/.y*ml dataset-of-record paths in the builder patch policy (BE-9612)01d9e71fix(groom): close dataset-of-record deny bypasses from review (BE-9612)32eb35dfix(groom): deny suites-tree symlinks by mode; widen to zero mid segments; --no-renames on numstat (BE-9612)31ced91test(groom): pin the rename-record fail-closed invariant in parse_raw_z (BE-9612)📊 Changes
4 files changed (+373 additions, -47 deletions)
View changed files
📝
.github/groom/README.md(+35 -5)📝
.github/groom/patch_policy.py(+149 -22)📝
.github/groom/tests/test_patch_policy.py(+159 -10)📝
.github/workflows/groom.yml(+30 -10)📄 Description
ELI-5
Groom's opt-in auto-builder can write a patch against any tracked file, and the only thing standing between that patch and an auto-opened PR is a deny-list of paths a human must author. Until now that list only covered CI-privileged paths (workflows, lockfiles, build config). One consumer repo just turned the builder on, and its crown jewels are graded eval case files under a
suites/**/cases/tree — files whose merge publishes immutable versions and whose meaning is reserved for the dataset owner. This teaches the deny-list about that family: a builder patch touching those paths is downgraded to a filed issue, so a human authors the change. Nothing is dropped — the finding still gets filed.What is here
.github/groom/patch_policy.py— a separately named_DATASET_OF_RECORD_REGEXEStuple folded into_PATTERNwith the same segment-anchored,$-terminated, IGNORECASE wrapping as_PATH_SUFFIXES:suites/(?:[^/]+/)+cases/(?s:.*)\.ya?ml— case files, any depth above and belowcases/, empty stems and newline-bearing names included.suites/(?:[^/]+/)+cases— a change whose final segment iscasesunder a suite. git tracks no directories, so that path shape is a file or a symlink: the indirection that would otherwise point a globbing importer at an undenied tree._is_deniednow tests the unsplit path as well as each newline-split line, so a match may straddle a raw newline (the line-split alone cannot satisfy any$-anchored branch across one). Strictly additive.denied_pathsdocstrings no longer promise "CI-privileged" for a list that now returns two classes..github/workflows/groom.yml—--no-renameson the policy producer. With git's default rename detection,diff --cached --name-onlyreports only a rename's DESTINATION, so a patch that moved a case out ofcases/— the retirement this class exists to gate — showed the policy an empty list and would have auto-opened a PR. Plus the bail message, the::warningline, and the header comment naming both families..github/groom/tests/test_patch_policy.py—DatasetOfRecordTest: the shape matrix, a no-bypass test (grouped/versioned layouts, empty stem, embedded newline), and the symlink-shape test. Fixtures are deliberately generic (s1,s2) — public repo. Review round addedtest_parse_raw_z_rejects_rename_records, pinning invariant 2's documented fail-closed handling of a two-path R/C record — the tripwire for a future editor who drops--no-renames..github/groom/README.md— the family, the--no-renamesrequirement, and an explicit statement of this entry's asymmetry: it is the one entry with no CI-execution justification, it is hardcoded rather than caller-gated, and an unrelatedsuites/*/cases/fixture tree inherits it with no opt-out.Verification
python3 -m unittest discover -s .github/groom/tests -p 'test_*.py' -v— 374 tests, all green (1 pre-existing skip: the Linux-only sandbox suite). The other two suites are green too: cursor-review 369, agents-md-integrity 46.--no-renamesreproduced end-to-end on a scratch repo (git 2.54):git mv suites/s1/cases/c1.yaml suites/s1/retired-c1.yaml→ before the fix the policy sees nothing (PR would be auto-opened); after, it reportssuites/s1/cases/c1.yamland the patch is denied.denied_paths— every case file denied, zero non-casesuites/files denied (drivers, fixtures, READMEs all pass through), and every other denied path a pre-existing CI-privileged hit. No behaviour change for callers without asuites/**/cases/tree; all pre-existing patch-policy tests pass unchanged.[^/]+cannot cross/, so the segment partition is unique and there is nothing to backtrack over — verified sub-millisecond on 9k-char pathological near-miss paths.check_workflow_pins.pyOK;check_agents_md.py --root .passed (1 pre-existing CODEOWNERS warning);groom.ymlparses.Judgment calls
::warningecho next to the bail still said "CI-privileged file(s)", misleading on a dataset-of-record bail; updated to name both families. One adjacent line beyond the ticket's enumerated edits, flagged rather than shipped silently.tests/suites/*/cases/collateral, but it under-blocks any caller whose dataset tree is nested, and invariant 1 is explicit that under-blocking is the hole while over-blocking only downgrades a PR to an issue.denied_pathsstill returns the two classes undifferentiated. With one consumer that tests only non-emptiness, a richer return type is unexercised API surface; the docstring now states the constraint instead.Provenance
--no-renamesregression reproduced and fixed on a scratch repo (denied path invisible before, reported after); live-tree sweep of the motivating consumer repo — all case files denied, zero false positives; ReDoS probe to 9k-char paths sub-millisecond;check_workflow_pins.pyOK;check_agents_md.pypassed (1 pre-existing warning);groom.ymlparses. Review round: the new rename-record test verified by mutation — relaxing eitherraiseinparse_raw_zfails it.::warningline updated beyond the ticket's enumerated edits, for message consistency; the review pass widened the regex beyond the ticket's literalsuites/*/cases/*.y*ml(any depth above and belowcases/, empty stems, embedded newlines, thecases-as-symlink shape) and added--no-renamesto the producer — all in the over-block direction invariant 1 mandates.Residual
suites/**/cases/shape, they are not covered by this PR.builder_deny_pathsinput, with an opt-out for this class) is the known open item — raised by 2 of 8 review models and recorded as a follow-up. It is a new public input on a shared reusable workflow with a live caller fleet, so it needs a human decision on naming and default semantics before it can be built.cases/is not covered. Closing that class means denying on git mode 120000 rather than on path shape — a different mechanism, no reachable path today.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.