Sweeping a monorepo, and who owns what¶
apiverity sweep .
apiverity sweep . --base ../main-worktree
The gap¶
A repository with twelve services has twelve contract gates and no view across them. Each pipeline says whether its own contract is fine; nothing says how many of the twelve are failing, or which team should be told.
sweep walks the tree once and answers both.
0 of 6 contract(s) failing.
@org/orders: 2 error(s), 1 warning(s) across 3 contract(s)
(unowned): 0 error(s), 4 warning(s) across 1 contract(s)
The JSON carries the whole thing: a record per contract with its protocol,
title, operation count, findings and owners, plus a by_owner rollup and an
unowned list.
Discovery is deep, on purpose¶
apiverity init looks in the conventional directories at the top of a project
and stops at the first that yields anything — that is how it learns the
project's convention. sweep cannot do that. Monorepo contracts live at
services/<name>/api/openapi.yaml, and none of those directory names appear at
the root, so a sweep that stopped at the top would report a clean tree it never
finished reading.
So sweep walks everything, skipping node_modules, .git, virtualenvs and
build output, and --limit is the backstop.
An empty tree is a usage error, not a clean sweep. Zero contracts found and zero contracts broken read identically in a summary line, and only one of them is good news.
A contract that will not load is reported, not skipped. Skipping it would
make the sweep read cleaner than the repository is. Malformed YAML raises an
exception that is not a ValueError, so one bad file used to end the whole run
with "internal error" — the least useful thing a sweep can say. Now it becomes
one unreadable row and the other ninety-nine services still get checked.
Ownership comes from CODEOWNERS¶
Not from a second file. A second ownership file goes stale the week after it is written, and the repository already answers this question.
One rule is worth stating because getting it backwards silently assigns the wrong team: the last matching pattern wins, not the most specific one.
* @org/platform
/api/ @org/payments
api/orders/*.yaml @org/orders @alice
api/users.yaml belongs to @org/payments, not @org/platform. A reader
scanning top-down for the first match would conclude the opposite.
Patterns are gitignore-shaped: a leading / anchors to the root, a trailing
/ covers a directory and everything under it, a pattern with no slash matches
at any depth, ** spans directories and a bare * does not. All three
locations GitHub looks in are read: .github/CODEOWNERS, CODEOWNERS,
docs/CODEOWNERS.
A path no rule matches is reported as unowned. Guessing an owner from a directory name would produce a plausible team that never agreed to anything.
Comparing a whole repository against a base¶
git worktree add ../main-worktree origin/main
apiverity sweep . --base ../main-worktree
Each contract is compared with the file at the same relative path under
--base, so a monorepo gets one breaking-change verdict instead of one per
service. A contract with no counterpart is recorded as new, with
compared: null and a note — "no findings" for something that was never
compared is not the same claim, and a rollup that conflated them would count a
brand-new service as a passing one.
Routing¶
by_owner is the payload a notifier needs: who, which contracts, how many
errors. Delivery is not built in — the self-hosted server already has signed
webhooks, and adding a second, unsigned notification path next to it would be
the wrong place to put an integration.