src-auth-perms-sync 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/.gitignore +3 -0
  2. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/AGENTS.md +35 -11
  3. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/PKG-INFO +1 -1
  4. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/dev/TODO.md +24 -21
  5. src_auth_perms_sync-0.3.0/dev/memory-efficiency.md → src_auth_perms_sync-0.4.0/dev/engineering-requests.md +108 -179
  6. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/dev/hooks/pre-commit +1 -5
  7. {src_auth_perms_sync-0.3.0/dev → src_auth_perms_sync-0.4.0/dev/memory-analysis}/memory-efficiency-generate.py +1 -1
  8. src_auth_perms_sync-0.4.0/dev/memory-efficiency.md +177 -0
  9. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/cli.py +212 -36
  10. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/orgs/sync.py +129 -109
  11. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/apply.py +137 -165
  12. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/command.py +745 -110
  13. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/full_set.py +180 -13
  14. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/mapping.py +29 -0
  15. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/maps.py +1 -1
  16. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/queries.py +95 -26
  17. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/restore.py +13 -12
  18. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/snapshot.py +316 -166
  19. src_auth_perms_sync-0.4.0/src/src_auth_perms_sync/permissions/sourcegraph.py +913 -0
  20. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/types.py +6 -0
  21. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/workflow.py +129 -6
  22. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/shared/queries.py +17 -9
  23. src_auth_perms_sync-0.4.0/src/src_auth_perms_sync/shared/run_context.py +265 -0
  24. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/shared/saml_groups.py +2 -2
  25. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/shared/site_config.py +10 -7
  26. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/shared/sourcegraph.py +10 -2
  27. src_auth_perms_sync-0.4.0/tests/README.md +146 -0
  28. src_auth_perms_sync-0.4.0/tests/__init__.py +12 -0
  29. src_auth_perms_sync-0.4.0/tests/e2e/__init__.py +11 -0
  30. src_auth_perms_sync-0.4.0/tests/e2e/case_runner.py +796 -0
  31. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/add-users-by-email-and-list/after.json +85 -0
  32. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/add-users-by-email-and-list/before.json +80 -0
  33. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/add-users-by-email-and-list/maps.yaml +11 -0
  34. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/add-users-preserves-existing/after.json +84 -0
  35. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/add-users-preserves-existing/before.json +82 -0
  36. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/add-users-preserves-existing/maps.yaml +10 -0
  37. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/and-filters-intersect/after.json +103 -0
  38. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/and-filters-intersect/before.json +101 -0
  39. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/and-filters-intersect/maps.yaml +11 -0
  40. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/empty-maps-noop/before.json +48 -0
  41. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/empty-maps-noop/maps.yaml +1 -0
  42. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-dry-run/before.json +90 -0
  43. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-dry-run/maps.yaml +16 -0
  44. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-removes-stale-grant/after.json +75 -0
  45. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-removes-stale-grant/before.json +75 -0
  46. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-removes-stale-grant/maps.yaml +9 -0
  47. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-unions/after.json +91 -0
  48. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-unions/before.json +90 -0
  49. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-unions/maps.yaml +16 -0
  50. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-with-backup/after.json +75 -0
  51. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-with-backup/before.json +75 -0
  52. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/full-overwrite-with-backup/maps.yaml +9 -0
  53. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/get-full-snapshot/before.json +48 -0
  54. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/get-repos-without-explicit-perms/before.json +54 -0
  55. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/get-user-grants/before.json +48 -0
  56. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-bad-regex/before.json +48 -0
  57. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-bad-regex/maps.yaml +8 -0
  58. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-missing-repos-section/before.json +48 -0
  59. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-missing-repos-section/maps.yaml +5 -0
  60. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-restore-wrong-schema-version/before.json +48 -0
  61. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-restore-wrong-schema-version/snapshot.json +21 -0
  62. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-set-created-after-date/before.json +46 -0
  63. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-set-created-after-date/maps.yaml +8 -0
  64. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-set-repos-created-after-date/before.json +46 -0
  65. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-set-repos-created-after-date/maps.yaml +8 -0
  66. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-set-unknown-repo/before.json +46 -0
  67. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-set-unknown-repo/maps.yaml +8 -0
  68. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-set-unknown-user/before.json +46 -0
  69. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-set-unknown-user/maps.yaml +8 -0
  70. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-unknown-selector-field/before.json +48 -0
  71. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/invalid-unknown-selector-field/maps.yaml +8 -0
  72. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/match-provider-and-host-fields/after.json +142 -0
  73. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/match-provider-and-host-fields/before.json +136 -0
  74. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/match-provider-and-host-fields/maps.yaml +13 -0
  75. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/no-match-noop/before.json +48 -0
  76. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/no-match-noop/maps.yaml +8 -0
  77. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/regex-filters-scope/after.json +97 -0
  78. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/regex-filters-scope/before.json +91 -0
  79. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/regex-filters-scope/maps.yaml +8 -0
  80. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/restore-applies-snapshot/after.json +70 -0
  81. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/restore-applies-snapshot/before.json +70 -0
  82. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/restore-applies-snapshot/snapshot.json +30 -0
  83. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/restore-dry-run-noop/before.json +70 -0
  84. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/restore-dry-run-noop/snapshot.json +30 -0
  85. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/restore-missing-file/before.json +49 -0
  86. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/saml-group-filter/after.json +140 -0
  87. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/saml-group-filter/before.json +136 -0
  88. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/saml-group-filter/maps.yaml +9 -0
  89. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/saml-group-live/after.json +181 -0
  90. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/saml-group-live/before.json +175 -0
  91. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/saml-group-live/maps.yaml +10 -0
  92. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-created-after-sync-saml-orgs-dry-run/maps.yaml +8 -0
  93. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-created-after-temp-user/after.json +42 -0
  94. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-created-after-temp-user/before.json +40 -0
  95. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-created-after-temp-user/maps.yaml +8 -0
  96. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-full-sync-saml-orgs-dry-run/maps.yaml +8 -0
  97. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-created-after/after.json +70 -0
  98. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-created-after/before.json +67 -0
  99. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-created-after/maps.yaml +10 -0
  100. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-created-after-noop/before.json +49 -0
  101. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-created-after-noop/maps.yaml +8 -0
  102. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-created-after-sync-saml-orgs-dry-run/maps.yaml +8 -0
  103. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-filter/after.json +72 -0
  104. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-filter/before.json +71 -0
  105. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-filter/maps.yaml +10 -0
  106. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-sync-saml-orgs-dry-run/maps.yaml +8 -0
  107. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-without-explicit-perms/after.json +72 -0
  108. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-without-explicit-perms/before.json +69 -0
  109. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-without-explicit-perms/maps.yaml +10 -0
  110. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-repos-without-perms-sync-saml-orgs-dry-run/maps.yaml +8 -0
  111. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-created-after/after.json +85 -0
  112. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-created-after/before.json +80 -0
  113. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-created-after/maps.yaml +9 -0
  114. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-created-after-noop/before.json +49 -0
  115. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-created-after-noop/maps.yaml +8 -0
  116. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-sync-saml-orgs-dry-run/maps.yaml +8 -0
  117. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-without-explicit-perms/after.json +98 -0
  118. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-without-explicit-perms/before.json +95 -0
  119. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-without-explicit-perms/maps.yaml +10 -0
  120. src_auth_perms_sync-0.4.0/tests/e2e/fixtures/set-users-without-perms-sync-saml-orgs-dry-run/maps.yaml +8 -0
  121. src_auth_perms_sync-0.4.0/tests/e2e/test_local_cases.py +94 -0
  122. src_auth_perms_sync-0.4.0/tests/integration/__init__.py +11 -0
  123. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/tests/integration/test_cli_entrypoint.py +1 -1
  124. src_auth_perms_sync-0.4.0/tests/run.py +3084 -0
  125. src_auth_perms_sync-0.4.0/tests/setup.py +419 -0
  126. src_auth_perms_sync-0.4.0/tests/tests.yaml +922 -0
  127. src_auth_perms_sync-0.4.0/tests/unit/__init__.py +11 -0
  128. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/tests/unit/test_cli_config.py +269 -21
  129. src_auth_perms_sync-0.4.0/tests/unit/test_command_additive.py +280 -0
  130. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/tests/unit/test_maps.py +72 -0
  131. src_auth_perms_sync-0.4.0/tests/unit/test_permissions_sourcegraph.py +308 -0
  132. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/tests/unit/test_restore.py +4 -8
  133. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/tests/unit/test_snapshot.py +211 -18
  134. src_auth_perms_sync-0.3.0/dev/test-end-to-end.py +0 -3042
  135. src_auth_perms_sync-0.3.0/src/src_auth_perms_sync/permissions/sourcegraph.py +0 -405
  136. src_auth_perms_sync-0.3.0/src/src_auth_perms_sync/shared/run_context.py +0 -34
  137. src_auth_perms_sync-0.3.0/tests/__init__.py +0 -1
  138. src_auth_perms_sync-0.3.0/tests/integration/__init__.py +0 -1
  139. src_auth_perms_sync-0.3.0/tests/unit/__init__.py +0 -1
  140. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/.env.example +0 -0
  141. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/.github/CODEOWNERS +0 -0
  142. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/.github/workflows/ci.yml +0 -0
  143. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/.github/workflows/release.yml +0 -0
  144. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/.github/workflows/validate.yml +0 -0
  145. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/.markdownlint-cli2.yaml +0 -0
  146. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/.python-version +0 -0
  147. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/LICENSE +0 -0
  148. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/README.md +0 -0
  149. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/SECURITY.md +0 -0
  150. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/dev/audit-dead-code.md +0 -0
  151. {src_auth_perms_sync-0.3.0/dev → src_auth_perms_sync-0.4.0/dev/memory-analysis}/mapping-efficiency.md +0 -0
  152. {src_auth_perms_sync-0.3.0/dev → src_auth_perms_sync-0.4.0/dev/memory-analysis}/memory-efficiency-analyze.py +0 -0
  153. {src_auth_perms_sync-0.3.0/dev → src_auth_perms_sync-0.4.0/dev/memory-analysis}/memory-efficiency-monitor-sourcegraph.sh +0 -0
  154. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/dev/update-python-versions.md +0 -0
  155. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/maps-example.yaml +0 -0
  156. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/pyproject.toml +0 -0
  157. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/renovate.json +0 -0
  158. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/__init__.py +0 -0
  159. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/__main__.py +0 -0
  160. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/orgs/__init__.py +0 -0
  161. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/orgs/command.py +0 -0
  162. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/orgs/queries.py +0 -0
  163. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/orgs/types.py +0 -0
  164. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/permissions/__init__.py +0 -0
  165. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/shared/__init__.py +0 -0
  166. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/shared/backups.py +0 -0
  167. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/src/src_auth_perms_sync/shared/types.py +0 -0
  168. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/tests/unit/test_apply.py +0 -0
  169. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/tests/unit/test_backups.py +0 -0
  170. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/tests/unit/test_saml_groups.py +0 -0
  171. {src_auth_perms_sync-0.3.0 → src_auth_perms_sync-0.4.0}/uv.lock +0 -0
@@ -12,6 +12,7 @@ __pycache__
12
12
  *.py[cod]
13
13
  *.py[oc]
14
14
  *.swp
15
+ *.tsv
15
16
  *.yaml
16
17
  build/
17
18
  dist/
@@ -24,3 +25,5 @@ wheels/
24
25
  !.env.example
25
26
  !.markdownlint-cli2.yaml
26
27
  !maps-example.yaml
28
+ !tests/tests.yaml
29
+ !tests/e2e/fixtures/**/maps.yaml
@@ -1,5 +1,10 @@
1
1
  # AGENTS.md
2
2
 
3
+ ## Reference materials
4
+
5
+ - GraphQL schema and database migrations (changes to SQL schema) are available in
6
+ <https://github.com/sourcegraph/artifacts>
7
+
3
8
  ## Linting
4
9
 
5
10
  ```bash
@@ -26,21 +31,40 @@ uv run src-auth-perms-sync --help
26
31
 
27
32
  ## Testing
28
33
 
29
- - First run a dry-run (default behaviour, without `--apply` flag) against a Sourcegraph instance
34
+ All testing runs through one entrypoint: `tests/run.py`. Output goes to the
35
+ console and to a per-run log file under `logs/`. Each level runs only its
36
+ own checks.
30
37
 
31
38
  ```bash
32
- uv run src-auth-perms-sync [--get]
33
- uv run src-auth-perms-sync --set maps.yaml --full
34
- uv run src-auth-perms-sync --restore backups/<source>/<run>/before.json
39
+ # Fast, no network (also what the pre-commit hook runs):
40
+ # lint, format, pyright, unit + fixture tests, CLI rejection matrix,
41
+ # randomized permission invariants
42
+ uv run tests/run.py
43
+
44
+ # End-to-end runs against the .env test instance with independent GraphQL
45
+ # read-back verification, and a wheel install smoke test
46
+ uv run tests/run.py --live
47
+
48
+ # Run a subset: comma-delimited test names, substring match
49
+ uv run tests/run.py --live full-overwrite-unions
50
+ uv run tests/run.py --live wheel,baseline
51
+
52
+ # Repeated timed runs with Jaeger trace retention, RSS sampling,
53
+ # optional kubectl load monitoring, and baseline comparison
54
+ uv run tests/run.py --performance --repeat 3
55
+ uv run tests/run.py --performance --baseline-command "uvx src-auth-perms-sync@latest" \
56
+ --fail-on-memory-regression-percent 10
57
+
58
+ # Regenerate fixture goldens after editing tests/e2e/fixtures/ cases
59
+ uv run tests/run.py --update-golden
35
60
  ```
36
61
 
37
- - Read the output, and evaluate the expected changes
38
- - If the expected changes look correct
39
- - Run with the `--apply` flag against the test instance
40
- - Read and evaluate the output for expected changes
41
- - Run with the `--restore` flag against the test instance
42
- - Always inspect the before / after snapshots in
43
- `src-auth-perms-sync-runs/<endpoint>/backups/` afterward to confirm the diff matches what you expected
62
+ - Fixture cases live in `tests/e2e/fixtures/<case>/` — see the README there
63
+ for the format. Add cases there to cover new mapping behaviors.
64
+ - For manual verification against a real instance, dry-run first (no
65
+ `--apply`), read the planned changes, then `--apply` on a scratch instance
66
+ and inspect the before/after snapshots under
67
+ `src-auth-perms-sync-runs/<endpoint>/runs/`.
44
68
 
45
69
  ## Release process
46
70
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: src-auth-perms-sync
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Set Sourcegraph permissions from authentication provider data
5
5
  Project-URL: Homepage, https://github.com/sourcegraph/src-auth-perms-sync
6
6
  Project-URL: Issues, https://github.com/sourcegraph/src-auth-perms-sync/issues
@@ -1,20 +1,21 @@
1
1
  # TODO
2
2
 
3
- ## High priority: Sync modes
3
+ ## Medium priority: extend SAML-group live coverage to org sync
4
4
 
5
- ### Fast
5
+ tests/setup.py now fabricates SAML accounts with synthetic groups
6
+ (`perms-sync-test-eng` / `perms-sync-test-sales`, see tests/setup.yaml).
7
+ saml-group-live covers permission mapping; add a seeded
8
+ `sync-saml-orgs --apply` live case that maps those groups to a throwaway
9
+ org and asserts membership is added AND removed (today's
10
+ sync-saml-orgs-apply only covers the single real Okta user, add-only).
6
11
 
7
- - Additive modes, to add new users’ perms quickly,
8
- without the extraneous load on the database of a full sync
9
- - Take a list of usernames and/or email addresses as input,
10
- query users on the instance for these,
11
- then trigger a perms sync for found users
12
- - Query the instance for all new users, which do not yet have explicit perms
13
- - Query the instance for all new repos, which do not yet have explicit perms
12
+ ## Decide: pendingBindIDs / usersWithPendingPermissions
14
13
 
15
- ### Full: Overwrite all perms
16
-
17
- - Separate full sync mode with an arg
14
+ The CLI cannot create pending permissions (it validates users exist), but
15
+ snapshots record `pending_bindIDs`, and setup.py / the live hygiene check
16
+ report (never delete) any that appear. Decide whether "grant before first
17
+ login" is a customer need; if not, consider dropping the snapshot field.
18
+ See the thread discussion 2026-06-11.
18
19
 
19
20
  ## High priority: Remote trigger on demand
20
21
 
@@ -29,14 +30,6 @@
29
30
  - How do we avoid stampedes (e.g., bulk repo sync triggering thousands
30
31
  of re-runs)?
31
32
 
32
- ## High priority: End to End test cases
33
-
34
- - Create test cases. Each test case should contain:
35
- - Before state
36
- - maps.yaml file
37
- - Expected after state
38
- - Script to run the script, and verify the after state matches the expected after state
39
-
40
33
  ## High priority: Verify perms are updated when a user's SAML groups change
41
34
 
42
35
  - If a user gets added to a new SAML group, which hits a mapping, ensure they
@@ -45,14 +38,24 @@
45
38
  ## High priority: Reduce worst-case full-permission sync load
46
39
 
47
40
  - Use the stress-run evidence in
48
- [memory-efficiency.md](./memory-efficiency.md)
41
+ [engineering-requests.md](./engineering-requests.md)
49
42
  to request Sourcegraph bulk explicit-permission read and write APIs.
43
+ New evidence 2026-06-10: the whole-instance apply (1,150 repo
44
+ overwrites x 10,002 bindIDs each at parallelism 16) crashed the test
45
+ instance's Postgres ("connection refused", "unexpected EOF"); the
46
+ client circuit breaker opened and the harness restored cleanly. That
47
+ stress cycle is now opt-in: `uv run tests/run.py --live "full cycle"`.
50
48
  - Add an explicit destructive/performance-test mode to the e2e runner so giant
51
49
  stress runs can skip or defer full restore cleanup when the goal is finding
52
50
  the server-side breaking point.
53
51
  - Revisit full snapshot capture once Sourcegraph exposes a bulk read path;
54
52
  replace aliased `User.permissionsInfo.repositories(source: API)` calls before
55
53
  raising concurrency further.
54
+ - `get --repos <name>` still scans every user's explicit grants to find one
55
+ repo's holders (~400 s at 10k users). A repo-centric read
56
+ (`repository.permissionsInfo.users` + site-admin disambiguation, as the
57
+ test harness already does) would make it seconds — see the repo-centric
58
+ section below.
56
59
 
57
60
  ## Low priority: Repo-centric path, when users > repos, or for cross-checking
58
61
 
@@ -1,173 +1,18 @@
1
- # Memory efficiency testing
2
-
3
- Use this when full snapshot capture or full-set apply is slow. The goal is to
4
- correlate `src-auth-perms-sync` structured logs with Sourcegraph Jaeger spans
5
- and pod/Postgres load, then use the evidence to ask Sourcegraph engineering for
6
- bulk explicit-permissions APIs.
7
-
8
- ## Capture a focused trace
9
-
10
- Run a small command with `--fetch-sg-traces`. This sends
11
- `X-Sourcegraph-Request-Trace` and a W3C `traceparent` header on each GraphQL
12
- request. Sourcegraph may also return `x-trace`, `x-trace-span`, and
13
- `x-trace-url` response headers.
14
-
15
- ```bash
16
- uv run src-auth-perms-sync get \
17
- --fetch-sg-traces \
18
- --sample-interval 0 \
19
- --parallelism 2 \
20
- --explicit-permissions-batch-size 25
21
- ```
22
-
23
- Find the slow GraphQL HTTP requests in the run log:
24
-
25
- ```bash
26
- LOG=src-auth-perms-sync-runs/<endpoint>/runs/<run>/log.json
27
-
28
- jq -r '
29
- select(.event == "http_request" and .phase == "end") |
30
- select(.url | endswith("/.api/graphql")) |
31
- [
32
- .duration_ms,
33
- (.response_headers["x-trace"] // ""),
34
- (.response_headers["x-trace-url"] // ""),
35
- (.request_headers.traceparent // "")
36
- ] | @tsv
37
- ' "$LOG" | sort -nr | head -20
38
- ```
39
-
40
- Prefer the `x-trace` value when present. If Sourcegraph did not return one,
41
- extract the trace ID from `traceparent`:
42
-
43
- ```bash
44
- TRACEPARENT=00-<trace-id>-<span-id>-01
45
- TRACE_ID="$(printf '%s' "$TRACEPARENT" | cut -d- -f2)"
46
- ```
47
-
48
- Fetch the trace JSON from Jaeger:
49
-
50
- ```bash
51
- curl -sS \
52
- -H "Authorization: token $SRC_ACCESS_TOKEN" \
53
- "$SRC_ENDPOINT/-/debug/jaeger/api/traces/$TRACE_ID" \
54
- > /tmp/sourcegraph-trace.json
55
- ```
56
-
57
- Jaeger ingestion can lag. If the API returns `trace not found`, wait briefly
58
- and retry. For long runs, fetch traces as soon as the relevant command
59
- finishes; older trace IDs can disappear before a full matrix ends.
60
-
61
- Summarize the hottest spans:
62
-
63
- ```bash
64
- uv run python - <<'PY'
65
- import collections
66
- import json
67
-
68
- trace = json.load(open("/tmp/sourcegraph-trace.json"))["data"][0]
69
- durations_by_operation = collections.defaultdict(list)
70
- for span in trace["spans"]:
71
- durations_by_operation[span["operationName"]].append(span["duration"] / 1000)
72
-
73
- for operation, durations in sorted(
74
- durations_by_operation.items(),
75
- key=lambda item: sum(item[1]),
76
- reverse=True,
77
- )[:15]:
78
- print(
79
- f"{operation}: count={len(durations)} "
80
- f"sum_ms={sum(durations):.1f} "
81
- f"max_ms={max(durations):.1f}"
82
- )
83
- PY
84
- ```
85
-
86
- Do not commit tokens, customer URLs, raw trace JSON, benchmark CSVs, or monitor
87
- artifacts. Keep them in `/tmp` unless a human asks to preserve them.
88
-
89
- ## Trace the end-to-end matrix
90
-
91
- Prefer the end-to-end runner as the single orchestrator. With
92
- `--fetch-sg-traces`, it passes Sourcegraph debug trace collection to every child
93
- CLI command, tails child JSON logs, and fetches Jaeger traces in the background
94
- while each child command is still running.
95
-
96
- ```bash
97
- uv run python dev/test-end-to-end.py \
98
- --fetch-sg-traces \
99
- --sample-interval 0 \
100
- --external-sample-interval 0 \
101
- --results-json /tmp/src-auth-perms-sync-end-to-end-trace.json \
102
- --results-csv /tmp/src-auth-perms-sync-end-to-end-trace.csv
103
- ```
104
-
105
- Useful trace options:
106
-
107
- - `--jaeger-trace-limit N`: fetch only the `N` slowest GraphQL traces per case.
108
- - `--jaeger-trace-limit 0`: send trace headers but skip Jaeger fetching.
109
- - `--jaeger-trace-parallelism N`: tune concurrent Jaeger fetches.
110
- - `--jaeger-trace-jsonl PATH`: stream compact trace summaries as JSON Lines.
111
- - `--jaeger-trace-dir PATH`: store complete raw Jaeger payloads.
112
-
113
- Raw trace files include:
114
-
115
- - `trace_request`: CLI-side HTTP and `graphql_query` correlation metadata,
116
- including query name, page number, page size, cursor presence, query byte
117
- count, variable names, response fields, status, and timing.
118
- - `jaeger_summary`: compact hot-operation and GraphQL-operation summary.
119
- - `jaeger_trace`: the complete Jaeger trace JSON returned by Sourcegraph.
120
-
121
- All runner flags are Config-backed. You can set them in the shell or `.env`
122
- with `SRC_AUTH_PERMS_SYNC_E2E_*` names, plus `SRC_ENDPOINT`,
123
- `SRC_ACCESS_TOKEN`, and `SRC_AUTH_PERMS_SYNC_TEST_USER`.
124
-
125
- For each tested batch size and parallelism, record:
126
-
127
- - CLI `capture_explicit_grants` duration from the structured log
128
- - slowest GraphQL `http_request` duration and its trace metadata
129
- - Jaeger counts and summed duration for `GraphQL Request`, `repos.Get`,
130
- `sql.conn.query`, and `database.PermsStore.LoadUserPermissions`
131
- - run-end `http_retry_count`, `http_request_attempt_count`, and timeout/error
132
- counts
133
-
134
- ## Monitor Sourcegraph load during e2e runs
135
-
136
- The runner can start the Sourcegraph pod/Postgres monitor and write monitor
137
- artifact paths into the result JSON:
138
-
139
- ```bash
140
- uv run python dev/test-end-to-end.py \
141
- --fetch-sg-traces \
142
- --monitor-sourcegraph-load \
143
- --sample-interval 0 \
144
- --external-sample-interval 0 \
145
- --results-json /tmp/src-auth-perms-sync-end-to-end-trace.json \
146
- --results-csv /tmp/src-auth-perms-sync-end-to-end-trace.csv
147
- ```
1
+ # Engineering requests
148
2
 
149
- By default, monitor output is written beside `--results-json` or
150
- `--results-csv` as `*-sourcegraph-load`, and the monitor's stdout/stderr goes
151
- to `*-sourcegraph-load.log`. Override the location with
152
- `--monitor-output-dir PATH`. Tune Kubernetes targets and sample intervals with
153
- the `--monitor-*` flags if the test namespace or pod names differ.
3
+ Use this when opening Sourcegraph Engineering issues from memory-efficiency
4
+ evidence. Capture steps stay in [memory-efficiency.md](./memory-efficiency.md);
5
+ this file keeps the request-ready problem statement, evidence, proposed API
6
+ shape, and copy/paste issue text.
154
7
 
155
- The lower-level helper remains available for focused profiling outside a full
156
- e2e run:
8
+ ## Requested Sourcegraph changes
157
9
 
158
- ```bash
159
- dev/memory-efficiency-monitor-sourcegraph.sh \
160
- --namespace m \
161
- --output-dir /tmp/src-auth-perms-sync-sourcegraph-load-$(date -u +%Y%m%d-%H%M%S)
162
- ```
163
-
164
- Stop the helper with Ctrl-C, or add `--duration-seconds N`. It samples
165
- Kubernetes CPU/memory, frontend and Postgres processes, cgroup CPU/memory
166
- pressure, Postgres active queries/waits/locks, `pg_stat_statements` when
167
- enabled, and frontend logs. On startup it runs `CREATE EXTENSION IF NOT EXISTS
168
- pg_stat_statements` and `pg_stat_statements_reset()` through `kubectl exec`
169
- against `pod/pgsql-0`, so statement summaries start clean for the monitored
170
- run.
10
+ 1. Add a bulk GraphQL read path for explicit API repository permissions.
11
+ 2. Add a cheaper presence/filter path for users without explicit API repo
12
+ permissions.
13
+ 3. Add Jaeger spans / metrics around the new store methods and around current
14
+ `ListUserPermissions` / `CountUserPermissions` paths.
15
+ 4. Follow up with a bulk overwrite API for large full-set applies.
171
16
 
172
17
  ## Current trace findings
173
18
 
@@ -226,11 +71,46 @@ slower under the large explicit-perms state. This reinforces that the CLI needs
226
71
  better Sourcegraph bulk read and write APIs for very large explicit permission
227
72
  sets.
228
73
 
229
- ## Sourcegraph engineering request
230
-
231
- `src-auth-perms-sync` needs to snapshot explicit API permissions for many
232
- users. Today it calls `User.permissionsInfo.repositories(source: API)` with
233
- GraphQL aliases. This is correct, but expensive at scale.
74
+ ## Concurrent-operator evidence (2026-06-10)
75
+
76
+ Four `src-auth-perms-sync` processes ran full explicit-permissions captures
77
+ concurrently against the 10k-user / 50k-repo test instance (each at
78
+ `--parallelism 8`, `--explicit-permissions-batch-size 25`), while a fifth ran a
79
+ small `set` command. Instance: single `pgsql-0` on an 8-core node.
80
+
81
+ Observed during the concurrent captures:
82
+
83
+ - `pgsql-0` CPU (`kubectl top`): 7,636–7,683 millicores of 8,000 (saturated).
84
+ - `frontend` / `gitserver` CPU: 124–138m / 2–3m (idle bystanders).
85
+ - `pg_stat_activity`: 29 active statements, all
86
+ `permsStore.ListUserPermissions`, **zero wait events** — pure CPU, no lock
87
+ contention.
88
+ - `pg_stat_statements`: `permsStore.ListUserPermissions` at 24,026 calls,
89
+ 27,635.6s total, 1,150ms mean.
90
+ - Per-client capture throughput: 23 users/sec solo → 2–4 users/sec at 4-way
91
+ concurrency.
92
+ - Aggregate throughput: 8–16 users/sec at 4-way — **below the 23 users/sec a
93
+ single client achieves alone** (negative scaling).
94
+ - ALB (CloudWatch): no 5xx, no rejected connections — the edge and frontend
95
+ are not the bottleneck.
96
+ - Collateral failure: the fifth client's queries exceeded the 60s read timeout
97
+ under this load; 5 retry attempts exhausted; its run failed with exit 1.
98
+
99
+ Implications for the engineering request:
100
+
101
+ - A single per-user `permissionsInfo.repositories(source: API)` read costs
102
+ roughly 0.3–0.4s of Postgres CPU at this state size (1,150ms mean execution
103
+ under contention), so one operator at modest parallelism can saturate the
104
+ database by itself, and two concurrent operators degrade each other below
105
+ single-operator throughput.
106
+ - Timeout/retry behavior amplifies the problem: once statements exceed the
107
+ client read timeout, retries re-run the same expensive queries, adding load
108
+ exactly when the database is saturated.
109
+ - A bulk read API (one query returning explicit grants for many users or for
110
+ whole repos) would replace ~10,000 × ~1s statements per capture with a
111
+ single scan, and would also make concurrent operators viable.
112
+
113
+ ## Sourcegraph codepath findings
234
114
 
235
115
  [Deep Search findings](https://sourcegraph.sourcegraph.com/deepsearch/52a24164-1eb3-4db1-a92d-e320ef1c7557)
236
116
  from `github.com/sourcegraph/sourcegraph`:
@@ -254,8 +134,14 @@ from `github.com/sourcegraph/sourcegraph`:
254
134
  permissions, but the read path uses per-user connection queries and repo
255
135
  resolver fanout.
256
136
 
257
- Request a bulk read API for explicit permissions. GraphQL semantics make this
258
- a query, not a mutation:
137
+ ## Proposed bulk read API
138
+
139
+ `src-auth-perms-sync` needs to snapshot explicit API permissions for many
140
+ users. Today it calls `User.permissionsInfo.repositories(source: API)` with
141
+ GraphQL aliases. This is correct, but expensive at scale.
142
+
143
+ Request a bulk read API for explicit permissions. GraphQL semantics make this a
144
+ query, not a mutation:
259
145
 
260
146
  ```graphql
261
147
  type ExplicitRepositoryPermission {
@@ -298,10 +184,47 @@ Important requirements:
298
184
  Expected benefit: replace hundreds or thousands of per-repo resolver SQL spans
299
185
  per request with one indexed `user_repo_permissions` join per user batch.
300
186
 
301
- The stress profile also needs attention on the write path. A purpose-built
302
- bulk overwrite API that accepts many repo/user edges at once, streams or stages
303
- the input server-side, and avoids repeated per-repo permission reconciliation
304
- would make worst-case full syncs much safer.
187
+ ## Proposed presence/filter API
188
+
189
+ The `get --users-without-explicit-perms` path also needs a cheaper presence
190
+ check. Today it has to ask
191
+ `User.permissionsInfo.repositories(source: API, first: 1)` for every candidate
192
+ user, in aliased batches. Recent test runs show the client can parallelize
193
+ those batches, but the Sourcegraph frontend / load balancer can still return
194
+ 502/503s under that resolver load. Add one or both direct APIs:
195
+
196
+ ```graphql
197
+ type ExplicitRepositoryPermissionPresence {
198
+ userID: ID!
199
+ hasExplicitRepositoryPermissions: Boolean!
200
+ }
201
+
202
+ extend type Query {
203
+ explicitRepositoryPermissionPresenceForUsers(
204
+ userIDs: [ID!]!
205
+ source: PermissionSource = API
206
+ ): [ExplicitRepositoryPermissionPresence!]!
207
+
208
+ usersWithoutExplicitRepositoryPermissions(
209
+ createdAt: DateTimeFilter
210
+ source: PermissionSource = API
211
+ first: Int
212
+ after: String
213
+ ): UserConnection!
214
+ }
215
+ ```
216
+
217
+ Expected benefit: `src-auth-perms-sync get --users-without-explicit-perms` can
218
+ either check explicit-permission presence for candidate users in one indexed
219
+ batch query, or ask Sourcegraph for the filtered user set directly instead of
220
+ probing every user through the expensive permissions connection resolver.
221
+
222
+ ## Bulk overwrite follow-up
223
+
224
+ The stress profile also needs attention on the write path. A purpose-built bulk
225
+ overwrite API that accepts many repo/user edges at once, streams or stages the
226
+ input server-side, and avoids repeated per-repo permission reconciliation would
227
+ make worst-case full syncs much safer.
305
228
 
306
229
  ## Copy/paste request
307
230
 
@@ -324,7 +247,10 @@ Request: add a bulk explicit-permissions read API that accepts many user IDs and
324
247
  returns compact permission edges (`userID`, `repositoryID`, `repositoryName`,
325
248
  `updatedAt`) for `source: API`, without resolving full `Repository` GraphQL
326
249
  objects. A single indexed query over `user_repo_permissions` joined to `repo`
327
- should be enough for each user batch.
250
+ should be enough for each user batch. Also add a cheaper presence/filter path
251
+ for `get --users-without-explicit-perms`: either `userID -> has explicit API
252
+ repo permissions` for many users, or a direct query for users without explicit
253
+ API repo permissions, optionally filtered by `createdAt`.
328
254
 
329
255
  Acceptance criteria:
330
256
 
@@ -336,6 +262,9 @@ Acceptance criteria:
336
262
  latency visible.
337
263
  - `src-auth-perms-sync` can replace its aliased
338
264
  `User.permissionsInfo.repositories(source: API)` calls with this API.
265
+ - `src-auth-perms-sync get --users-without-explicit-perms` can stop probing
266
+ every candidate user through `User.permissionsInfo.repositories(source: API,
267
+ first: 1)`.
339
268
  - Follow-up: evaluate a bulk overwrite API for large full-set applies. The
340
269
  stress run planned roughly 10 million grants and observed
341
270
  `permsStore.upsertUserRepoPermissions-range1` averaging about 2.5s per call.
@@ -19,10 +19,6 @@ run git diff --cached --check
19
19
  run git diff --cached --stat
20
20
  run git diff --stat
21
21
 
22
- run uv run ruff check .
23
- run uv run ruff format . --check
24
- run uv run pyright
25
- run uv run python -m unittest discover -s tests
26
- run uv run src-auth-perms-sync --help
22
+ run uv run tests/run.py --local
27
23
 
28
24
  printf '\nPre-commit quality checks passed.\n'
@@ -551,7 +551,7 @@ def list_external_services(client: src.SourcegraphClient) -> list[ExternalServic
551
551
  )
552
552
  )
553
553
  if not services:
554
- raise SystemExit("No external services found on the Sourcegraph instance")
554
+ raise SystemExit("No code hosts found on the Sourcegraph instance")
555
555
  return services
556
556
 
557
557