fallow_output/audit_ownership.rs
1//! Audit review-brief ownership output contracts.
2
3use serde::Serialize;
4
5/// Maximum number of owner groups listed in [`OwnershipFacts::groups`]. The
6/// groups beyond the cap are the lightest ones and are counted in
7/// [`OwnershipFacts::groups_omitted`].
8pub const OWNER_GROUP_CAP: usize = 10;
9
10/// How far a changeset reaches across CODEOWNERS owner groups.
11///
12/// Computed from the CODEOWNERS file alone: no git history is read, so the
13/// section is present whenever a CODEOWNERS file is found, also when the churn
14/// walk behind `routing` finds nothing. Each file maps to its primary owner
15/// (the first owner of the last matching rule). A file that no rule matches,
16/// or that a GitLab negation rule matches, belongs to the `(unowned)` group.
17/// The owner strings use the same vocabulary as `routing.units[].expert`.
18///
19/// Absent from the brief when no CODEOWNERS file is found, or when the file
20/// cannot be read or does not parse. A configured `codeowners` path that
21/// fails also prints a warning on stderr.
22///
23/// `groups[].direct_count` counts all changed files, source or not, so the
24/// sum over all groups is the number of changed files, not the size of
25/// `impact_closure.in_diff`. Slice owners count only the files of the
26/// partition units, which are source files.
27#[derive(Debug, Clone, Default, Serialize)]
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29pub struct OwnershipFacts {
30 /// Distinct owner groups across the changed files and the impact closure.
31 /// The `(unowned)` group counts as one group. Exact, never capped.
32 pub group_count: usize,
33 /// Owner groups that own no changed file and appear only through the
34 /// impact closure. Exact, never capped.
35 pub transitive_only_count: usize,
36 /// Changed files that belong to the `(unowned)` group.
37 pub unowned_direct_count: usize,
38 /// The owner groups, sorted by `direct_count` descending, then
39 /// `affected_count` descending, then `owner`. At most [`OWNER_GROUP_CAP`]
40 /// entries. The counts in each entry are exact.
41 pub groups: Vec<OwnerGroupFact>,
42 /// How many owner groups did not fit within [`OWNER_GROUP_CAP`] and are
43 /// absent from `groups`. Zero when nothing was omitted.
44 pub groups_omitted: usize,
45 /// The owner set of each independent slice, aligned by index with
46 /// `partition.independent_slices`. Present only when that list is present
47 /// (two or more slices). A fact for the reviewer, never a demand to split.
48 #[serde(default, skip_serializing_if = "Vec::is_empty")]
49 pub slices: Vec<OwnershipSliceFact>,
50}
51
52/// One owner group and how many files of the changeset it owns.
53#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
55pub struct OwnerGroupFact {
56 /// The CODEOWNERS owner (`@user`, `@org/team`, or an email), or
57 /// `(unowned)`.
58 pub owner: String,
59 /// Changed files this group owns.
60 pub direct_count: usize,
61 /// Files of the impact closure (affected, not in the diff) this group owns.
62 pub affected_count: usize,
63}
64
65/// The owners of one independent slice of the partition.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
67#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
68pub struct OwnershipSliceFact {
69 /// The module directories of the slice, as in
70 /// `partition.independent_slices`.
71 pub module_dirs: Vec<String>,
72 /// The distinct owners of the changed files in the slice, sorted. The
73 /// `(unowned)` group is a distinct owner. Never empty.
74 pub owners: Vec<String>,
75 /// True when the slice has exactly one owner, so one owner group can
76 /// review it on its own.
77 pub separable: bool,
78}