Skip to content

feat: Prow-style directory-scoped OWNERS files - #138

Merged
jeefy merged 3 commits into
mainfrom
feat/directory-scoped-owners
Sep 11, 2026
Merged

feat: Prow-style directory-scoped OWNERS files#138
jeefy merged 3 commits into
mainfrom
feat/directory-scoped-owners

Conversation

@jeefy

@jeefy jeefy commented Sep 11, 2026

Copy link
Copy Markdown
Member

Description

Closes #65. Implements the design agreed on the issue: multiple OWNERS files across directories with Prow's resolution semantics, read from the PR's base branch, with single-actor authorization ("Model A").

Resolution

For a changed file, walk from its directory up to the root collecting every OWNERS (child ∪ parents); options: { no_parent_owners: true } stops the walk. A root-only OWNERS covers everything — today's behaviour is the special case, and issues (non-PR) are unchanged byte-for-byte: same single GET /contents/OWNERS, same error strings.

Authorization on PRs

  • /approve: commenter must be an approver for every changed file (renames count both old and new paths). Denial names the first uncovered file and the OWNERS consulted.
  • /lgtm: commenter must be a reviewer or approver for at least one changed file (Prow's rule).

Security

OWNERS are loaded from pull.base.sha, never the head — a PR cannot add itself to approvers. Pinned by a test whose PR edits OWNERS.

Fail closed

A changed file with no covering OWNERS → no OWNERS file covers <file>. Any fetch/parse error → error. The org-member/collaborator fallback applies only when the repository has no OWNERS files at all. A role that isn't a list of strings now errors — the old code did "alice".includes(username), a substring match (latent bug, fixed by sharing one parser).

Loading

One git.getTree(base, recursive) + one git.getBlob per OWNERS on the changed files' ancestor chains (pruned; blobs for unrelated dirs are not fetched). Typical nested PR: 5 API calls. Truncated tree (>100k entries) → probe dir/OWNERS per ancestor directory instead. Logins compare case-insensitively. emeritus_* ignored; unknown keys tolerated; filters, labels, aliases, teams unsupported (documented).

Testing

  • 371 → 419 tests, 35 files; owners.ts at 100%; coverage 91.3 / 92.1 br / 98.2 fn. No existing assertion changed.
  • Matrix in __tests__/utils/ownersAuth.test.ts (19): root parity · nested inheritance · no_parent_owners · approve strictness across two dirs · lgtm any-file · uncovered file · zero OWNERS → membership · head OWNERS ignored · issue path untouched · truncated-tree probe · malformed role · renames · pagination · tree/blob errors · ancestor pruning.
  • Failing-test-first for no_parent_owners and head-ignored (red output in the delegated report).
  • Two bundle-harness scenarios against dist/index.js: nested approver → review created; cross-dir denial → reply comment names the file, no review, exit 1.
  • Hermetic under a simulated Actions env; dist/ byte-identical across repeated packs.

Out of scope (per #65): aggregated multi-approver approval + approved label, OWNERS_ALIASES, filters, owners-file path/URL input.

Adds src/utils/owners.ts: parseOwners (approvers/reviewers lists,
options.no_parent_owners, emeritus and unknown keys tolerated, filters
noted and ignored, logins lowercased), ownersDir, effectiveOwners (walk
from the file's directory to the root taking the union, stopping at
no_parent_owners) and loadOwnersTree (recursive git tree at a ref,
fetching only the OWNERS blobs in ancestor directories of the paths of
interest; a truncated tree falls back to probing each candidate path
with the contents API).

Not wired into authorization yet, so dist/ is unchanged.

Signed-off-by: Jeffrey Sica <me@jeefy.dev>
… the PR base

On a pull request, assertAuthorizedByOwnersOrMembership now resolves the
OWNERS files covering each changed file (Prow inheritance, including
options.no_parent_owners and both sides of a rename) from the PR's base
commit, so a PR cannot grant itself approvers:

- /approve: the commenter must be an approver for every changed file;
  the denial names the first uncovered file and the OWNERS consulted.
- /lgtm: the commenter must be a reviewer or approver for at least one
  changed file.
- A changed file with no covering OWNERS is an error naming the file.
- The org-member/collaborator fallback applies only when the base tree
  has no OWNERS file at all.

Issues keep using the root OWNERS of the default branch via the contents
API with unchanged messages; the tree/blob machinery is PR-only. Logins
are compared case-insensitively and a role that is not a list is
rejected on both paths.

Closes #65

Signed-off-by: Jeffrey Sica <me@jeefy.dev>
Rewrite the OWNERS section of docs/commands.md for multiple OWNERS
files: resolution walk and no_parent_owners, base-branch reading and
why, /approve (every changed file) versus /lgtm (at least one), the
issue case, fail-closed rules, when the membership fallback applies,
and the supported, ignored and unsupported keys. Point the /lgtm and
/approve policy cells at the section and extend the example in
docs/examples.md with a nested sdk/OWNERS.

Signed-off-by: Jeffrey Sica <me@jeefy.dev>
@jeefy
jeefy requested a review from jpmcb as a code owner September 11, 2026 01:39
@jeefy
jeefy merged commit 395e897 into main Sep 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prow-style directory-scoped OWNERS files

1 participant