From 4b68468d5f688846dbfef6cb86db6f78b4e72afd Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 12 Jun 2026 09:21:06 -0600 Subject: [PATCH 1/5] tests: document expected users/repos/perms affected per case Each case in tests/tests.yaml now carries an '# Affects:' comment with the net state change between before.json and after.json: distinct users whose grants change, repos whose grant lists change, and grant additions+removals. Counts computed by diffing each fixture pair. Amp-Thread-ID: https://ampcode.com/threads/T-019ebc69-17c5-743a-abd7-3683073e282d Co-authored-by: Amp --- tests/tests.yaml | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/tests/tests.yaml b/tests/tests.yaml index d2b45ea..6a9759d 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -66,11 +66,21 @@ # # `{user}` in a cliCommand resolves to the configured --user on the test # instance (live/performance modes only). +# +# Each case carries an `# Affects:` comment — the net state change between +# before.json and after.json: +# users distinct users whose explicit grants change +# repos repos whose explicit or pending grant lists change +# perms grant additions + removals (user-repo pairs, incl. pending bindIDs) +# Rejections, expected errors, dry runs, no-ops, and read-only gets affect +# nothing (users=0 repos=0 perms=0). Overwrite mutations that resend an +# identical list count in expectedMutations but not here. cases: # ── Local parse replays: argument validation, in-process, no files (fastest) ── reject-bare-invocation: + # Affects: users=0 repos=0 perms=0 description: A bare invocation without a command prints usage and exits 2. modes: - local @@ -80,6 +90,7 @@ cases: - "the following arguments are required: COMMAND" reject-unknown-command: + # Affects: users=0 repos=0 perms=0 description: An unknown subcommand is rejected with the valid choices. modes: - local @@ -89,6 +100,7 @@ cases: - "invalid choice: 'bogus'" reject-two-commands: + # Affects: users=0 repos=0 perms=0 description: Two subcommands in one invocation are rejected. modes: - local @@ -98,6 +110,7 @@ cases: - unrecognized arguments reject-get-apply: + # Affects: users=0 repos=0 perms=0 description: get is read-only; --apply is rejected. modes: - local @@ -107,6 +120,7 @@ cases: - unrecognized arguments reject-get-full: + # Affects: users=0 repos=0 perms=0 description: --full requires the set command. modes: - local @@ -116,6 +130,7 @@ cases: - unrecognized arguments reject-malformed-date: + # Affects: users=0 repos=0 perms=0 description: dates must match YYYY-MM-DD before any network call. modes: - local @@ -125,6 +140,7 @@ cases: - string_pattern_mismatch reject-user-filter-conflict: + # Affects: users=0 repos=0 perms=0 description: user filters are mutually exclusive. modes: - local @@ -134,6 +150,7 @@ cases: - choose only one of --users reject-user-and-repo-filters: + # Affects: users=0 repos=0 perms=0 description: user filters and repo filters cannot be combined. modes: - local @@ -143,6 +160,7 @@ cases: - choose either user filters or repo filters reject-repo-filter-conflict: + # Affects: users=0 repos=0 perms=0 description: repo filters are mutually exclusive. modes: - local @@ -152,6 +170,7 @@ cases: - choose only one of --repos reject-repos-created-after-malformed: + # Affects: users=0 repos=0 perms=0 description: repo creation dates must match YYYY-MM-DD at parse time. modes: - local @@ -161,6 +180,7 @@ cases: - string_pattern_mismatch reject-get-removed-repositories-created-after: + # Affects: users=0 repos=0 perms=0 description: >- The removed --repositories-created-after spelling stays removed; the flag is --repos-created-after. @@ -172,6 +192,7 @@ cases: - "unrecognized arguments: --repositories-created-after" reject-verbosity-conflict: + # Affects: users=0 repos=0 perms=0 description: the --verbose and --quiet log-level aliases are mutually exclusive. modes: - local @@ -181,6 +202,7 @@ cases: - choose only one of --verbose/-v, --quiet/-q, or --silent/-s reject-bare-set: + # Affects: users=0 repos=0 perms=0 description: set requires an explicit mode flag. modes: - local @@ -190,6 +212,7 @@ cases: - set requires one of --full reject-set-full-and-users: + # Affects: users=0 repos=0 perms=0 description: set modes are mutually exclusive. modes: - local @@ -199,6 +222,7 @@ cases: - choose at most one reject-set-user-filter-conflict: + # Affects: users=0 repos=0 perms=0 description: set user filters are mutually exclusive. modes: - local @@ -208,6 +232,7 @@ cases: - choose only one of --users reject-set-full-and-created-after: + # Affects: users=0 repos=0 perms=0 description: full overwrite cannot be combined with the additive date filter. modes: - local @@ -217,6 +242,7 @@ cases: - "--full cannot be combined with --created-after" reject-set-restore-path: + # Affects: users=0 repos=0 perms=0 description: "--restore-path belongs to restore; set does not accept it." modes: - local @@ -226,6 +252,7 @@ cases: - "unrecognized arguments: --restore-path" reject-bare-restore: + # Affects: users=0 repos=0 perms=0 description: restore requires a snapshot path. modes: - local @@ -235,6 +262,7 @@ cases: - restore requires --restore-path reject-restore-with-users: + # Affects: users=0 repos=0 perms=0 description: restore does not take user filters. modes: - local @@ -244,6 +272,7 @@ cases: - unrecognized arguments reject-restore-repos: + # Affects: users=0 repos=0 perms=0 description: restore does not take repo filters. modes: - local @@ -253,6 +282,7 @@ cases: - "unrecognized arguments: --repos" reject-restore-sync-saml-orgs: + # Affects: users=0 repos=0 perms=0 description: "--sync-saml-orgs belongs to set; restore does not accept it." modes: - local @@ -262,6 +292,7 @@ cases: - "unrecognized arguments: --sync-saml-orgs" reject-sync-saml-orgs-created-after: + # Affects: users=0 repos=0 perms=0 description: sync-saml-orgs does not take user filters. modes: - local @@ -271,6 +302,7 @@ cases: - unrecognized arguments reject-sync-saml-orgs-users: + # Affects: users=0 repos=0 perms=0 description: sync-saml-orgs does not take a user list. modes: - local @@ -280,6 +312,7 @@ cases: - "unrecognized arguments: --users" reject-sync-saml-orgs-full: + # Affects: users=0 repos=0 perms=0 description: "--full belongs to set; sync-saml-orgs does not accept it." modes: - local @@ -289,6 +322,7 @@ cases: - "unrecognized arguments: --full" reject-sync-saml-orgs-restore-path: + # Affects: users=0 repos=0 perms=0 description: "--restore-path belongs to restore; sync-saml-orgs does not accept it." modes: - local @@ -299,6 +333,7 @@ cases: # ── Local state cases: full CLI runs against an in-memory instance ── and-filters-intersect: + # Affects: users=1 repos=1 perms=1 description: >- Multiple user filters AND together: both users are in the SAML group, but only test_user_09991 matches the email filter. @@ -312,6 +347,7 @@ cases: expectedMutations: 1 regex-filters-scope: + # Affects: users=2 repos=2 perms=4 description: >- Email and repo-name regex filters scope grants to matching users and repos only. Live, the declared involvedRepos cover the full closed @@ -341,6 +377,7 @@ cases: expectedMutations: 2 saml-group-filter: + # Affects: users=1 repos=2 perms=2 description: >- authProvider samlGroup filter grants Bitbucket repos only to users whose SAML assertion includes the group. @@ -354,6 +391,7 @@ cases: expectedMutations: 2 saml-group-live: + # Affects: users=2 repos=2 perms=4 description: >- authProvider samlGroup filter against the FABRICATED SAML accounts that tests/setup.py provisions (setup.yaml samlAccounts): eng-group @@ -384,6 +422,7 @@ cases: expectedMutations: 2 set-created-after-temp-user: + # Affects: users=1 repos=1 perms=1 description: >- POSITIVE created-after selection on the real instance: the harness creates a fresh temporary user (created today), so @@ -405,6 +444,7 @@ cases: expectedMutations: 1 set-users-created-after: + # Affects: users=2 repos=2 perms=4 description: >- createdAfter mode additively grants mapped repos to users created on/after the date, preserving existing grants. @@ -419,6 +459,7 @@ cases: expectedMutations: 4 set-users-without-explicit-perms: + # Affects: users=1 repos=2 perms=2 description: >- --users-without-explicit-perms additively grants mapped repos only to users who currently hold no explicit grants anywhere. The local @@ -439,6 +480,7 @@ cases: expectedMutations: 2 set-users-without-explicit-perms-default-batch: + # Affects: users=1 repos=2 perms=2 description: >- Performance-tier twin of set-users-without-explicit-perms at default --parallelism and --explicit-permissions-batch-size, so the measured @@ -459,6 +501,7 @@ cases: expectedMutations: 2 set-repos-without-explicit-perms: + # Affects: users=2 repos=1 perms=2 description: >- --repos-without-explicit-perms overwrites only repos that currently have no explicit grants. Runs in the performance tier on the instance @@ -474,6 +517,7 @@ cases: expectedMutations: 1 set-repos-created-after: + # Affects: users=2 repos=1 perms=2 description: >- --repos-created-after scopes the overwrite to repos created on/after the date. @@ -487,6 +531,7 @@ cases: expectedMutations: 1 full-overwrite-dry-run: + # Affects: users=0 repos=0 perms=0 description: >- set without --apply plans the full overwrite but makes zero mutations and leaves state untouched — the dry-run default never mutates. @@ -499,6 +544,7 @@ cases: expectedMutations: 0 empty-maps-noop: + # Affects: users=0 repos=0 perms=0 description: >- An empty maps list is a no-op, not an error: zero mutations, existing grants untouched. @@ -512,6 +558,7 @@ cases: expectedMutations: 0 match-provider-and-host-fields: + # Affects: users=2 repos=2 perms=4 description: >- Users matched by authProvider type/serviceID/clientID/displayName (without configID or samlGroup) and repos matched by codeHostConnection @@ -526,6 +573,7 @@ cases: expectedMutations: 2 add-users-by-email-and-list: + # Affects: users=2 repos=2 perms=4 description: >- --users accepts a comma-delimited mix of an email address and a username; only the two selected users gain grants, additively. @@ -541,6 +589,7 @@ cases: expectedMutations: 4 restore-dry-run-noop: + # Affects: users=0 repos=0 perms=0 description: >- restore without --apply plans against a snapshot that differs from current state but makes zero mutations. @@ -553,6 +602,7 @@ cases: expectedMutations: 0 restore-applies-snapshot: + # Affects: users=2 repos=1 perms=2 description: >- restore --apply overwrites the repo that differs from the snapshot and skips the repo that already matches. @@ -566,6 +616,7 @@ cases: expectedMutations: 1 restore-restores-pending: + # Affects: users=0 repos=2 perms=2 (both perms are pending bindID grants) description: >- restore --apply recreates the snapshot's pending grants (bindIDs that never resolved to a user) and wipes pending grants the snapshot does @@ -580,6 +631,7 @@ cases: expectedMutations: 2 full-overwrite-preserves-pending: + # Affects: users=2 repos=1 perms=2 description: >- A full-overwrite apply (without backup, so the pending state is fetched live) resends each mapped repo's pending bindIDs and leaves @@ -595,6 +647,7 @@ cases: expectedMutations: 1 full-overwrite-with-backup: + # Affects: users=2 repos=1 perms=2 description: >- The default backup path (no --no-backup) captures before/after snapshots (including a pending grant on the mutated repo, which must @@ -608,6 +661,7 @@ cases: expectedMutations: 1 get-user-grants: + # Affects: users=0 repos=0 perms=0 description: >- get scoped to one user captures that user's explicit grants and never mutates the instance. @@ -621,6 +675,7 @@ cases: # ── Local + live: expected-error cases, replayed read-only on the instance ── invalid-bad-regex: + # Affects: users=0 repos=0 perms=0 description: >- An invalid Python regex in a filter is rejected by structural validation before any mutation. @@ -635,6 +690,7 @@ cases: - is not a valid Python regex invalid-missing-repos-section: + # Affects: users=0 repos=0 perms=0 description: >- A rule without a repos section is rejected by structural validation before any mutation. @@ -649,6 +705,7 @@ cases: - "`repos:` section is missing" invalid-unknown-selector-field: + # Affects: users=0 repos=0 perms=0 description: >- A typo'd selector field is rejected by structural validation before any mutation. @@ -663,6 +720,7 @@ cases: - unknown users field 'userNames' invalid-set-created-after-date: + # Affects: users=0 repos=0 perms=0 description: >- An impossible calendar date passes the YYYY-MM-DD shape check but set rejects it post-parse, before any mutation. @@ -677,6 +735,7 @@ cases: - "--created-after must use YYYY-MM-DD" invalid-set-repos-created-after-date: + # Affects: users=0 repos=0 perms=0 description: >- An impossible calendar date passes the YYYY-MM-DD shape check but the repo-scoped set rejects it post-parse, before any mutation. @@ -691,6 +750,7 @@ cases: - "--repos-created-after must use YYYY-MM-DD" invalid-set-unknown-user: + # Affects: users=0 repos=0 perms=0 description: >- A --users value naming no Sourcegraph user fails before any mutation. modes: @@ -705,6 +765,7 @@ cases: - "No Sourcegraph user found for 'username_doesnt_exist_01'" invalid-set-unknown-repo: + # Affects: users=0 repos=0 perms=0 description: >- A --repos value naming no Sourcegraph repo fails before any mutation. modes: @@ -719,6 +780,7 @@ cases: - "No Sourcegraph repo found for: repo-doesnt-exist-49999" invalid-restore-wrong-schema-version: + # Affects: users=0 repos=0 perms=0 description: >- A snapshot with an unsupported schema_version is refused without changing any state. @@ -732,6 +794,7 @@ cases: - "snapshot schema_version is 1, expected" restore-missing-file: + # Affects: users=0 repos=0 perms=0 description: >- restore with a snapshot path that does not exist fails without changing any state. @@ -747,6 +810,7 @@ cases: # ── Local + live: mutating cases, seeded and restored on the instance ── no-match-noop: + # Affects: users=0 repos=0 perms=0 description: >- A rule matching no users produces zero mutations and leaves existing grants untouched. @@ -761,6 +825,7 @@ cases: expectedMutations: 0 set-repos-filter: + # Affects: users=3 repos=1 perms=3 description: >- --repos scopes the full overwrite to the listed repos; other mapped repos keep their existing grants. Also pins --src-log-level DEBUG: @@ -779,6 +844,7 @@ cases: expectedMutations: 1 add-users-preserves-existing: + # Affects: users=1 repos=2 perms=2 description: >- Additive --users mode grants mapped repos to one user without dropping existing repo users. @@ -794,6 +860,7 @@ cases: expectedMutations: 2 full-overwrite-removes-stale-grant: + # Affects: users=2 repos=1 perms=2 description: >- Full set mode overwrites a mapped repo's explicit users, removing grants that no rule justifies. @@ -809,6 +876,7 @@ cases: # ── Live only: real-instance validation and organization sync ── invalid-created-after-date: + # Affects: users=0 repos=0 perms=0 description: >- An impossible calendar date passes the YYYY-MM-DD shape check but is rejected by date validation. @@ -821,6 +889,7 @@ cases: - "--created-after must use YYYY-MM-DD" invalid-missing-maps-file: + # Affects: users=0 repos=0 perms=0 description: >- A maps path that does not exist is rejected with a pointer to the command that creates the default maps file. Also pins the --quiet @@ -836,6 +905,7 @@ cases: - set input file does not exist get-created-after-future: + # Affects: users=0 repos=0 perms=0 description: >- A far-future --created-after selects no users on the real instance. modes: @@ -846,6 +916,7 @@ cases: - Selected 0 user(s) for get output. get-user-created-after-future: + # Affects: users=0 repos=0 perms=0 description: >- --users combined with a far-future --created-after filters the named user out of the selection. @@ -857,6 +928,7 @@ cases: - no user metadata selected get-users-without-perms-created-after-future: + # Affects: users=0 repos=0 perms=0 description: >- --users-without-explicit-perms combined with a far-future --created-after selects no users. @@ -868,6 +940,7 @@ cases: - Selected 0 user(s) for get output. get-repos-filter: + # Affects: users=0 repos=0 perms=0 description: >- get scoped to one repo by exact name. The snapshot still scans every user's explicit grants to find the repo's holders (measured ~400 s at @@ -880,6 +953,7 @@ cases: - Selected 1 repo(s) by exact name. get-repos-created-after-future: + # Affects: users=0 repos=0 perms=0 description: >- A far-future --repos-created-after selects no repos. Also pins the --verbose alias: DEBUG verbosity must not hide the INFO summary. @@ -891,6 +965,7 @@ cases: - Selected 0 Sourcegraph repo(s) created on or after 2099-01-01. set-users-created-after-noop: + # Affects: users=0 repos=0 perms=0 description: >- A far-future --created-after selects no users on the real instance: zero mutations, seeded state untouched. @@ -905,6 +980,7 @@ cases: expectedMutations: 0 set-repos-created-after-noop: + # Affects: users=0 repos=0 perms=0 description: >- A far-future --repos-created-after selects no repos on the real instance: zero mutations, seeded state untouched. @@ -919,6 +995,7 @@ cases: expectedMutations: 0 sync-saml-orgs-dry-run: + # Affects: users=0 repos=0 perms=0 description: >- Standalone organization sync dry run. Also pins the explicit --env-file flag against its default value. @@ -930,6 +1007,7 @@ cases: - Dry run complete set-users-sync-saml-orgs-dry-run: + # Affects: users=0 repos=0 perms=0 description: >- Combined permission + organization sync dispatch, user-scoped, dry run only. @@ -941,6 +1019,7 @@ cases: - Dry run complete set-full-sync-saml-orgs-dry-run: + # Affects: users=0 repos=0 perms=0 description: >- Combined permission + organization sync dispatch, full mode, dry run only. The dry-run before-capture scans every user (measured ~395 s), @@ -953,6 +1032,7 @@ cases: - Dry run complete set-created-after-sync-saml-orgs-dry-run: + # Affects: users=0 repos=0 perms=0 description: >- Combined permission + organization sync dispatch, created-after mode (far-future date selects no users), dry run only. @@ -964,6 +1044,7 @@ cases: - Dry run complete set-repos-sync-saml-orgs-dry-run: + # Affects: users=0 repos=0 perms=0 description: >- Combined permission + organization sync dispatch, repo-scoped, dry run only. The dry-run before-capture scans every user even for one repo @@ -976,6 +1057,7 @@ cases: - Dry run complete set-users-without-perms-sync-saml-orgs-dry-run: + # Affects: users=0 repos=0 perms=0 description: >- Combined permission + organization sync dispatch, users-without-explicit-perms mode, dry run only. The unfiltered @@ -989,6 +1071,7 @@ cases: - Dry run complete set-repos-created-after-sync-saml-orgs-dry-run: + # Affects: users=0 repos=0 perms=0 description: >- Combined permission + organization sync dispatch, repos-created-after mode (far-future date selects no repos), dry run only. @@ -1000,6 +1083,7 @@ cases: - Dry run complete sync-saml-orgs-apply: + # Affects: users=0 repos=0 perms=0 (converges org memberships only; no repo perms) description: >- Org membership sync converges to SAML group data and validates its own outcome; it is safe to re-run. @@ -1012,6 +1096,7 @@ cases: # ── Live + performance: timed, measured runs (slowest) ── full-overwrite-unions: + # Affects: users=3 repos=2 perms=5 description: >- Full set mode unions users across rules, overwrites mapped repos, and leaves unmapped repos alone. Pending bindIDs ride along: one on a @@ -1030,6 +1115,7 @@ cases: expectedMutations: 2 get-user-baseline: + # Affects: users=0 repos=0 perms=0 description: Timed read-only baseline of one user's explicit grants. modes: - performance @@ -1037,6 +1123,7 @@ cases: expectedExitCode: 0 get-full-snapshot: + # Affects: users=0 repos=0 perms=0 description: >- Bare get captures the full instance snapshot. Locally it proves get never mutates; on the instance it is the timed full 10k-user capture, @@ -1049,6 +1136,7 @@ cases: expectedMutations: 0 get-repos-without-explicit-perms: + # Affects: users=0 repos=0 perms=0 description: >- get --repos-without-explicit-perms scopes the snapshot to repos with no explicit grants. Requires the full before-snapshot, so on the @@ -1062,6 +1150,7 @@ cases: expectedMutations: 0 set-repos-without-perms-sync-saml-orgs-dry-run: + # Affects: users=0 repos=0 perms=0 description: >- Combined permission + organization sync dispatch, repos-without-explicit-perms mode, dry run only. Needs the full From 9fa592ab3266d6fe5fe148ec5b172a552f72ee18 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 12 Jun 2026 09:36:15 -0600 Subject: [PATCH 2/5] tests: add per-case runtime-cost comments to tests.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each case now carries a '# Cost:' comment alongside '# Affects:', counting the reads that drive runtime (user-metadata fetches, repo fetches, and per-user explicit-grant scans — the ~400 s dominant cost at 10k users) plus mutation writes, with a complexity class (O(1), O(U), O(R), O(U+R), O(U·scan)). Derived from the command read paths in permissions/{command,full_set,restore,snapshot}.py and orgs/sync.py, against the live instance scale in tests/setup.yaml (10k users, 50k repos). Amp-Thread-ID: https://ampcode.com/threads/T-019ebc69-17c5-743a-abd7-3683073e282d Co-authored-by: Amp --- tests/tests.yaml | 99 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/tests/tests.yaml b/tests/tests.yaml index 6a9759d..43cb3af 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -67,20 +67,36 @@ # `{user}` in a cliCommand resolves to the configured --user on the test # instance (live/performance modes only). # -# Each case carries an `# Affects:` comment — the net state change between -# before.json and after.json: +# Each case carries two comments sizing its work: +# +# `# Affects:` — net state change between before.json and after.json: # users distinct users whose explicit grants change # repos repos whose explicit or pending grant lists change # perms grant additions + removals (user-repo pairs, incl. pending bindIDs) # Rejections, expected errors, dry runs, no-ops, and read-only gets affect # nothing (users=0 repos=0 perms=0). Overwrite mutations that resend an # identical list count in expectedMutations but not here. +# +# `# Cost:` — reads + writes driving runtime, and the complexity class: +# users= user-metadata records fetched (paged, fast) +# repos= repo records fetched (paged, fast) +# permScans= per-user explicit-grant reads — the DOMINANT cost: the +# server materializes every accessible repo per probe +# (~25 users/s; permScans=all ≈ 400 s live) +# writes GraphQL permission mutations (= expectedMutations) +# `all` means a full-instance scan: 10,000 users / 50,000 repos on the +# live instance (tests/setup.yaml); locally it is the fixture's tiny +# counts, so every local run is sub-second regardless of class. +# Complexity: U = user count, R = repo count, U·scan = per-user +# explicit-grant scan (the ~400 s tier). Live harness seeding/verify/ +# restore overhead is excluded. cases: # ── Local parse replays: argument validation, in-process, no files (fastest) ── reject-bare-invocation: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: A bare invocation without a command prints usage and exits 2. modes: - local @@ -91,6 +107,7 @@ cases: reject-unknown-command: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: An unknown subcommand is rejected with the valid choices. modes: - local @@ -101,6 +118,7 @@ cases: reject-two-commands: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: Two subcommands in one invocation are rejected. modes: - local @@ -111,6 +129,7 @@ cases: reject-get-apply: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: get is read-only; --apply is rejected. modes: - local @@ -121,6 +140,7 @@ cases: reject-get-full: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: --full requires the set command. modes: - local @@ -131,6 +151,7 @@ cases: reject-malformed-date: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: dates must match YYYY-MM-DD before any network call. modes: - local @@ -141,6 +162,7 @@ cases: reject-user-filter-conflict: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: user filters are mutually exclusive. modes: - local @@ -151,6 +173,7 @@ cases: reject-user-and-repo-filters: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: user filters and repo filters cannot be combined. modes: - local @@ -161,6 +184,7 @@ cases: reject-repo-filter-conflict: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: repo filters are mutually exclusive. modes: - local @@ -171,6 +195,7 @@ cases: reject-repos-created-after-malformed: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: repo creation dates must match YYYY-MM-DD at parse time. modes: - local @@ -181,6 +206,7 @@ cases: reject-get-removed-repositories-created-after: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: >- The removed --repositories-created-after spelling stays removed; the flag is --repos-created-after. @@ -193,6 +219,7 @@ cases: reject-verbosity-conflict: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: the --verbose and --quiet log-level aliases are mutually exclusive. modes: - local @@ -203,6 +230,7 @@ cases: reject-bare-set: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: set requires an explicit mode flag. modes: - local @@ -213,6 +241,7 @@ cases: reject-set-full-and-users: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: set modes are mutually exclusive. modes: - local @@ -223,6 +252,7 @@ cases: reject-set-user-filter-conflict: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: set user filters are mutually exclusive. modes: - local @@ -233,6 +263,7 @@ cases: reject-set-full-and-created-after: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: full overwrite cannot be combined with the additive date filter. modes: - local @@ -243,6 +274,7 @@ cases: reject-set-restore-path: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: "--restore-path belongs to restore; set does not accept it." modes: - local @@ -253,6 +285,7 @@ cases: reject-bare-restore: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: restore requires a snapshot path. modes: - local @@ -263,6 +296,7 @@ cases: reject-restore-with-users: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: restore does not take user filters. modes: - local @@ -273,6 +307,7 @@ cases: reject-restore-repos: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: restore does not take repo filters. modes: - local @@ -283,6 +318,7 @@ cases: reject-restore-sync-saml-orgs: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: "--sync-saml-orgs belongs to set; restore does not accept it." modes: - local @@ -293,6 +329,7 @@ cases: reject-sync-saml-orgs-created-after: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: sync-saml-orgs does not take user filters. modes: - local @@ -303,6 +340,7 @@ cases: reject-sync-saml-orgs-users: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: sync-saml-orgs does not take a user list. modes: - local @@ -313,6 +351,7 @@ cases: reject-sync-saml-orgs-full: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: "--full belongs to set; sync-saml-orgs does not accept it." modes: - local @@ -323,6 +362,7 @@ cases: reject-sync-saml-orgs-restore-path: # Affects: users=0 repos=0 perms=0 + # Cost: O(1) — parse-only, no instance reads or writes description: "--restore-path belongs to restore; sync-saml-orgs does not accept it." modes: - local @@ -334,6 +374,7 @@ cases: # ── Local state cases: full CLI runs against an in-memory instance ── and-filters-intersect: # Affects: users=1 repos=1 perms=1 + # Cost: reads users=all repos=all permScans=0; writes=1 mutation → O(U+R) description: >- Multiple user filters AND together: both users are in the SAML group, but only test_user_09991 matches the email filter. @@ -348,6 +389,7 @@ cases: regex-filters-scope: # Affects: users=2 repos=2 perms=4 + # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) description: >- Email and repo-name regex filters scope grants to matching users and repos only. Live, the declared involvedRepos cover the full closed @@ -378,6 +420,7 @@ cases: saml-group-filter: # Affects: users=1 repos=2 perms=2 + # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) description: >- authProvider samlGroup filter grants Bitbucket repos only to users whose SAML assertion includes the group. @@ -392,6 +435,7 @@ cases: saml-group-live: # Affects: users=2 repos=2 perms=4 + # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) description: >- authProvider samlGroup filter against the FABRICATED SAML accounts that tests/setup.py provisions (setup.yaml samlAccounts): eng-group @@ -423,6 +467,7 @@ cases: set-created-after-temp-user: # Affects: users=1 repos=1 perms=1 + # Cost: reads users=1 (server-side date filter) repos=all permScans=1; writes=1 mutation → O(R) description: >- POSITIVE created-after selection on the real instance: the harness creates a fresh temporary user (created today), so @@ -445,6 +490,7 @@ cases: set-users-created-after: # Affects: users=2 repos=2 perms=4 + # Cost: reads users=selected (server-side date filter) repos=all permScans=selected; writes=4 mutations → O(R) description: >- createdAfter mode additively grants mapped repos to users created on/after the date, preserving existing grants. @@ -460,6 +506,7 @@ cases: set-users-without-explicit-perms: # Affects: users=1 repos=2 perms=2 + # Cost: reads users=all (candidates + hydration) repos=all permScans=rule-matched only; writes=2 mutations → O(U+R) description: >- --users-without-explicit-perms additively grants mapped repos only to users who currently hold no explicit grants anywhere. The local @@ -481,6 +528,7 @@ cases: set-users-without-explicit-perms-default-batch: # Affects: users=1 repos=2 perms=2 + # Cost: reads users=all (candidates + hydration) repos=all permScans=rule-matched only; writes=2 mutations → O(U+R) description: >- Performance-tier twin of set-users-without-explicit-perms at default --parallelism and --explicit-permissions-batch-size, so the measured @@ -502,6 +550,7 @@ cases: set-repos-without-explicit-perms: # Affects: users=2 repos=1 perms=2 + # Cost: reads users=all repos=all permScans=all (forced before-snapshot); writes=1 mutation → O(U·scan + R) description: >- --repos-without-explicit-perms overwrites only repos that currently have no explicit grants. Runs in the performance tier on the instance @@ -518,6 +567,7 @@ cases: set-repos-created-after: # Affects: users=2 repos=1 perms=2 + # Cost: reads users=all repos=date-matched (createdAt-ordered scan stops at threshold) permScans=0; writes=1 mutation → O(U) description: >- --repos-created-after scopes the overwrite to repos created on/after the date. @@ -532,6 +582,7 @@ cases: full-overwrite-dry-run: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all repos=all permScans=all (dry run forces before-capture); writes none → O(U·scan + R) description: >- set without --apply plans the full overwrite but makes zero mutations and leaves state untouched — the dry-run default never mutates. @@ -545,6 +596,7 @@ cases: empty-maps-noop: # Affects: users=0 repos=0 perms=0 + # Cost: no rules → exits before any instance scan (apply + --no-backup skips snapshots) → O(1) description: >- An empty maps list is a no-op, not an error: zero mutations, existing grants untouched. @@ -559,6 +611,7 @@ cases: match-provider-and-host-fields: # Affects: users=2 repos=2 perms=4 + # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) description: >- Users matched by authProvider type/serviceID/clientID/displayName (without configID or samlGroup) and repos matched by codeHostConnection @@ -574,6 +627,7 @@ cases: add-users-by-email-and-list: # Affects: users=2 repos=2 perms=4 + # Cost: reads users=2 (exact lookups) repos=all permScans=2; writes=4 mutations → O(R) description: >- --users accepts a comma-delimited mix of an email address and a username; only the two selected users gain grants, additively. @@ -590,6 +644,7 @@ cases: restore-dry-run-noop: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all permScans=all (current-state capture); writes none → O(U·scan) description: >- restore without --apply plans against a snapshot that differs from current state but makes zero mutations. @@ -603,6 +658,7 @@ cases: restore-applies-snapshot: # Affects: users=2 repos=1 perms=2 + # Cost: reads users=all permScans=all (current-state capture); writes=1 mutation → O(U·scan) description: >- restore --apply overwrites the repo that differs from the snapshot and skips the repo that already matches. @@ -617,6 +673,7 @@ cases: restore-restores-pending: # Affects: users=0 repos=2 perms=2 (both perms are pending bindID grants) + # Cost: reads users=all permScans=all + pending listing; writes=2 mutations → O(U·scan) description: >- restore --apply recreates the snapshot's pending grants (bindIDs that never resolved to a user) and wipes pending grants the snapshot does @@ -632,6 +689,7 @@ cases: full-overwrite-preserves-pending: # Affects: users=2 repos=1 perms=2 + # Cost: reads users=all repos=all permScans=0 (pending listing only); writes=1 mutation → O(U+R) description: >- A full-overwrite apply (without backup, so the pending state is fetched live) resends each mapped repo's pending bindIDs and leaves @@ -648,6 +706,7 @@ cases: full-overwrite-with-backup: # Affects: users=2 repos=1 perms=2 + # Cost: reads users=all repos=all permScans=2×all (before + after snapshots); writes=1 mutation → O(U·scan + R) description: >- The default backup path (no --no-backup) captures before/after snapshots (including a pending grant on the mutated repo, which must @@ -662,6 +721,7 @@ cases: get-user-grants: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=1 permScans=1; writes none → O(1) description: >- get scoped to one user captures that user's explicit grants and never mutates the instance. @@ -676,6 +736,7 @@ cases: # ── Local + live: expected-error cases, replayed read-only on the instance ── invalid-bad-regex: # Affects: users=0 repos=0 perms=0 + # Cost: maps validation fails before any instance read → O(1) description: >- An invalid Python regex in a filter is rejected by structural validation before any mutation. @@ -691,6 +752,7 @@ cases: invalid-missing-repos-section: # Affects: users=0 repos=0 perms=0 + # Cost: maps validation fails before any instance read → O(1) description: >- A rule without a repos section is rejected by structural validation before any mutation. @@ -706,6 +768,7 @@ cases: invalid-unknown-selector-field: # Affects: users=0 repos=0 perms=0 + # Cost: maps validation fails before any instance read → O(1) description: >- A typo'd selector field is rejected by structural validation before any mutation. @@ -721,6 +784,7 @@ cases: invalid-set-created-after-date: # Affects: users=0 repos=0 perms=0 + # Cost: date validation fails before any instance read → O(1) description: >- An impossible calendar date passes the YYYY-MM-DD shape check but set rejects it post-parse, before any mutation. @@ -736,6 +800,7 @@ cases: invalid-set-repos-created-after-date: # Affects: users=0 repos=0 perms=0 + # Cost: date validation fails before any instance read → O(1) description: >- An impossible calendar date passes the YYYY-MM-DD shape check but the repo-scoped set rejects it post-parse, before any mutation. @@ -751,6 +816,7 @@ cases: invalid-set-unknown-user: # Affects: users=0 repos=0 perms=0 + # Cost: reads ≤2 user lookups, fails before any scan → O(1) description: >- A --users value naming no Sourcegraph user fails before any mutation. modes: @@ -766,6 +832,7 @@ cases: invalid-set-unknown-repo: # Affects: users=0 repos=0 perms=0 + # Cost: reads 1 repo-name lookup, fails before the user scan → O(1) description: >- A --repos value naming no Sourcegraph repo fails before any mutation. modes: @@ -781,6 +848,7 @@ cases: invalid-restore-wrong-schema-version: # Affects: users=0 repos=0 perms=0 + # Cost: reads the snapshot file only, no instance reads → O(1) description: >- A snapshot with an unsupported schema_version is refused without changing any state. @@ -795,6 +863,7 @@ cases: restore-missing-file: # Affects: users=0 repos=0 perms=0 + # Cost: file-existence check only, no instance reads → O(1) description: >- restore with a snapshot path that does not exist fails without changing any state. @@ -811,6 +880,7 @@ cases: # ── Local + live: mutating cases, seeded and restored on the instance ── no-match-noop: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all repos=all permScans=0; writes none → O(U+R) description: >- A rule matching no users produces zero mutations and leaves existing grants untouched. @@ -826,6 +896,7 @@ cases: set-repos-filter: # Affects: users=3 repos=1 perms=3 + # Cost: reads users=all repos=1 (exact name) permScans=0; writes=1 mutation → O(U) description: >- --repos scopes the full overwrite to the listed repos; other mapped repos keep their existing grants. Also pins --src-log-level DEBUG: @@ -845,6 +916,7 @@ cases: add-users-preserves-existing: # Affects: users=1 repos=2 perms=2 + # Cost: reads users=1 repos=all permScans=1; writes=2 mutations → O(R) description: >- Additive --users mode grants mapped repos to one user without dropping existing repo users. @@ -861,6 +933,7 @@ cases: full-overwrite-removes-stale-grant: # Affects: users=2 repos=1 perms=2 + # Cost: reads users=all repos=all permScans=0; writes=1 mutation → O(U+R) description: >- Full set mode overwrites a mapped repo's explicit users, removing grants that no rule justifies. @@ -877,6 +950,7 @@ cases: # ── Live only: real-instance validation and organization sync ── invalid-created-after-date: # Affects: users=0 repos=0 perms=0 + # Cost: date validation fails before any instance read → O(1) description: >- An impossible calendar date passes the YYYY-MM-DD shape check but is rejected by date validation. @@ -890,6 +964,7 @@ cases: invalid-missing-maps-file: # Affects: users=0 repos=0 perms=0 + # Cost: maps-file existence check only, no instance reads → O(1) description: >- A maps path that does not exist is rejected with a pointer to the command that creates the default maps file. Also pins the --quiet @@ -906,6 +981,7 @@ cases: get-created-after-future: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=0 (server-side date filter) permScans=0 → O(1) description: >- A far-future --created-after selects no users on the real instance. modes: @@ -917,6 +993,7 @@ cases: get-user-created-after-future: # Affects: users=0 repos=0 perms=0 + # Cost: reads 1 user lookup, filtered out by date; permScans=0 → O(1) description: >- --users combined with a far-future --created-after filters the named user out of the selection. @@ -929,6 +1006,7 @@ cases: get-users-without-perms-created-after-future: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=0 (filtered candidate query) permScans=0 → O(1) description: >- --users-without-explicit-perms combined with a far-future --created-after selects no users. @@ -941,6 +1019,7 @@ cases: get-repos-filter: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all repos=1 permScans=all (~400 s at 10k users); writes none → O(U·scan) description: >- get scoped to one repo by exact name. The snapshot still scans every user's explicit grants to find the repo's holders (measured ~400 s at @@ -954,6 +1033,7 @@ cases: get-repos-created-after-future: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all (metadata only) repos≈1 page permScans=0 (empty repo selection skips probes) → O(U) description: >- A far-future --repos-created-after selects no repos. Also pins the --verbose alias: DEBUG verbosity must not hide the INFO summary. @@ -966,6 +1046,7 @@ cases: set-users-created-after-noop: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=0 (server-side date filter, exits before the repo scan); writes none → O(1) description: >- A far-future --created-after selects no users on the real instance: zero mutations, seeded state untouched. @@ -981,6 +1062,7 @@ cases: set-repos-created-after-noop: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all (metadata only) repos≈1 page permScans=0; writes none → O(U) description: >- A far-future --repos-created-after selects no repos on the real instance: zero mutations, seeded state untouched. @@ -996,6 +1078,7 @@ cases: sync-saml-orgs-dry-run: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all (SAML account data) + org member pages; writes none → O(U) description: >- Standalone organization sync dry run. Also pins the explicit --env-file flag against its default value. @@ -1008,6 +1091,7 @@ cases: set-users-sync-saml-orgs-dry-run: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=1 (perm phase) + all (org phase) permScans=1; writes none → O(U) description: >- Combined permission + organization sync dispatch, user-scoped, dry run only. @@ -1020,6 +1104,7 @@ cases: set-full-sync-saml-orgs-dry-run: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all repos=all permScans=all (~395 s); org phase reuses the streamed users; writes none → O(U·scan + R) description: >- Combined permission + organization sync dispatch, full mode, dry run only. The dry-run before-capture scans every user (measured ~395 s), @@ -1033,6 +1118,7 @@ cases: set-created-after-sync-saml-orgs-dry-run: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=0 (perm phase) + all (org phase); writes none → O(U) description: >- Combined permission + organization sync dispatch, created-after mode (far-future date selects no users), dry run only. @@ -1045,6 +1131,7 @@ cases: set-repos-sync-saml-orgs-dry-run: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=2×all (perm + org phases) repos=1 permScans=all (~400 s); writes none → O(U·scan) description: >- Combined permission + organization sync dispatch, repo-scoped, dry run only. The dry-run before-capture scans every user even for one repo @@ -1058,6 +1145,7 @@ cases: set-users-without-perms-sync-saml-orgs-dry-run: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=2×all (hydration + org stream) repos=all permScans=rule-matched only; writes none → O(U+R) description: >- Combined permission + organization sync dispatch, users-without-explicit-perms mode, dry run only. The unfiltered @@ -1072,6 +1160,7 @@ cases: set-repos-created-after-sync-saml-orgs-dry-run: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=0 (perm phase) + all (org phase) repos≈1 page; writes none → O(U) description: >- Combined permission + organization sync dispatch, repos-created-after mode (far-future date selects no repos), dry run only. @@ -1084,6 +1173,7 @@ cases: sync-saml-orgs-apply: # Affects: users=0 repos=0 perms=0 (converges org memberships only; no repo perms) + # Cost: reads users=all (SAML account data) + org member pages; writes org-membership mutations (convergent, usually few) → O(U) description: >- Org membership sync converges to SAML group data and validates its own outcome; it is safe to re-run. @@ -1097,6 +1187,7 @@ cases: # ── Live + performance: timed, measured runs (slowest) ── full-overwrite-unions: # Affects: users=3 repos=2 perms=5 + # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) description: >- Full set mode unions users across rules, overwrites mapped repos, and leaves unmapped repos alone. Pending bindIDs ride along: one on a @@ -1116,6 +1207,7 @@ cases: get-user-baseline: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=1 permScans=1; writes none → O(1) description: Timed read-only baseline of one user's explicit grants. modes: - performance @@ -1124,6 +1216,7 @@ cases: get-full-snapshot: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all permScans=all (~400 s at 10k users); writes none → O(U·scan) description: >- Bare get captures the full instance snapshot. Locally it proves get never mutates; on the instance it is the timed full 10k-user capture, @@ -1137,6 +1230,7 @@ cases: get-repos-without-explicit-perms: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=all repos=all permScans=all; writes none → O(U·scan + R) description: >- get --repos-without-explicit-perms scopes the snapshot to repos with no explicit grants. Requires the full before-snapshot, so on the @@ -1151,6 +1245,7 @@ cases: set-repos-without-perms-sync-saml-orgs-dry-run: # Affects: users=0 repos=0 perms=0 + # Cost: reads users=2×all (perm + org phases) repos=all permScans=all; writes none → O(U·scan + R) description: >- Combined permission + organization sync dispatch, repos-without-explicit-perms mode, dry run only. Needs the full From df9302ce92cef5f6e3fb084e8789c5df2db679e4 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:40:49 -0600 Subject: [PATCH 3/5] tests: reformat per-case cost comments as maps with a bigO field The affects/cost comments in tests.yaml are now YAML-style maps instead of single lines: affects is a one-line flow map, and cost is a block map whose first field is bigO, so the complexity class is visible without scanning to the end of a line. Trivial O(1) cases keep a one-line cost map. Amp-Thread-ID: https://ampcode.com/threads/T-019ebc69-17c5-743a-abd7-3683073e282d Co-authored-by: Amp --- tests/tests.yaml | 489 ++++++++++++++++++++++++++++++----------------- 1 file changed, 312 insertions(+), 177 deletions(-) diff --git a/tests/tests.yaml b/tests/tests.yaml index 43cb3af..632abb7 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -67,36 +67,38 @@ # `{user}` in a cliCommand resolves to the configured --user on the test # instance (live/performance modes only). # -# Each case carries two comments sizing its work: +# Each case carries two comment maps sizing its work: # -# `# Affects:` — net state change between before.json and after.json: -# users distinct users whose explicit grants change -# repos repos whose explicit or pending grant lists change -# perms grant additions + removals (user-repo pairs, incl. pending bindIDs) +# affects: — net state change between before.json and after.json: +# users: distinct users whose explicit grants change +# repos: repos whose explicit or pending grant lists change +# perms: grant additions + removals (user-repo pairs, incl. pending +# bindIDs) # Rejections, expected errors, dry runs, no-ops, and read-only gets affect -# nothing (users=0 repos=0 perms=0). Overwrite mutations that resend an -# identical list count in expectedMutations but not here. +# nothing. Overwrite mutations that resend an identical list count in +# expectedMutations but not here. # -# `# Cost:` — reads + writes driving runtime, and the complexity class: -# users= user-metadata records fetched (paged, fast) -# repos= repo records fetched (paged, fast) -# permScans= per-user explicit-grant reads — the DOMINANT cost: the -# server materializes every accessible repo per probe -# (~25 users/s; permScans=all ≈ 400 s live) -# writes GraphQL permission mutations (= expectedMutations) +# cost: — the reads + writes driving runtime: +# bigO: complexity class. U = user count, R = repo count, +# U·scan = per-user explicit-grant scan (the ~400 s tier) +# reads: users: user-metadata records fetched (paged, fast) +# repos: repo records fetched (paged, fast) +# permScans: per-user explicit-grant reads — the DOMINANT +# cost: the server materializes every accessible +# repo per probe (~25 users/s; all ≈ 400 s live) +# writes: GraphQL permission mutations (= expectedMutations) +# note: what bounds or skips the work # `all` means a full-instance scan: 10,000 users / 50,000 repos on the # live instance (tests/setup.yaml); locally it is the fixture's tiny # counts, so every local run is sub-second regardless of class. -# Complexity: U = user count, R = repo count, U·scan = per-user -# explicit-grant scan (the ~400 s tier). Live harness seeding/verify/ -# restore overhead is excluded. +# Live harness seeding/verify/restore overhead is excluded. cases: # ── Local parse replays: argument validation, in-process, no files (fastest) ── reject-bare-invocation: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: A bare invocation without a command prints usage and exits 2. modes: - local @@ -106,8 +108,8 @@ cases: - "the following arguments are required: COMMAND" reject-unknown-command: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: An unknown subcommand is rejected with the valid choices. modes: - local @@ -117,8 +119,8 @@ cases: - "invalid choice: 'bogus'" reject-two-commands: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: Two subcommands in one invocation are rejected. modes: - local @@ -128,8 +130,8 @@ cases: - unrecognized arguments reject-get-apply: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: get is read-only; --apply is rejected. modes: - local @@ -139,8 +141,8 @@ cases: - unrecognized arguments reject-get-full: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: --full requires the set command. modes: - local @@ -150,8 +152,8 @@ cases: - unrecognized arguments reject-malformed-date: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: dates must match YYYY-MM-DD before any network call. modes: - local @@ -161,8 +163,8 @@ cases: - string_pattern_mismatch reject-user-filter-conflict: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: user filters are mutually exclusive. modes: - local @@ -172,8 +174,8 @@ cases: - choose only one of --users reject-user-and-repo-filters: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: user filters and repo filters cannot be combined. modes: - local @@ -183,8 +185,8 @@ cases: - choose either user filters or repo filters reject-repo-filter-conflict: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: repo filters are mutually exclusive. modes: - local @@ -194,8 +196,8 @@ cases: - choose only one of --repos reject-repos-created-after-malformed: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: repo creation dates must match YYYY-MM-DD at parse time. modes: - local @@ -205,8 +207,8 @@ cases: - string_pattern_mismatch reject-get-removed-repositories-created-after: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: >- The removed --repositories-created-after spelling stays removed; the flag is --repos-created-after. @@ -218,8 +220,8 @@ cases: - "unrecognized arguments: --repositories-created-after" reject-verbosity-conflict: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: the --verbose and --quiet log-level aliases are mutually exclusive. modes: - local @@ -229,8 +231,8 @@ cases: - choose only one of --verbose/-v, --quiet/-q, or --silent/-s reject-bare-set: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: set requires an explicit mode flag. modes: - local @@ -240,8 +242,8 @@ cases: - set requires one of --full reject-set-full-and-users: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: set modes are mutually exclusive. modes: - local @@ -251,8 +253,8 @@ cases: - choose at most one reject-set-user-filter-conflict: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: set user filters are mutually exclusive. modes: - local @@ -262,8 +264,8 @@ cases: - choose only one of --users reject-set-full-and-created-after: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: full overwrite cannot be combined with the additive date filter. modes: - local @@ -273,8 +275,8 @@ cases: - "--full cannot be combined with --created-after" reject-set-restore-path: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: "--restore-path belongs to restore; set does not accept it." modes: - local @@ -284,8 +286,8 @@ cases: - "unrecognized arguments: --restore-path" reject-bare-restore: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: restore requires a snapshot path. modes: - local @@ -295,8 +297,8 @@ cases: - restore requires --restore-path reject-restore-with-users: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: restore does not take user filters. modes: - local @@ -306,8 +308,8 @@ cases: - unrecognized arguments reject-restore-repos: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: restore does not take repo filters. modes: - local @@ -317,8 +319,8 @@ cases: - "unrecognized arguments: --repos" reject-restore-sync-saml-orgs: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: "--sync-saml-orgs belongs to set; restore does not accept it." modes: - local @@ -328,8 +330,8 @@ cases: - "unrecognized arguments: --sync-saml-orgs" reject-sync-saml-orgs-created-after: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: sync-saml-orgs does not take user filters. modes: - local @@ -339,8 +341,8 @@ cases: - unrecognized arguments reject-sync-saml-orgs-users: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: sync-saml-orgs does not take a user list. modes: - local @@ -350,8 +352,8 @@ cases: - "unrecognized arguments: --users" reject-sync-saml-orgs-full: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: "--full belongs to set; sync-saml-orgs does not accept it." modes: - local @@ -361,8 +363,8 @@ cases: - "unrecognized arguments: --full" reject-sync-saml-orgs-restore-path: - # Affects: users=0 repos=0 perms=0 - # Cost: O(1) — parse-only, no instance reads or writes + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: parse-only — no instance reads or writes} description: "--restore-path belongs to restore; sync-saml-orgs does not accept it." modes: - local @@ -373,8 +375,11 @@ cases: # ── Local state cases: full CLI runs against an in-memory instance ── and-filters-intersect: - # Affects: users=1 repos=1 perms=1 - # Cost: reads users=all repos=all permScans=0; writes=1 mutation → O(U+R) + # affects: {users: 1, repos: 1, perms: 1} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: 0} + # writes: 1 mutation description: >- Multiple user filters AND together: both users are in the SAML group, but only test_user_09991 matches the email filter. @@ -388,8 +393,11 @@ cases: expectedMutations: 1 regex-filters-scope: - # Affects: users=2 repos=2 perms=4 - # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) + # affects: {users: 2, repos: 2, perms: 4} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: 0} + # writes: 2 mutations description: >- Email and repo-name regex filters scope grants to matching users and repos only. Live, the declared involvedRepos cover the full closed @@ -419,8 +427,11 @@ cases: expectedMutations: 2 saml-group-filter: - # Affects: users=1 repos=2 perms=2 - # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) + # affects: {users: 1, repos: 2, perms: 2} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: 0} + # writes: 2 mutations description: >- authProvider samlGroup filter grants Bitbucket repos only to users whose SAML assertion includes the group. @@ -434,8 +445,11 @@ cases: expectedMutations: 2 saml-group-live: - # Affects: users=2 repos=2 perms=4 - # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) + # affects: {users: 2, repos: 2, perms: 4} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: 0} + # writes: 2 mutations description: >- authProvider samlGroup filter against the FABRICATED SAML accounts that tests/setup.py provisions (setup.yaml samlAccounts): eng-group @@ -466,8 +480,12 @@ cases: expectedMutations: 2 set-created-after-temp-user: - # Affects: users=1 repos=1 perms=1 - # Cost: reads users=1 (server-side date filter) repos=all permScans=1; writes=1 mutation → O(R) + # affects: {users: 1, repos: 1, perms: 1} + # cost: + # bigO: O(R) + # reads: {users: 1, repos: all, permScans: 1} + # writes: 1 mutation + # note: server-side date filter selects only the temp user description: >- POSITIVE created-after selection on the real instance: the harness creates a fresh temporary user (created today), so @@ -489,8 +507,12 @@ cases: expectedMutations: 1 set-users-created-after: - # Affects: users=2 repos=2 perms=4 - # Cost: reads users=selected (server-side date filter) repos=all permScans=selected; writes=4 mutations → O(R) + # affects: {users: 2, repos: 2, perms: 4} + # cost: + # bigO: O(R) + # reads: {users: selected, repos: all, permScans: selected} + # writes: 4 mutations + # note: server-side date filter description: >- createdAfter mode additively grants mapped repos to users created on/after the date, preserving existing grants. @@ -505,8 +527,12 @@ cases: expectedMutations: 4 set-users-without-explicit-perms: - # Affects: users=1 repos=2 perms=2 - # Cost: reads users=all (candidates + hydration) repos=all permScans=rule-matched only; writes=2 mutations → O(U+R) + # affects: {users: 1, repos: 2, perms: 2} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: rule-matched} + # writes: 2 mutations + # note: candidates + hydration for all users; perm probes only for rule-matched description: >- --users-without-explicit-perms additively grants mapped repos only to users who currently hold no explicit grants anywhere. The local @@ -527,8 +553,12 @@ cases: expectedMutations: 2 set-users-without-explicit-perms-default-batch: - # Affects: users=1 repos=2 perms=2 - # Cost: reads users=all (candidates + hydration) repos=all permScans=rule-matched only; writes=2 mutations → O(U+R) + # affects: {users: 1, repos: 2, perms: 2} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: rule-matched} + # writes: 2 mutations + # note: candidates + hydration for all users; perm probes only for rule-matched description: >- Performance-tier twin of set-users-without-explicit-perms at default --parallelism and --explicit-permissions-batch-size, so the measured @@ -549,8 +579,12 @@ cases: expectedMutations: 2 set-repos-without-explicit-perms: - # Affects: users=2 repos=1 perms=2 - # Cost: reads users=all repos=all permScans=all (forced before-snapshot); writes=1 mutation → O(U·scan + R) + # affects: {users: 2, repos: 1, perms: 2} + # cost: + # bigO: O(U·scan + R) + # reads: {users: all, repos: all, permScans: all} + # writes: 1 mutation + # note: mode forces the full before-snapshot description: >- --repos-without-explicit-perms overwrites only repos that currently have no explicit grants. Runs in the performance tier on the instance @@ -566,8 +600,12 @@ cases: expectedMutations: 1 set-repos-created-after: - # Affects: users=2 repos=1 perms=2 - # Cost: reads users=all repos=date-matched (createdAt-ordered scan stops at threshold) permScans=0; writes=1 mutation → O(U) + # affects: {users: 2, repos: 1, perms: 2} + # cost: + # bigO: O(U) + # reads: {users: all, repos: date-matched, permScans: 0} + # writes: 1 mutation + # note: createdAt-ordered repo scan stops at the threshold description: >- --repos-created-after scopes the overwrite to repos created on/after the date. @@ -581,8 +619,12 @@ cases: expectedMutations: 1 full-overwrite-dry-run: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all repos=all permScans=all (dry run forces before-capture); writes none → O(U·scan + R) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U·scan + R) + # reads: {users: all, repos: all, permScans: all} + # writes: none + # note: dry run forces the before-capture description: >- set without --apply plans the full overwrite but makes zero mutations and leaves state untouched — the dry-run default never mutates. @@ -595,8 +637,8 @@ cases: expectedMutations: 0 empty-maps-noop: - # Affects: users=0 repos=0 perms=0 - # Cost: no rules → exits before any instance scan (apply + --no-backup skips snapshots) → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: no rules → exits before any instance scan} description: >- An empty maps list is a no-op, not an error: zero mutations, existing grants untouched. @@ -610,8 +652,11 @@ cases: expectedMutations: 0 match-provider-and-host-fields: - # Affects: users=2 repos=2 perms=4 - # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) + # affects: {users: 2, repos: 2, perms: 4} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: 0} + # writes: 2 mutations description: >- Users matched by authProvider type/serviceID/clientID/displayName (without configID or samlGroup) and repos matched by codeHostConnection @@ -626,8 +671,11 @@ cases: expectedMutations: 2 add-users-by-email-and-list: - # Affects: users=2 repos=2 perms=4 - # Cost: reads users=2 (exact lookups) repos=all permScans=2; writes=4 mutations → O(R) + # affects: {users: 2, repos: 2, perms: 4} + # cost: + # bigO: O(R) + # reads: {users: 2, repos: all, permScans: 2} + # writes: 4 mutations description: >- --users accepts a comma-delimited mix of an email address and a username; only the two selected users gain grants, additively. @@ -643,8 +691,12 @@ cases: expectedMutations: 4 restore-dry-run-noop: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all permScans=all (current-state capture); writes none → O(U·scan) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U·scan) + # reads: {users: all, permScans: all} + # writes: none + # note: current-state capture scans every user description: >- restore without --apply plans against a snapshot that differs from current state but makes zero mutations. @@ -657,8 +709,12 @@ cases: expectedMutations: 0 restore-applies-snapshot: - # Affects: users=2 repos=1 perms=2 - # Cost: reads users=all permScans=all (current-state capture); writes=1 mutation → O(U·scan) + # affects: {users: 2, repos: 1, perms: 2} + # cost: + # bigO: O(U·scan) + # reads: {users: all, permScans: all} + # writes: 1 mutation + # note: current-state capture scans every user description: >- restore --apply overwrites the repo that differs from the snapshot and skips the repo that already matches. @@ -672,8 +728,11 @@ cases: expectedMutations: 1 restore-restores-pending: - # Affects: users=0 repos=2 perms=2 (both perms are pending bindID grants) - # Cost: reads users=all permScans=all + pending listing; writes=2 mutations → O(U·scan) + # affects: {users: 0, repos: 2, perms: 2} (both perms are pending bindID grants) + # cost: + # bigO: O(U·scan) + # reads: {users: all, permScans: all + pending listing} + # writes: 2 mutations description: >- restore --apply recreates the snapshot's pending grants (bindIDs that never resolved to a user) and wipes pending grants the snapshot does @@ -688,8 +747,12 @@ cases: expectedMutations: 2 full-overwrite-preserves-pending: - # Affects: users=2 repos=1 perms=2 - # Cost: reads users=all repos=all permScans=0 (pending listing only); writes=1 mutation → O(U+R) + # affects: {users: 2, repos: 1, perms: 2} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: 0} + # writes: 1 mutation + # note: pending listing only description: >- A full-overwrite apply (without backup, so the pending state is fetched live) resends each mapped repo's pending bindIDs and leaves @@ -705,8 +768,12 @@ cases: expectedMutations: 1 full-overwrite-with-backup: - # Affects: users=2 repos=1 perms=2 - # Cost: reads users=all repos=all permScans=2×all (before + after snapshots); writes=1 mutation → O(U·scan + R) + # affects: {users: 2, repos: 1, perms: 2} + # cost: + # bigO: O(U·scan + R) + # reads: {users: all, repos: all, permScans: 2×all} + # writes: 1 mutation + # note: before + after snapshots description: >- The default backup path (no --no-backup) captures before/after snapshots (including a pending grant on the mutated repo, which must @@ -720,8 +787,8 @@ cases: expectedMutations: 1 get-user-grants: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=1 permScans=1; writes none → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), reads: {users: 1, permScans: 1}} description: >- get scoped to one user captures that user's explicit grants and never mutates the instance. @@ -735,8 +802,8 @@ cases: # ── Local + live: expected-error cases, replayed read-only on the instance ── invalid-bad-regex: - # Affects: users=0 repos=0 perms=0 - # Cost: maps validation fails before any instance read → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: maps validation fails before any instance read} description: >- An invalid Python regex in a filter is rejected by structural validation before any mutation. @@ -751,8 +818,8 @@ cases: - is not a valid Python regex invalid-missing-repos-section: - # Affects: users=0 repos=0 perms=0 - # Cost: maps validation fails before any instance read → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: maps validation fails before any instance read} description: >- A rule without a repos section is rejected by structural validation before any mutation. @@ -767,8 +834,8 @@ cases: - "`repos:` section is missing" invalid-unknown-selector-field: - # Affects: users=0 repos=0 perms=0 - # Cost: maps validation fails before any instance read → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: maps validation fails before any instance read} description: >- A typo'd selector field is rejected by structural validation before any mutation. @@ -783,8 +850,8 @@ cases: - unknown users field 'userNames' invalid-set-created-after-date: - # Affects: users=0 repos=0 perms=0 - # Cost: date validation fails before any instance read → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: date validation fails before any instance read} description: >- An impossible calendar date passes the YYYY-MM-DD shape check but set rejects it post-parse, before any mutation. @@ -799,8 +866,8 @@ cases: - "--created-after must use YYYY-MM-DD" invalid-set-repos-created-after-date: - # Affects: users=0 repos=0 perms=0 - # Cost: date validation fails before any instance read → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: date validation fails before any instance read} description: >- An impossible calendar date passes the YYYY-MM-DD shape check but the repo-scoped set rejects it post-parse, before any mutation. @@ -815,8 +882,8 @@ cases: - "--repos-created-after must use YYYY-MM-DD" invalid-set-unknown-user: - # Affects: users=0 repos=0 perms=0 - # Cost: reads ≤2 user lookups, fails before any scan → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), reads: ≤2 user lookups, note: fails before any scan} description: >- A --users value naming no Sourcegraph user fails before any mutation. modes: @@ -831,8 +898,8 @@ cases: - "No Sourcegraph user found for 'username_doesnt_exist_01'" invalid-set-unknown-repo: - # Affects: users=0 repos=0 perms=0 - # Cost: reads 1 repo-name lookup, fails before the user scan → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), reads: 1 repo-name lookup, note: fails before the user scan} description: >- A --repos value naming no Sourcegraph repo fails before any mutation. modes: @@ -847,8 +914,8 @@ cases: - "No Sourcegraph repo found for: repo-doesnt-exist-49999" invalid-restore-wrong-schema-version: - # Affects: users=0 repos=0 perms=0 - # Cost: reads the snapshot file only, no instance reads → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: reads the snapshot file only — no instance reads} description: >- A snapshot with an unsupported schema_version is refused without changing any state. @@ -862,8 +929,8 @@ cases: - "snapshot schema_version is 1, expected" restore-missing-file: - # Affects: users=0 repos=0 perms=0 - # Cost: file-existence check only, no instance reads → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: file-existence check only — no instance reads} description: >- restore with a snapshot path that does not exist fails without changing any state. @@ -879,8 +946,11 @@ cases: # ── Local + live: mutating cases, seeded and restored on the instance ── no-match-noop: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all repos=all permScans=0; writes none → O(U+R) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: 0} + # writes: none description: >- A rule matching no users produces zero mutations and leaves existing grants untouched. @@ -895,8 +965,11 @@ cases: expectedMutations: 0 set-repos-filter: - # Affects: users=3 repos=1 perms=3 - # Cost: reads users=all repos=1 (exact name) permScans=0; writes=1 mutation → O(U) + # affects: {users: 3, repos: 1, perms: 3} + # cost: + # bigO: O(U) + # reads: {users: all, repos: 1, permScans: 0} + # writes: 1 mutation description: >- --repos scopes the full overwrite to the listed repos; other mapped repos keep their existing grants. Also pins --src-log-level DEBUG: @@ -915,8 +988,11 @@ cases: expectedMutations: 1 add-users-preserves-existing: - # Affects: users=1 repos=2 perms=2 - # Cost: reads users=1 repos=all permScans=1; writes=2 mutations → O(R) + # affects: {users: 1, repos: 2, perms: 2} + # cost: + # bigO: O(R) + # reads: {users: 1, repos: all, permScans: 1} + # writes: 2 mutations description: >- Additive --users mode grants mapped repos to one user without dropping existing repo users. @@ -932,8 +1008,11 @@ cases: expectedMutations: 2 full-overwrite-removes-stale-grant: - # Affects: users=2 repos=1 perms=2 - # Cost: reads users=all repos=all permScans=0; writes=1 mutation → O(U+R) + # affects: {users: 2, repos: 1, perms: 2} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: 0} + # writes: 1 mutation description: >- Full set mode overwrites a mapped repo's explicit users, removing grants that no rule justifies. @@ -949,8 +1028,8 @@ cases: # ── Live only: real-instance validation and organization sync ── invalid-created-after-date: - # Affects: users=0 repos=0 perms=0 - # Cost: date validation fails before any instance read → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: date validation fails before any instance read} description: >- An impossible calendar date passes the YYYY-MM-DD shape check but is rejected by date validation. @@ -963,8 +1042,8 @@ cases: - "--created-after must use YYYY-MM-DD" invalid-missing-maps-file: - # Affects: users=0 repos=0 perms=0 - # Cost: maps-file existence check only, no instance reads → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), note: maps-file existence check only — no instance reads} description: >- A maps path that does not exist is rejected with a pointer to the command that creates the default maps file. Also pins the --quiet @@ -980,8 +1059,8 @@ cases: - set input file does not exist get-created-after-future: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=0 (server-side date filter) permScans=0 → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), reads: {users: 0, permScans: 0}, note: server-side date filter selects nobody} description: >- A far-future --created-after selects no users on the real instance. modes: @@ -992,8 +1071,8 @@ cases: - Selected 0 user(s) for get output. get-user-created-after-future: - # Affects: users=0 repos=0 perms=0 - # Cost: reads 1 user lookup, filtered out by date; permScans=0 → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), reads: {users: 1 lookup, permScans: 0}, note: the user is filtered out by date} description: >- --users combined with a far-future --created-after filters the named user out of the selection. @@ -1005,8 +1084,8 @@ cases: - no user metadata selected get-users-without-perms-created-after-future: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=0 (filtered candidate query) permScans=0 → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), reads: {users: 0, permScans: 0}, note: filtered candidate query selects nobody} description: >- --users-without-explicit-perms combined with a far-future --created-after selects no users. @@ -1018,8 +1097,12 @@ cases: - Selected 0 user(s) for get output. get-repos-filter: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all repos=1 permScans=all (~400 s at 10k users); writes none → O(U·scan) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U·scan) + # reads: {users: all, repos: 1, permScans: all} + # writes: none + # note: ~400 s at 10k users description: >- get scoped to one repo by exact name. The snapshot still scans every user's explicit grants to find the repo's holders (measured ~400 s at @@ -1032,8 +1115,12 @@ cases: - Selected 1 repo(s) by exact name. get-repos-created-after-future: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all (metadata only) repos≈1 page permScans=0 (empty repo selection skips probes) → O(U) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U) + # reads: {users: all, repos: ~1 page, permScans: 0} + # writes: none + # note: empty repo selection skips the perm probes description: >- A far-future --repos-created-after selects no repos. Also pins the --verbose alias: DEBUG verbosity must not hide the INFO summary. @@ -1045,8 +1132,8 @@ cases: - Selected 0 Sourcegraph repo(s) created on or after 2099-01-01. set-users-created-after-noop: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=0 (server-side date filter, exits before the repo scan); writes none → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), reads: {users: 0}, note: server-side date filter selects nobody — exits before the repo scan} description: >- A far-future --created-after selects no users on the real instance: zero mutations, seeded state untouched. @@ -1061,8 +1148,11 @@ cases: expectedMutations: 0 set-repos-created-after-noop: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all (metadata only) repos≈1 page permScans=0; writes none → O(U) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U) + # reads: {users: all, repos: ~1 page, permScans: 0} + # writes: none description: >- A far-future --repos-created-after selects no repos on the real instance: zero mutations, seeded state untouched. @@ -1077,8 +1167,12 @@ cases: expectedMutations: 0 sync-saml-orgs-dry-run: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all (SAML account data) + org member pages; writes none → O(U) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U) + # reads: {users: all, orgMembers: all} + # writes: none + # note: streams every user's SAML account data description: >- Standalone organization sync dry run. Also pins the explicit --env-file flag against its default value. @@ -1090,8 +1184,12 @@ cases: - Dry run complete set-users-sync-saml-orgs-dry-run: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=1 (perm phase) + all (org phase) permScans=1; writes none → O(U) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U) + # reads: {users: 1 + all, permScans: 1} + # writes: none + # note: org phase streams all users description: >- Combined permission + organization sync dispatch, user-scoped, dry run only. @@ -1103,8 +1201,12 @@ cases: - Dry run complete set-full-sync-saml-orgs-dry-run: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all repos=all permScans=all (~395 s); org phase reuses the streamed users; writes none → O(U·scan + R) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U·scan + R) + # reads: {users: all, repos: all, permScans: all} + # writes: none + # note: ~395 s; org phase reuses the streamed users description: >- Combined permission + organization sync dispatch, full mode, dry run only. The dry-run before-capture scans every user (measured ~395 s), @@ -1117,8 +1219,12 @@ cases: - Dry run complete set-created-after-sync-saml-orgs-dry-run: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=0 (perm phase) + all (org phase); writes none → O(U) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U) + # reads: {users: 0 + all} + # writes: none + # note: perm phase selects nobody; org phase streams all users description: >- Combined permission + organization sync dispatch, created-after mode (far-future date selects no users), dry run only. @@ -1130,8 +1236,12 @@ cases: - Dry run complete set-repos-sync-saml-orgs-dry-run: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=2×all (perm + org phases) repos=1 permScans=all (~400 s); writes none → O(U·scan) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U·scan) + # reads: {users: 2×all, repos: 1, permScans: all} + # writes: none + # note: ~400 s; perm and org phases each stream all users description: >- Combined permission + organization sync dispatch, repo-scoped, dry run only. The dry-run before-capture scans every user even for one repo @@ -1144,8 +1254,12 @@ cases: - Dry run complete set-users-without-perms-sync-saml-orgs-dry-run: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=2×all (hydration + org stream) repos=all permScans=rule-matched only; writes none → O(U+R) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U+R) + # reads: {users: 2×all, repos: all, permScans: rule-matched} + # writes: none + # note: hydration + org stream each cover all users description: >- Combined permission + organization sync dispatch, users-without-explicit-perms mode, dry run only. The unfiltered @@ -1159,8 +1273,12 @@ cases: - Dry run complete set-repos-created-after-sync-saml-orgs-dry-run: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=0 (perm phase) + all (org phase) repos≈1 page; writes none → O(U) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U) + # reads: {users: 0 + all, repos: ~1 page} + # writes: none + # note: perm phase selects nobody; org phase streams all users description: >- Combined permission + organization sync dispatch, repos-created-after mode (far-future date selects no repos), dry run only. @@ -1172,8 +1290,11 @@ cases: - Dry run complete sync-saml-orgs-apply: - # Affects: users=0 repos=0 perms=0 (converges org memberships only; no repo perms) - # Cost: reads users=all (SAML account data) + org member pages; writes org-membership mutations (convergent, usually few) → O(U) + # affects: {users: 0, repos: 0, perms: 0} (converges org memberships only; no repo perms) + # cost: + # bigO: O(U) + # reads: {users: all, orgMembers: all} + # writes: org-membership mutations (convergent — usually few) description: >- Org membership sync converges to SAML group data and validates its own outcome; it is safe to re-run. @@ -1186,8 +1307,11 @@ cases: # ── Live + performance: timed, measured runs (slowest) ── full-overwrite-unions: - # Affects: users=3 repos=2 perms=5 - # Cost: reads users=all repos=all permScans=0; writes=2 mutations → O(U+R) + # affects: {users: 3, repos: 2, perms: 5} + # cost: + # bigO: O(U+R) + # reads: {users: all, repos: all, permScans: 0} + # writes: 2 mutations description: >- Full set mode unions users across rules, overwrites mapped repos, and leaves unmapped repos alone. Pending bindIDs ride along: one on a @@ -1206,8 +1330,8 @@ cases: expectedMutations: 2 get-user-baseline: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=1 permScans=1; writes none → O(1) + # affects: {users: 0, repos: 0, perms: 0} + # cost: {bigO: O(1), reads: {users: 1, permScans: 1}} description: Timed read-only baseline of one user's explicit grants. modes: - performance @@ -1215,8 +1339,12 @@ cases: expectedExitCode: 0 get-full-snapshot: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all permScans=all (~400 s at 10k users); writes none → O(U·scan) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U·scan) + # reads: {users: all, permScans: all} + # writes: none + # note: ~400 s at 10k users description: >- Bare get captures the full instance snapshot. Locally it proves get never mutates; on the instance it is the timed full 10k-user capture, @@ -1229,8 +1357,11 @@ cases: expectedMutations: 0 get-repos-without-explicit-perms: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=all repos=all permScans=all; writes none → O(U·scan + R) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U·scan + R) + # reads: {users: all, repos: all, permScans: all} + # writes: none description: >- get --repos-without-explicit-perms scopes the snapshot to repos with no explicit grants. Requires the full before-snapshot, so on the @@ -1244,8 +1375,12 @@ cases: expectedMutations: 0 set-repos-without-perms-sync-saml-orgs-dry-run: - # Affects: users=0 repos=0 perms=0 - # Cost: reads users=2×all (perm + org phases) repos=all permScans=all; writes none → O(U·scan + R) + # affects: {users: 0, repos: 0, perms: 0} + # cost: + # bigO: O(U·scan + R) + # reads: {users: 2×all, repos: all, permScans: all} + # writes: none + # note: perm and org phases each stream all users description: >- Combined permission + organization sync dispatch, repos-without-explicit-perms mode, dry run only. Needs the full From 9041b53ba680766338d5b16d78427e8ea6e71a55 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:43:52 -0600 Subject: [PATCH 4/5] tests: expand affects and cost comments to block maps everywhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every case's affects and cost comment is now a block map — one field per line, bigO first — including the O(1) cases that previously used one-line flow maps. Amp-Thread-ID: https://ampcode.com/threads/T-019ebc69-17c5-743a-abd7-3683073e282d Co-authored-by: Amp --- tests/tests.yaml | 578 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 455 insertions(+), 123 deletions(-) diff --git a/tests/tests.yaml b/tests/tests.yaml index 632abb7..a884a58 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -97,8 +97,13 @@ cases: # ── Local parse replays: argument validation, in-process, no files (fastest) ── reject-bare-invocation: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: A bare invocation without a command prints usage and exits 2. modes: - local @@ -108,8 +113,13 @@ cases: - "the following arguments are required: COMMAND" reject-unknown-command: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: An unknown subcommand is rejected with the valid choices. modes: - local @@ -119,8 +129,13 @@ cases: - "invalid choice: 'bogus'" reject-two-commands: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: Two subcommands in one invocation are rejected. modes: - local @@ -130,8 +145,13 @@ cases: - unrecognized arguments reject-get-apply: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: get is read-only; --apply is rejected. modes: - local @@ -141,8 +161,13 @@ cases: - unrecognized arguments reject-get-full: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: --full requires the set command. modes: - local @@ -152,8 +177,13 @@ cases: - unrecognized arguments reject-malformed-date: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: dates must match YYYY-MM-DD before any network call. modes: - local @@ -163,8 +193,13 @@ cases: - string_pattern_mismatch reject-user-filter-conflict: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: user filters are mutually exclusive. modes: - local @@ -174,8 +209,13 @@ cases: - choose only one of --users reject-user-and-repo-filters: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: user filters and repo filters cannot be combined. modes: - local @@ -185,8 +225,13 @@ cases: - choose either user filters or repo filters reject-repo-filter-conflict: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: repo filters are mutually exclusive. modes: - local @@ -196,8 +241,13 @@ cases: - choose only one of --repos reject-repos-created-after-malformed: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: repo creation dates must match YYYY-MM-DD at parse time. modes: - local @@ -207,8 +257,13 @@ cases: - string_pattern_mismatch reject-get-removed-repositories-created-after: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: >- The removed --repositories-created-after spelling stays removed; the flag is --repos-created-after. @@ -220,8 +275,13 @@ cases: - "unrecognized arguments: --repositories-created-after" reject-verbosity-conflict: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: the --verbose and --quiet log-level aliases are mutually exclusive. modes: - local @@ -231,8 +291,13 @@ cases: - choose only one of --verbose/-v, --quiet/-q, or --silent/-s reject-bare-set: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: set requires an explicit mode flag. modes: - local @@ -242,8 +307,13 @@ cases: - set requires one of --full reject-set-full-and-users: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: set modes are mutually exclusive. modes: - local @@ -253,8 +323,13 @@ cases: - choose at most one reject-set-user-filter-conflict: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: set user filters are mutually exclusive. modes: - local @@ -264,8 +339,13 @@ cases: - choose only one of --users reject-set-full-and-created-after: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: full overwrite cannot be combined with the additive date filter. modes: - local @@ -275,8 +355,13 @@ cases: - "--full cannot be combined with --created-after" reject-set-restore-path: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: "--restore-path belongs to restore; set does not accept it." modes: - local @@ -286,8 +371,13 @@ cases: - "unrecognized arguments: --restore-path" reject-bare-restore: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: restore requires a snapshot path. modes: - local @@ -297,8 +387,13 @@ cases: - restore requires --restore-path reject-restore-with-users: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: restore does not take user filters. modes: - local @@ -308,8 +403,13 @@ cases: - unrecognized arguments reject-restore-repos: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: restore does not take repo filters. modes: - local @@ -319,8 +419,13 @@ cases: - "unrecognized arguments: --repos" reject-restore-sync-saml-orgs: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: "--sync-saml-orgs belongs to set; restore does not accept it." modes: - local @@ -330,8 +435,13 @@ cases: - "unrecognized arguments: --sync-saml-orgs" reject-sync-saml-orgs-created-after: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: sync-saml-orgs does not take user filters. modes: - local @@ -341,8 +451,13 @@ cases: - unrecognized arguments reject-sync-saml-orgs-users: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: sync-saml-orgs does not take a user list. modes: - local @@ -352,8 +467,13 @@ cases: - "unrecognized arguments: --users" reject-sync-saml-orgs-full: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: "--full belongs to set; sync-saml-orgs does not accept it." modes: - local @@ -363,8 +483,13 @@ cases: - "unrecognized arguments: --full" reject-sync-saml-orgs-restore-path: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: parse-only — no instance reads or writes} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: parse-only — no instance reads or writes description: "--restore-path belongs to restore; sync-saml-orgs does not accept it." modes: - local @@ -375,7 +500,10 @@ cases: # ── Local state cases: full CLI runs against an in-memory instance ── and-filters-intersect: - # affects: {users: 1, repos: 1, perms: 1} + # affects: + # users: 1 + # repos: 1 + # perms: 1 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: 0} @@ -393,7 +521,10 @@ cases: expectedMutations: 1 regex-filters-scope: - # affects: {users: 2, repos: 2, perms: 4} + # affects: + # users: 2 + # repos: 2 + # perms: 4 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: 0} @@ -427,7 +558,10 @@ cases: expectedMutations: 2 saml-group-filter: - # affects: {users: 1, repos: 2, perms: 2} + # affects: + # users: 1 + # repos: 2 + # perms: 2 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: 0} @@ -445,7 +579,10 @@ cases: expectedMutations: 2 saml-group-live: - # affects: {users: 2, repos: 2, perms: 4} + # affects: + # users: 2 + # repos: 2 + # perms: 4 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: 0} @@ -480,7 +617,10 @@ cases: expectedMutations: 2 set-created-after-temp-user: - # affects: {users: 1, repos: 1, perms: 1} + # affects: + # users: 1 + # repos: 1 + # perms: 1 # cost: # bigO: O(R) # reads: {users: 1, repos: all, permScans: 1} @@ -507,7 +647,10 @@ cases: expectedMutations: 1 set-users-created-after: - # affects: {users: 2, repos: 2, perms: 4} + # affects: + # users: 2 + # repos: 2 + # perms: 4 # cost: # bigO: O(R) # reads: {users: selected, repos: all, permScans: selected} @@ -527,7 +670,10 @@ cases: expectedMutations: 4 set-users-without-explicit-perms: - # affects: {users: 1, repos: 2, perms: 2} + # affects: + # users: 1 + # repos: 2 + # perms: 2 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: rule-matched} @@ -553,7 +699,10 @@ cases: expectedMutations: 2 set-users-without-explicit-perms-default-batch: - # affects: {users: 1, repos: 2, perms: 2} + # affects: + # users: 1 + # repos: 2 + # perms: 2 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: rule-matched} @@ -579,7 +728,10 @@ cases: expectedMutations: 2 set-repos-without-explicit-perms: - # affects: {users: 2, repos: 1, perms: 2} + # affects: + # users: 2 + # repos: 1 + # perms: 2 # cost: # bigO: O(U·scan + R) # reads: {users: all, repos: all, permScans: all} @@ -600,7 +752,10 @@ cases: expectedMutations: 1 set-repos-created-after: - # affects: {users: 2, repos: 1, perms: 2} + # affects: + # users: 2 + # repos: 1 + # perms: 2 # cost: # bigO: O(U) # reads: {users: all, repos: date-matched, permScans: 0} @@ -619,7 +774,10 @@ cases: expectedMutations: 1 full-overwrite-dry-run: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U·scan + R) # reads: {users: all, repos: all, permScans: all} @@ -637,8 +795,13 @@ cases: expectedMutations: 0 empty-maps-noop: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: no rules → exits before any instance scan} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: no rules → exits before any instance scan description: >- An empty maps list is a no-op, not an error: zero mutations, existing grants untouched. @@ -652,7 +815,10 @@ cases: expectedMutations: 0 match-provider-and-host-fields: - # affects: {users: 2, repos: 2, perms: 4} + # affects: + # users: 2 + # repos: 2 + # perms: 4 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: 0} @@ -671,7 +837,10 @@ cases: expectedMutations: 2 add-users-by-email-and-list: - # affects: {users: 2, repos: 2, perms: 4} + # affects: + # users: 2 + # repos: 2 + # perms: 4 # cost: # bigO: O(R) # reads: {users: 2, repos: all, permScans: 2} @@ -691,7 +860,10 @@ cases: expectedMutations: 4 restore-dry-run-noop: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U·scan) # reads: {users: all, permScans: all} @@ -709,7 +881,10 @@ cases: expectedMutations: 0 restore-applies-snapshot: - # affects: {users: 2, repos: 1, perms: 2} + # affects: + # users: 2 + # repos: 1 + # perms: 2 # cost: # bigO: O(U·scan) # reads: {users: all, permScans: all} @@ -728,7 +903,10 @@ cases: expectedMutations: 1 restore-restores-pending: - # affects: {users: 0, repos: 2, perms: 2} (both perms are pending bindID grants) + # affects: + # users: 0 + # repos: 2 + # perms: 2 (both perms are pending bindID grants) # cost: # bigO: O(U·scan) # reads: {users: all, permScans: all + pending listing} @@ -747,7 +925,10 @@ cases: expectedMutations: 2 full-overwrite-preserves-pending: - # affects: {users: 2, repos: 1, perms: 2} + # affects: + # users: 2 + # repos: 1 + # perms: 2 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: 0} @@ -768,7 +949,10 @@ cases: expectedMutations: 1 full-overwrite-with-backup: - # affects: {users: 2, repos: 1, perms: 2} + # affects: + # users: 2 + # repos: 1 + # perms: 2 # cost: # bigO: O(U·scan + R) # reads: {users: all, repos: all, permScans: 2×all} @@ -787,8 +971,13 @@ cases: expectedMutations: 1 get-user-grants: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), reads: {users: 1, permScans: 1}} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # reads: {users: 1, permScans: 1} description: >- get scoped to one user captures that user's explicit grants and never mutates the instance. @@ -802,8 +991,13 @@ cases: # ── Local + live: expected-error cases, replayed read-only on the instance ── invalid-bad-regex: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: maps validation fails before any instance read} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: maps validation fails before any instance read description: >- An invalid Python regex in a filter is rejected by structural validation before any mutation. @@ -818,8 +1012,13 @@ cases: - is not a valid Python regex invalid-missing-repos-section: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: maps validation fails before any instance read} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: maps validation fails before any instance read description: >- A rule without a repos section is rejected by structural validation before any mutation. @@ -834,8 +1033,13 @@ cases: - "`repos:` section is missing" invalid-unknown-selector-field: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: maps validation fails before any instance read} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: maps validation fails before any instance read description: >- A typo'd selector field is rejected by structural validation before any mutation. @@ -850,8 +1054,13 @@ cases: - unknown users field 'userNames' invalid-set-created-after-date: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: date validation fails before any instance read} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: date validation fails before any instance read description: >- An impossible calendar date passes the YYYY-MM-DD shape check but set rejects it post-parse, before any mutation. @@ -866,8 +1075,13 @@ cases: - "--created-after must use YYYY-MM-DD" invalid-set-repos-created-after-date: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: date validation fails before any instance read} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: date validation fails before any instance read description: >- An impossible calendar date passes the YYYY-MM-DD shape check but the repo-scoped set rejects it post-parse, before any mutation. @@ -882,8 +1096,14 @@ cases: - "--repos-created-after must use YYYY-MM-DD" invalid-set-unknown-user: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), reads: ≤2 user lookups, note: fails before any scan} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # reads: ≤2 user lookups + # note: fails before any scan description: >- A --users value naming no Sourcegraph user fails before any mutation. modes: @@ -898,8 +1118,14 @@ cases: - "No Sourcegraph user found for 'username_doesnt_exist_01'" invalid-set-unknown-repo: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), reads: 1 repo-name lookup, note: fails before the user scan} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # reads: 1 repo-name lookup + # note: fails before the user scan description: >- A --repos value naming no Sourcegraph repo fails before any mutation. modes: @@ -914,8 +1140,13 @@ cases: - "No Sourcegraph repo found for: repo-doesnt-exist-49999" invalid-restore-wrong-schema-version: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: reads the snapshot file only — no instance reads} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: reads the snapshot file only — no instance reads description: >- A snapshot with an unsupported schema_version is refused without changing any state. @@ -929,8 +1160,13 @@ cases: - "snapshot schema_version is 1, expected" restore-missing-file: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: file-existence check only — no instance reads} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: file-existence check only — no instance reads description: >- restore with a snapshot path that does not exist fails without changing any state. @@ -946,7 +1182,10 @@ cases: # ── Local + live: mutating cases, seeded and restored on the instance ── no-match-noop: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: 0} @@ -965,7 +1204,10 @@ cases: expectedMutations: 0 set-repos-filter: - # affects: {users: 3, repos: 1, perms: 3} + # affects: + # users: 3 + # repos: 1 + # perms: 3 # cost: # bigO: O(U) # reads: {users: all, repos: 1, permScans: 0} @@ -988,7 +1230,10 @@ cases: expectedMutations: 1 add-users-preserves-existing: - # affects: {users: 1, repos: 2, perms: 2} + # affects: + # users: 1 + # repos: 2 + # perms: 2 # cost: # bigO: O(R) # reads: {users: 1, repos: all, permScans: 1} @@ -1008,7 +1253,10 @@ cases: expectedMutations: 2 full-overwrite-removes-stale-grant: - # affects: {users: 2, repos: 1, perms: 2} + # affects: + # users: 2 + # repos: 1 + # perms: 2 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: 0} @@ -1028,8 +1276,13 @@ cases: # ── Live only: real-instance validation and organization sync ── invalid-created-after-date: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: date validation fails before any instance read} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: date validation fails before any instance read description: >- An impossible calendar date passes the YYYY-MM-DD shape check but is rejected by date validation. @@ -1042,8 +1295,13 @@ cases: - "--created-after must use YYYY-MM-DD" invalid-missing-maps-file: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), note: maps-file existence check only — no instance reads} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # note: maps-file existence check only — no instance reads description: >- A maps path that does not exist is rejected with a pointer to the command that creates the default maps file. Also pins the --quiet @@ -1059,8 +1317,14 @@ cases: - set input file does not exist get-created-after-future: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), reads: {users: 0, permScans: 0}, note: server-side date filter selects nobody} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # reads: {users: 0, permScans: 0} + # note: server-side date filter selects nobody description: >- A far-future --created-after selects no users on the real instance. modes: @@ -1071,8 +1335,14 @@ cases: - Selected 0 user(s) for get output. get-user-created-after-future: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), reads: {users: 1 lookup, permScans: 0}, note: the user is filtered out by date} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # reads: {users: 1 lookup, permScans: 0} + # note: the user is filtered out by date description: >- --users combined with a far-future --created-after filters the named user out of the selection. @@ -1084,8 +1354,14 @@ cases: - no user metadata selected get-users-without-perms-created-after-future: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), reads: {users: 0, permScans: 0}, note: filtered candidate query selects nobody} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # reads: {users: 0, permScans: 0} + # note: filtered candidate query selects nobody description: >- --users-without-explicit-perms combined with a far-future --created-after selects no users. @@ -1097,7 +1373,10 @@ cases: - Selected 0 user(s) for get output. get-repos-filter: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U·scan) # reads: {users: all, repos: 1, permScans: all} @@ -1115,7 +1394,10 @@ cases: - Selected 1 repo(s) by exact name. get-repos-created-after-future: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U) # reads: {users: all, repos: ~1 page, permScans: 0} @@ -1132,8 +1414,14 @@ cases: - Selected 0 Sourcegraph repo(s) created on or after 2099-01-01. set-users-created-after-noop: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), reads: {users: 0}, note: server-side date filter selects nobody — exits before the repo scan} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # reads: {users: 0} + # note: server-side date filter selects nobody — exits before the repo scan description: >- A far-future --created-after selects no users on the real instance: zero mutations, seeded state untouched. @@ -1148,7 +1436,10 @@ cases: expectedMutations: 0 set-repos-created-after-noop: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U) # reads: {users: all, repos: ~1 page, permScans: 0} @@ -1167,7 +1458,10 @@ cases: expectedMutations: 0 sync-saml-orgs-dry-run: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U) # reads: {users: all, orgMembers: all} @@ -1184,7 +1478,10 @@ cases: - Dry run complete set-users-sync-saml-orgs-dry-run: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U) # reads: {users: 1 + all, permScans: 1} @@ -1201,7 +1498,10 @@ cases: - Dry run complete set-full-sync-saml-orgs-dry-run: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U·scan + R) # reads: {users: all, repos: all, permScans: all} @@ -1219,7 +1519,10 @@ cases: - Dry run complete set-created-after-sync-saml-orgs-dry-run: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U) # reads: {users: 0 + all} @@ -1236,7 +1539,10 @@ cases: - Dry run complete set-repos-sync-saml-orgs-dry-run: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U·scan) # reads: {users: 2×all, repos: 1, permScans: all} @@ -1254,7 +1560,10 @@ cases: - Dry run complete set-users-without-perms-sync-saml-orgs-dry-run: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U+R) # reads: {users: 2×all, repos: all, permScans: rule-matched} @@ -1273,7 +1582,10 @@ cases: - Dry run complete set-repos-created-after-sync-saml-orgs-dry-run: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U) # reads: {users: 0 + all, repos: ~1 page} @@ -1290,7 +1602,10 @@ cases: - Dry run complete sync-saml-orgs-apply: - # affects: {users: 0, repos: 0, perms: 0} (converges org memberships only; no repo perms) + # affects: + # users: 0 + # repos: 0 + # perms: 0 (converges org memberships only; no repo perms) # cost: # bigO: O(U) # reads: {users: all, orgMembers: all} @@ -1307,7 +1622,10 @@ cases: # ── Live + performance: timed, measured runs (slowest) ── full-overwrite-unions: - # affects: {users: 3, repos: 2, perms: 5} + # affects: + # users: 3 + # repos: 2 + # perms: 5 # cost: # bigO: O(U+R) # reads: {users: all, repos: all, permScans: 0} @@ -1330,8 +1648,13 @@ cases: expectedMutations: 2 get-user-baseline: - # affects: {users: 0, repos: 0, perms: 0} - # cost: {bigO: O(1), reads: {users: 1, permScans: 1}} + # affects: + # users: 0 + # repos: 0 + # perms: 0 + # cost: + # bigO: O(1) + # reads: {users: 1, permScans: 1} description: Timed read-only baseline of one user's explicit grants. modes: - performance @@ -1339,7 +1662,10 @@ cases: expectedExitCode: 0 get-full-snapshot: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U·scan) # reads: {users: all, permScans: all} @@ -1357,7 +1683,10 @@ cases: expectedMutations: 0 get-repos-without-explicit-perms: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U·scan + R) # reads: {users: all, repos: all, permScans: all} @@ -1375,7 +1704,10 @@ cases: expectedMutations: 0 set-repos-without-perms-sync-saml-orgs-dry-run: - # affects: {users: 0, repos: 0, perms: 0} + # affects: + # users: 0 + # repos: 0 + # perms: 0 # cost: # bigO: O(U·scan + R) # reads: {users: 2×all, repos: all, permScans: all} From d169c196e21a90225febe6d96b7562a377fc6fe2 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:53:26 -0600 Subject: [PATCH 5/5] Document tests with scope / cost notes --- tests/tests.yaml | 705 +++++++++++++++++++++++++++-------------------- 1 file changed, 410 insertions(+), 295 deletions(-) diff --git a/tests/tests.yaml b/tests/tests.yaml index a884a58..4898ef0 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -35,10 +35,10 @@ # args State cases: the command plus Config fields (`command` # plus snake_case field names; true = bare flag, lists = # comma-joined values). ONE mapping drives BOTH -# entrypoints: locally the case runs twice — the +# entrypoints: locally the case runs twice -- the # generated command line through the real argument # parser, and the same mapping through the Python import -# API (cli.Config + resolve_command + run_command) — +# API (cli.Config + resolve_command + run_command) -- # and both must produce the identical expected state. # Flags come from the Config field metadata, never # guessed. maps_path defaults to the case directory's @@ -69,25 +69,26 @@ # # Each case carries two comment maps sizing its work: # -# affects: — net state change between before.json and after.json: -# users: distinct users whose explicit grants change -# repos: repos whose explicit or pending grant lists change -# perms: grant additions + removals (user-repo pairs, incl. pending -# bindIDs) +# scope: -- net state change between before.json and after.json: +# users: distinct users whose explicit grants change +# repos: repos whose explicit or pending grant lists change +# perms: grant additions + removals (user-repo pairs, incl. pending +# bindIDs) # Rejections, expected errors, dry runs, no-ops, and read-only gets affect # nothing. Overwrite mutations that resend an identical list count in # expectedMutations but not here. # -# cost: — the reads + writes driving runtime: -# bigO: complexity class. U = user count, R = repo count, -# U·scan = per-user explicit-grant scan (the ~400 s tier) -# reads: users: user-metadata records fetched (paged, fast) -# repos: repo records fetched (paged, fast) -# permScans: per-user explicit-grant reads — the DOMINANT -# cost: the server materializes every accessible -# repo per probe (~25 users/s; all ≈ 400 s live) -# writes: GraphQL permission mutations (= expectedMutations) -# note: what bounds or skips the work +# cost: -- the reads + writes driving runtime: +# bigO: complexity class. U = user count, R = repo count, +# U*scan = per-user explicit-grant scan (the ~400 s tier) +# reads: +# users: user-metadata records fetched (paged, fast) +# repos: repo records fetched (paged, fast) +# userPermScans: per-user explicit-grant reads -- the DOMINANT cost: +# the server materializes every accessible repo per +# probe (~25 users/s; all ~ 400 s live) +# writes: GraphQL permission mutations (= expectedMutations) +# note: what bounds or skips the work # `all` means a full-instance scan: 10,000 users / 50,000 repos on the # live instance (tests/setup.yaml); locally it is the fixture's tiny # counts, so every local run is sub-second regardless of class. @@ -95,15 +96,15 @@ cases: - # ── Local parse replays: argument validation, in-process, no files (fastest) ── + # -- Local parse replays: argument validation, in-process, no files (fastest) -- reject-bare-invocation: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: A bare invocation without a command prints usage and exits 2. modes: - local @@ -113,13 +114,13 @@ cases: - "the following arguments are required: COMMAND" reject-unknown-command: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: An unknown subcommand is rejected with the valid choices. modes: - local @@ -129,13 +130,13 @@ cases: - "invalid choice: 'bogus'" reject-two-commands: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: Two subcommands in one invocation are rejected. modes: - local @@ -145,13 +146,13 @@ cases: - unrecognized arguments reject-get-apply: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: get is read-only; --apply is rejected. modes: - local @@ -161,13 +162,13 @@ cases: - unrecognized arguments reject-get-full: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: --full requires the set command. modes: - local @@ -177,13 +178,13 @@ cases: - unrecognized arguments reject-malformed-date: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: dates must match YYYY-MM-DD before any network call. modes: - local @@ -193,13 +194,13 @@ cases: - string_pattern_mismatch reject-user-filter-conflict: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: user filters are mutually exclusive. modes: - local @@ -209,13 +210,13 @@ cases: - choose only one of --users reject-user-and-repo-filters: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: user filters and repo filters cannot be combined. modes: - local @@ -225,13 +226,13 @@ cases: - choose either user filters or repo filters reject-repo-filter-conflict: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: repo filters are mutually exclusive. modes: - local @@ -241,13 +242,13 @@ cases: - choose only one of --repos reject-repos-created-after-malformed: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: repo creation dates must match YYYY-MM-DD at parse time. modes: - local @@ -257,13 +258,13 @@ cases: - string_pattern_mismatch reject-get-removed-repositories-created-after: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: >- The removed --repositories-created-after spelling stays removed; the flag is --repos-created-after. @@ -275,13 +276,13 @@ cases: - "unrecognized arguments: --repositories-created-after" reject-verbosity-conflict: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: the --verbose and --quiet log-level aliases are mutually exclusive. modes: - local @@ -291,13 +292,13 @@ cases: - choose only one of --verbose/-v, --quiet/-q, or --silent/-s reject-bare-set: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: set requires an explicit mode flag. modes: - local @@ -307,13 +308,13 @@ cases: - set requires one of --full reject-set-full-and-users: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: set modes are mutually exclusive. modes: - local @@ -323,13 +324,13 @@ cases: - choose at most one reject-set-user-filter-conflict: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: set user filters are mutually exclusive. modes: - local @@ -339,13 +340,13 @@ cases: - choose only one of --users reject-set-full-and-created-after: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: full overwrite cannot be combined with the additive date filter. modes: - local @@ -355,13 +356,13 @@ cases: - "--full cannot be combined with --created-after" reject-set-restore-path: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: "--restore-path belongs to restore; set does not accept it." modes: - local @@ -371,13 +372,13 @@ cases: - "unrecognized arguments: --restore-path" reject-bare-restore: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: restore requires a snapshot path. modes: - local @@ -387,13 +388,13 @@ cases: - restore requires --restore-path reject-restore-with-users: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: restore does not take user filters. modes: - local @@ -403,13 +404,13 @@ cases: - unrecognized arguments reject-restore-repos: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: restore does not take repo filters. modes: - local @@ -419,13 +420,13 @@ cases: - "unrecognized arguments: --repos" reject-restore-sync-saml-orgs: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: "--sync-saml-orgs belongs to set; restore does not accept it." modes: - local @@ -435,13 +436,13 @@ cases: - "unrecognized arguments: --sync-saml-orgs" reject-sync-saml-orgs-created-after: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: sync-saml-orgs does not take user filters. modes: - local @@ -451,13 +452,13 @@ cases: - unrecognized arguments reject-sync-saml-orgs-users: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: sync-saml-orgs does not take a user list. modes: - local @@ -467,13 +468,13 @@ cases: - "unrecognized arguments: --users" reject-sync-saml-orgs-full: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: "--full belongs to set; sync-saml-orgs does not accept it." modes: - local @@ -483,13 +484,13 @@ cases: - "unrecognized arguments: --full" reject-sync-saml-orgs-restore-path: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: parse-only — no instance reads or writes + # bigO: O(1) + # note: parse-only -- no instance reads or writes description: "--restore-path belongs to restore; sync-saml-orgs does not accept it." modes: - local @@ -498,15 +499,18 @@ cases: expectedOutput: - "unrecognized arguments: --restore-path" - # ── Local state cases: full CLI runs against an in-memory instance ── + # -- Local state cases: full CLI runs against an in-memory instance -- and-filters-intersect: - # affects: + # scope: # users: 1 # repos: 1 # perms: 1 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: 0} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: 0 # writes: 1 mutation description: >- Multiple user filters AND together: both users are in the SAML group, @@ -521,13 +525,16 @@ cases: expectedMutations: 1 regex-filters-scope: - # affects: + # scope: # users: 2 # repos: 2 # perms: 4 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: 0} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: 0 # writes: 2 mutations description: >- Email and repo-name regex filters scope grants to matching users and @@ -558,13 +565,16 @@ cases: expectedMutations: 2 saml-group-filter: - # affects: + # scope: # users: 1 # repos: 2 # perms: 2 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: 0} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: 0 # writes: 2 mutations description: >- authProvider samlGroup filter grants Bitbucket repos only to users whose @@ -579,13 +589,16 @@ cases: expectedMutations: 2 saml-group-live: - # affects: + # scope: # users: 2 # repos: 2 # perms: 4 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: 0} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: 0 # writes: 2 mutations description: >- authProvider samlGroup filter against the FABRICATED SAML accounts @@ -617,15 +630,18 @@ cases: expectedMutations: 2 set-created-after-temp-user: - # affects: + # scope: # users: 1 # repos: 1 # perms: 1 # cost: - # bigO: O(R) - # reads: {users: 1, repos: all, permScans: 1} + # bigO: O(R) + # reads: + # users: 1 + # repos: all + # userPermScans: 1 # writes: 1 mutation - # note: server-side date filter selects only the temp user + # note: server-side date filter selects only the temp user description: >- POSITIVE created-after selection on the real instance: the harness creates a fresh temporary user (created today), so @@ -647,15 +663,18 @@ cases: expectedMutations: 1 set-users-created-after: - # affects: + # scope: # users: 2 # repos: 2 # perms: 4 # cost: - # bigO: O(R) - # reads: {users: selected, repos: all, permScans: selected} + # bigO: O(R) + # reads: + # users: selected + # repos: all + # userPermScans: selected # writes: 4 mutations - # note: server-side date filter + # note: server-side date filter description: >- createdAfter mode additively grants mapped repos to users created on/after the date, preserving existing grants. @@ -670,15 +689,18 @@ cases: expectedMutations: 4 set-users-without-explicit-perms: - # affects: + # scope: # users: 1 # repos: 2 # perms: 2 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: rule-matched} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: rule-matched # writes: 2 mutations - # note: candidates + hydration for all users; perm probes only for rule-matched + # note: candidates + hydration for all users; perm probes only for rule-matched description: >- --users-without-explicit-perms additively grants mapped repos only to users who currently hold no explicit grants anywhere. The local @@ -699,15 +721,18 @@ cases: expectedMutations: 2 set-users-without-explicit-perms-default-batch: - # affects: + # scope: # users: 1 # repos: 2 # perms: 2 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: rule-matched} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: rule-matched # writes: 2 mutations - # note: candidates + hydration for all users; perm probes only for rule-matched + # note: candidates + hydration for all users; perm probes only for rule-matched description: >- Performance-tier twin of set-users-without-explicit-perms at default --parallelism and --explicit-permissions-batch-size, so the measured @@ -728,15 +753,18 @@ cases: expectedMutations: 2 set-repos-without-explicit-perms: - # affects: + # scope: # users: 2 # repos: 1 # perms: 2 # cost: - # bigO: O(U·scan + R) - # reads: {users: all, repos: all, permScans: all} + # bigO: O(U*scan + R) + # reads: + # users: all + # repos: all + # userPermScans: all # writes: 1 mutation - # note: mode forces the full before-snapshot + # note: mode forces the full before-snapshot description: >- --repos-without-explicit-perms overwrites only repos that currently have no explicit grants. Runs in the performance tier on the instance @@ -752,15 +780,18 @@ cases: expectedMutations: 1 set-repos-created-after: - # affects: + # scope: # users: 2 # repos: 1 # perms: 2 # cost: - # bigO: O(U) - # reads: {users: all, repos: date-matched, permScans: 0} + # bigO: O(U) + # reads: + # users: all + # repos: date-matched + # userPermScans: 0 # writes: 1 mutation - # note: createdAt-ordered repo scan stops at the threshold + # note: createdAt-ordered repo scan stops at the threshold description: >- --repos-created-after scopes the overwrite to repos created on/after the date. @@ -774,18 +805,21 @@ cases: expectedMutations: 1 full-overwrite-dry-run: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U·scan + R) - # reads: {users: all, repos: all, permScans: all} + # bigO: O(U*scan + R) + # reads: + # users: all + # repos: all + # userPermScans: all # writes: none - # note: dry run forces the before-capture + # note: dry run forces the before-capture description: >- set without --apply plans the full overwrite but makes zero mutations - and leaves state untouched — the dry-run default never mutates. + and leaves state untouched -- the dry-run default never mutates. modes: - local args: @@ -795,13 +829,13 @@ cases: expectedMutations: 0 empty-maps-noop: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: no rules → exits before any instance scan + # bigO: O(1) + # note: no rules -> exits before any instance scan description: >- An empty maps list is a no-op, not an error: zero mutations, existing grants untouched. @@ -815,13 +849,16 @@ cases: expectedMutations: 0 match-provider-and-host-fields: - # affects: + # scope: # users: 2 # repos: 2 # perms: 4 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: 0} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: 0 # writes: 2 mutations description: >- Users matched by authProvider type/serviceID/clientID/displayName @@ -837,13 +874,16 @@ cases: expectedMutations: 2 add-users-by-email-and-list: - # affects: + # scope: # users: 2 # repos: 2 # perms: 4 # cost: - # bigO: O(R) - # reads: {users: 2, repos: all, permScans: 2} + # bigO: O(R) + # reads: + # users: 2 + # repos: all + # userPermScans: 2 # writes: 4 mutations description: >- --users accepts a comma-delimited mix of an email address and a @@ -860,15 +900,17 @@ cases: expectedMutations: 4 restore-dry-run-noop: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U·scan) - # reads: {users: all, permScans: all} + # bigO: O(U*scan) + # reads: + # users: all + # userPermScans: all # writes: none - # note: current-state capture scans every user + # note: current-state capture scans every user description: >- restore without --apply plans against a snapshot that differs from current state but makes zero mutations. @@ -881,15 +923,17 @@ cases: expectedMutations: 0 restore-applies-snapshot: - # affects: + # scope: # users: 2 # repos: 1 # perms: 2 # cost: - # bigO: O(U·scan) - # reads: {users: all, permScans: all} + # bigO: O(U*scan) + # reads: + # users: all + # userPermScans: all # writes: 1 mutation - # note: current-state capture scans every user + # note: current-state capture scans every user description: >- restore --apply overwrites the repo that differs from the snapshot and skips the repo that already matches. @@ -903,13 +947,15 @@ cases: expectedMutations: 1 restore-restores-pending: - # affects: + # scope: # users: 0 # repos: 2 # perms: 2 (both perms are pending bindID grants) # cost: - # bigO: O(U·scan) - # reads: {users: all, permScans: all + pending listing} + # bigO: O(U*scan) + # reads: + # users: all + # userPermScans: all + pending listing # writes: 2 mutations description: >- restore --apply recreates the snapshot's pending grants (bindIDs that @@ -925,19 +971,22 @@ cases: expectedMutations: 2 full-overwrite-preserves-pending: - # affects: + # scope: # users: 2 # repos: 1 # perms: 2 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: 0} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: 0 # writes: 1 mutation - # note: pending listing only + # note: pending listing only description: >- A full-overwrite apply (without backup, so the pending state is fetched live) resends each mapped repo's pending bindIDs and leaves - unmapped repos' pending grants alone — the script neither creates + unmapped repos' pending grants alone -- the script neither creates nor loses pending permissions. modes: - local @@ -949,15 +998,18 @@ cases: expectedMutations: 1 full-overwrite-with-backup: - # affects: + # scope: # users: 2 # repos: 1 # perms: 2 # cost: - # bigO: O(U·scan + R) - # reads: {users: all, repos: all, permScans: 2×all} + # bigO: O(U*scan + R) + # reads: + # users: all + # repos: all + # userPermScans: 2x all # writes: 1 mutation - # note: before + after snapshots + # note: before + after snapshots description: >- The default backup path (no --no-backup) captures before/after snapshots (including a pending grant on the mutated repo, which must @@ -971,13 +1023,15 @@ cases: expectedMutations: 1 get-user-grants: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # reads: {users: 1, permScans: 1} + # bigO: O(1) + # reads: + # users: 1 + # userPermScans: 1 description: >- get scoped to one user captures that user's explicit grants and never mutates the instance. @@ -989,15 +1043,15 @@ cases: - test_user_09991 expectedMutations: 0 - # ── Local + live: expected-error cases, replayed read-only on the instance ── + # -- Local + live: expected-error cases, replayed read-only on the instance -- invalid-bad-regex: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: maps validation fails before any instance read + # bigO: O(1) + # note: maps validation fails before any instance read description: >- An invalid Python regex in a filter is rejected by structural validation before any mutation. @@ -1012,13 +1066,13 @@ cases: - is not a valid Python regex invalid-missing-repos-section: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: maps validation fails before any instance read + # bigO: O(1) + # note: maps validation fails before any instance read description: >- A rule without a repos section is rejected by structural validation before any mutation. @@ -1033,13 +1087,13 @@ cases: - "`repos:` section is missing" invalid-unknown-selector-field: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: maps validation fails before any instance read + # bigO: O(1) + # note: maps validation fails before any instance read description: >- A typo'd selector field is rejected by structural validation before any mutation. @@ -1054,13 +1108,13 @@ cases: - unknown users field 'userNames' invalid-set-created-after-date: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: date validation fails before any instance read + # bigO: O(1) + # note: date validation fails before any instance read description: >- An impossible calendar date passes the YYYY-MM-DD shape check but set rejects it post-parse, before any mutation. @@ -1075,13 +1129,13 @@ cases: - "--created-after must use YYYY-MM-DD" invalid-set-repos-created-after-date: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: date validation fails before any instance read + # bigO: O(1) + # note: date validation fails before any instance read description: >- An impossible calendar date passes the YYYY-MM-DD shape check but the repo-scoped set rejects it post-parse, before any mutation. @@ -1096,14 +1150,15 @@ cases: - "--repos-created-after must use YYYY-MM-DD" invalid-set-unknown-user: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # reads: ≤2 user lookups - # note: fails before any scan + # bigO: O(1) + # reads: + # users: <=2 lookups + # note: fails before any scan description: >- A --users value naming no Sourcegraph user fails before any mutation. modes: @@ -1118,14 +1173,15 @@ cases: - "No Sourcegraph user found for 'username_doesnt_exist_01'" invalid-set-unknown-repo: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # reads: 1 repo-name lookup - # note: fails before the user scan + # bigO: O(1) + # reads: + # repos: 1 name lookup + # note: fails before the user scan description: >- A --repos value naming no Sourcegraph repo fails before any mutation. modes: @@ -1140,13 +1196,13 @@ cases: - "No Sourcegraph repo found for: repo-doesnt-exist-49999" invalid-restore-wrong-schema-version: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: reads the snapshot file only — no instance reads + # bigO: O(1) + # note: reads the snapshot file only -- no instance reads description: >- A snapshot with an unsupported schema_version is refused without changing any state. @@ -1160,13 +1216,13 @@ cases: - "snapshot schema_version is 1, expected" restore-missing-file: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: file-existence check only — no instance reads + # bigO: O(1) + # note: file-existence check only -- no instance reads description: >- restore with a snapshot path that does not exist fails without changing any state. @@ -1180,15 +1236,18 @@ cases: expectedErrors: - "restore snapshot file does not exist" - # ── Local + live: mutating cases, seeded and restored on the instance ── + # -- Local + live: mutating cases, seeded and restored on the instance -- no-match-noop: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: 0} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: 0 # writes: none description: >- A rule matching no users produces zero mutations and leaves existing @@ -1204,13 +1263,16 @@ cases: expectedMutations: 0 set-repos-filter: - # affects: + # scope: # users: 3 # repos: 1 # perms: 3 # cost: - # bigO: O(U) - # reads: {users: all, repos: 1, permScans: 0} + # bigO: O(U) + # reads: + # users: all + # repos: 1 + # userPermScans: 0 # writes: 1 mutation description: >- --repos scopes the full overwrite to the listed repos; other mapped @@ -1230,13 +1292,16 @@ cases: expectedMutations: 1 add-users-preserves-existing: - # affects: + # scope: # users: 1 # repos: 2 # perms: 2 # cost: - # bigO: O(R) - # reads: {users: 1, repos: all, permScans: 1} + # bigO: O(R) + # reads: + # users: 1 + # repos: all + # userPermScans: 1 # writes: 2 mutations description: >- Additive --users mode grants mapped repos to one user without dropping @@ -1253,13 +1318,16 @@ cases: expectedMutations: 2 full-overwrite-removes-stale-grant: - # affects: + # scope: # users: 2 # repos: 1 # perms: 2 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: 0} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: 0 # writes: 1 mutation description: >- Full set mode overwrites a mapped repo's explicit users, removing grants @@ -1274,15 +1342,15 @@ cases: no_backup: true expectedMutations: 1 - # ── Live only: real-instance validation and organization sync ── + # -- Live only: real-instance validation and organization sync -- invalid-created-after-date: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: date validation fails before any instance read + # bigO: O(1) + # note: date validation fails before any instance read description: >- An impossible calendar date passes the YYYY-MM-DD shape check but is rejected by date validation. @@ -1295,13 +1363,13 @@ cases: - "--created-after must use YYYY-MM-DD" invalid-missing-maps-file: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # note: maps-file existence check only — no instance reads + # bigO: O(1) + # note: maps-file existence check only -- no instance reads description: >- A maps path that does not exist is rejected with a pointer to the command that creates the default maps file. Also pins the --quiet @@ -1317,14 +1385,16 @@ cases: - set input file does not exist get-created-after-future: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # reads: {users: 0, permScans: 0} - # note: server-side date filter selects nobody + # bigO: O(1) + # reads: + # users: 0 + # userPermScans: 0 + # note: server-side date filter selects nobody description: >- A far-future --created-after selects no users on the real instance. modes: @@ -1335,14 +1405,16 @@ cases: - Selected 0 user(s) for get output. get-user-created-after-future: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # reads: {users: 1 lookup, permScans: 0} - # note: the user is filtered out by date + # bigO: O(1) + # reads: + # users: 1 lookup + # userPermScans: 0 + # note: the user is filtered out by date description: >- --users combined with a far-future --created-after filters the named user out of the selection. @@ -1354,14 +1426,16 @@ cases: - no user metadata selected get-users-without-perms-created-after-future: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # reads: {users: 0, permScans: 0} - # note: filtered candidate query selects nobody + # bigO: O(1) + # reads: + # users: 0 + # userPermScans: 0 + # note: filtered candidate query selects nobody description: >- --users-without-explicit-perms combined with a far-future --created-after selects no users. @@ -1373,15 +1447,18 @@ cases: - Selected 0 user(s) for get output. get-repos-filter: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U·scan) - # reads: {users: all, repos: 1, permScans: all} + # bigO: O(U*scan) + # reads: + # users: all + # repos: 1 + # userPermScans: all # writes: none - # note: ~400 s at 10k users + # note: ~400 s at 10k users description: >- get scoped to one repo by exact name. The snapshot still scans every user's explicit grants to find the repo's holders (measured ~400 s at @@ -1394,15 +1471,18 @@ cases: - Selected 1 repo(s) by exact name. get-repos-created-after-future: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U) - # reads: {users: all, repos: ~1 page, permScans: 0} + # bigO: O(U) + # reads: + # users: all + # repos: ~1 page + # userPermScans: 0 # writes: none - # note: empty repo selection skips the perm probes + # note: empty repo selection skips the perm probes description: >- A far-future --repos-created-after selects no repos. Also pins the --verbose alias: DEBUG verbosity must not hide the INFO summary. @@ -1414,14 +1494,15 @@ cases: - Selected 0 Sourcegraph repo(s) created on or after 2099-01-01. set-users-created-after-noop: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # reads: {users: 0} - # note: server-side date filter selects nobody — exits before the repo scan + # bigO: O(1) + # reads: + # users: 0 + # note: server-side date filter selects nobody -- exits before the repo scan description: >- A far-future --created-after selects no users on the real instance: zero mutations, seeded state untouched. @@ -1436,13 +1517,16 @@ cases: expectedMutations: 0 set-repos-created-after-noop: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U) - # reads: {users: all, repos: ~1 page, permScans: 0} + # bigO: O(U) + # reads: + # users: all + # repos: ~1 page + # userPermScans: 0 # writes: none description: >- A far-future --repos-created-after selects no repos on the real @@ -1458,15 +1542,17 @@ cases: expectedMutations: 0 sync-saml-orgs-dry-run: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U) - # reads: {users: all, orgMembers: all} + # bigO: O(U) + # reads: + # users: all + # orgMembers: all # writes: none - # note: streams every user's SAML account data + # note: streams every user's SAML account data description: >- Standalone organization sync dry run. Also pins the explicit --env-file flag against its default value. @@ -1478,15 +1564,17 @@ cases: - Dry run complete set-users-sync-saml-orgs-dry-run: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U) - # reads: {users: 1 + all, permScans: 1} + # bigO: O(U) + # reads: + # users: 1 + all + # userPermScans: 1 # writes: none - # note: org phase streams all users + # note: org phase streams all users description: >- Combined permission + organization sync dispatch, user-scoped, dry run only. @@ -1498,15 +1586,18 @@ cases: - Dry run complete set-full-sync-saml-orgs-dry-run: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U·scan + R) - # reads: {users: all, repos: all, permScans: all} + # bigO: O(U*scan + R) + # reads: + # users: all + # repos: all + # userPermScans: all # writes: none - # note: ~395 s; org phase reuses the streamed users + # note: ~395 s; org phase reuses the streamed users description: >- Combined permission + organization sync dispatch, full mode, dry run only. The dry-run before-capture scans every user (measured ~395 s), @@ -1519,15 +1610,16 @@ cases: - Dry run complete set-created-after-sync-saml-orgs-dry-run: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U) - # reads: {users: 0 + all} + # bigO: O(U) + # reads: + # users: 0 + all # writes: none - # note: perm phase selects nobody; org phase streams all users + # note: perm phase selects nobody; org phase streams all users description: >- Combined permission + organization sync dispatch, created-after mode (far-future date selects no users), dry run only. @@ -1539,15 +1631,18 @@ cases: - Dry run complete set-repos-sync-saml-orgs-dry-run: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U·scan) - # reads: {users: 2×all, repos: 1, permScans: all} + # bigO: O(U*scan) + # reads: + # users: 2x all + # repos: 1 + # userPermScans: all # writes: none - # note: ~400 s; perm and org phases each stream all users + # note: ~400 s; perm and org phases each stream all users description: >- Combined permission + organization sync dispatch, repo-scoped, dry run only. The dry-run before-capture scans every user even for one repo @@ -1560,15 +1655,18 @@ cases: - Dry run complete set-users-without-perms-sync-saml-orgs-dry-run: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U+R) - # reads: {users: 2×all, repos: all, permScans: rule-matched} + # bigO: O(U+R) + # reads: + # users: 2x all + # repos: all + # userPermScans: rule-matched # writes: none - # note: hydration + org stream each cover all users + # note: hydration + org stream each cover all users description: >- Combined permission + organization sync dispatch, users-without-explicit-perms mode, dry run only. The unfiltered @@ -1582,15 +1680,17 @@ cases: - Dry run complete set-repos-created-after-sync-saml-orgs-dry-run: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U) - # reads: {users: 0 + all, repos: ~1 page} + # bigO: O(U) + # reads: + # users: 0 + all + # repos: ~1 page # writes: none - # note: perm phase selects nobody; org phase streams all users + # note: perm phase selects nobody; org phase streams all users description: >- Combined permission + organization sync dispatch, repos-created-after mode (far-future date selects no repos), dry run only. @@ -1602,14 +1702,16 @@ cases: - Dry run complete sync-saml-orgs-apply: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 (converges org memberships only; no repo perms) # cost: - # bigO: O(U) - # reads: {users: all, orgMembers: all} - # writes: org-membership mutations (convergent — usually few) + # bigO: O(U) + # reads: + # users: all + # orgMembers: all + # writes: org-membership mutations (convergent -- usually few) description: >- Org membership sync converges to SAML group data and validates its own outcome; it is safe to re-run. @@ -1620,21 +1722,24 @@ cases: expectedOutput: - "VALIDATION OK: all target org memberships match" - # ── Live + performance: timed, measured runs (slowest) ── + # -- Live + performance: timed, measured runs (slowest) -- full-overwrite-unions: - # affects: + # scope: # users: 3 # repos: 2 # perms: 5 # cost: - # bigO: O(U+R) - # reads: {users: all, repos: all, permScans: 0} + # bigO: O(U+R) + # reads: + # users: all + # repos: all + # userPermScans: 0 # writes: 2 mutations description: >- Full set mode unions users across rules, overwrites mapped repos, and leaves unmapped repos alone. Pending bindIDs ride along: one on a mapped repo must survive its overwrite, one on an unmapped canary - must stay untouched — live runs seed, verify, and restore them + must stay untouched -- live runs seed, verify, and restore them against the real instance. modes: - local @@ -1648,13 +1753,15 @@ cases: expectedMutations: 2 get-user-baseline: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(1) - # reads: {users: 1, permScans: 1} + # bigO: O(1) + # reads: + # users: 1 + # userPermScans: 1 description: Timed read-only baseline of one user's explicit grants. modes: - performance @@ -1662,15 +1769,17 @@ cases: expectedExitCode: 0 get-full-snapshot: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U·scan) - # reads: {users: all, permScans: all} + # bigO: O(U*scan) + # reads: + # users: all + # userPermScans: all # writes: none - # note: ~400 s at 10k users + # note: ~400 s at 10k users description: >- Bare get captures the full instance snapshot. Locally it proves get never mutates; on the instance it is the timed full 10k-user capture, @@ -1683,13 +1792,16 @@ cases: expectedMutations: 0 get-repos-without-explicit-perms: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U·scan + R) - # reads: {users: all, repos: all, permScans: all} + # bigO: O(U*scan + R) + # reads: + # users: all + # repos: all + # userPermScans: all # writes: none description: >- get --repos-without-explicit-perms scopes the snapshot to repos with @@ -1704,15 +1816,18 @@ cases: expectedMutations: 0 set-repos-without-perms-sync-saml-orgs-dry-run: - # affects: + # scope: # users: 0 # repos: 0 # perms: 0 # cost: - # bigO: O(U·scan + R) - # reads: {users: 2×all, repos: all, permScans: all} + # bigO: O(U*scan + R) + # reads: + # users: 2x all + # repos: all + # userPermScans: all # writes: none - # note: perm and org phases each stream all users + # note: perm and org phases each stream all users description: >- Combined permission + organization sync dispatch, repos-without-explicit-perms mode, dry run only. Needs the full