Skip to content

Preserve and restore pending explicit permissions#29

Merged
marcleblanc2 merged 2 commits into
mainfrom
feat/preserve-pending-permissions
Jun 12, 2026
Merged

Preserve and restore pending explicit permissions#29
marcleblanc2 merged 2 commits into
mainfrom
feat/preserve-pending-permissions

Conversation

@marcleblanc2

Copy link
Copy Markdown
Collaborator

Problem

setRepositoryPermissionsForUsers replaces a repo's whole explicit list — real grants AND pending rows (bindIDs that never resolved to a user, i.e. grant-before-first-login). Every overwrite this tool sent silently deleted pending grants on the touched repos, and snapshots only recorded a flat bindID list, so a restore could never recreate them: silent, irreversible data loss.

Server-side mechanics confirmed in sourcegraph/sourcegraph: unresolved bindIDs in the mutation become pending_repo_permissions rows in the same transaction (scoped to service_type='sourcegraph', so code-host-synced pending permissions are unreachable and unaffected), and authorizedUserRepositories falls back to the pending store for bindIDs with no matching user — the only API exposing which repos a pending bindID has.

Change

The script now neither creates nor loses pending explicit permissions:

  • Snapshots (schema v5 → v6) capture pending_users: bindID → pending repos, via the authorizedUserRepositories late-binding fallback. Capture re-checks the pending list and drops bindIDs that became real users mid-run; any lookup failure aborts the capture rather than writing a snapshot with missing grants.
  • set --full resends each repo's current pending bindIDs in overwrite payloads (from the before-snapshot, or fetched live with --no-backup).
  • restore replays the target snapshot's pending grants — including repos that had only pending grants — and wipes pending grants the snapshot does not contain.
  • Snapshot diffs report pending bindIDs added/removed/changed; post-apply validation gains a pending-preservation gate; the restore residual check catches pending drift.
  • Additive paths (set --users, user-scoped restore) use per-user mutations that never touch pending rows — unchanged.

Testing

  • Fixture fake mirrors the real resolver (unknown bindIDs become per-repo pending rows); new cases full-overwrite-preserves-pending and restore-restores-pending, plus a new randomized invariant: "set --full neither creates nor loses pending grants".
  • Live coverage automated end-to-end: the live fixture runner seeds per-repo pendingBindIDs from fixture state, independently reads them back, asserts they survive the apply, and restores them away. full-overwrite-unions seeds one pending bindID on a mapped repo (survives its overwrite) and one on an unmapped canary (stays untouched).
  • tests/setup.py deletes leftover synthetic pending bindIDs (perms-sync-test-pending-* prefix, from interrupted runs) with --apply, verified by read-back; pending bindIDs of unknown origin are still reported and never deleted.
  • Local suite 102/102; full live suite against the test instance 104/104 with clean end-of-run hygiene. Also verified manually against the live instance: seed → get captures it → set --full --apply preserves it → manual wipe → restore --apply recreates it exactly ("VALIDATION OK: post-restore state matches the snapshot exactly").

Caveats

  • Snapshot schema bumped to v6: restore refuses before.json files written by older releases (strict version check, consistent with previous bumps).
  • The Bitbucket project bulk-permissions worker writes pending rows into the same sourcegraph namespace; this tool preserves those too (indistinguishable from explicit-API pending grants).

marcleblanc2 and others added 2 commits June 12, 2026 01:52
Sourcegraph's setRepositoryPermissionsForUsers replaces a repo's whole
explicit list, including pending rows (bindIDs that never resolved to a
user), so every overwrite silently deleted pending grants the CLI could
never recreate. Make the script neither create nor lose them:

- Snapshots (schema v6) capture pending_users: bindID -> pending repos,
  via the authorizedUserRepositories late-binding fallback
- set --full resends each repo's current pending bindIDs in overwrite
  payloads (from the before-snapshot, or fetched live with --no-backup)
- restore replays the target snapshot's pending grants and wipes pending
  grants the snapshot does not contain
- Snapshot diffs report pending bindIDs added/removed/changed; post-apply
  validation checks pending state was preserved exactly
- Fixture fake mirrors the real resolver (unknown bindIDs become per-repo
  pending rows); new fixture cases and a randomized invariant cover
  preservation and restore

Amp-Thread-ID: https://ampcode.com/threads/T-019eba2a-7d12-71b8-a491-ab2e78476f0b
Co-authored-by: Amp <amp@ampcode.com>
The live fixture runner now carries per-repo pendingBindIDs from fixture
state through the whole cycle: seeded with the before-state (unknown
bindIDs become pending rows server-side), independently read back via the
authorizedUserRepositories late-binding fallback, asserted to survive the
apply exactly as the fixture says, and restored away with the original
state. full-overwrite-unions seeds one pending bindID on a mapped repo
(must survive its overwrite) and one on an unmapped canary (must stay
untouched).

Seeded bindIDs use the perms-sync-test-pending- prefix: tests/setup.py
classifies leftovers from interrupted runs as synthetic and deletes
exactly those rows with --apply (verified by GraphQL read-back), while
still reporting and never deleting pending bindIDs of unknown origin.
The live hygiene check points synthetic leftovers at setup --apply.

Amp-Thread-ID: https://ampcode.com/threads/T-019eba2a-7d12-71b8-a491-ab2e78476f0b
Co-authored-by: Amp <amp@ampcode.com>
@marcleblanc2 marcleblanc2 force-pushed the feat/preserve-pending-permissions branch from 182baf4 to 5962afa Compare June 12, 2026 07:56
@marcleblanc2 marcleblanc2 merged commit deba21e into main Jun 12, 2026
6 checks passed
@marcleblanc2 marcleblanc2 deleted the feat/preserve-pending-permissions branch June 12, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant