# Importer Target Authorization and Single-Job Execution

## Status

Implemented local change for the GitHub-to-AGS importer. This replaces the
temporary all-or-nothing SiteAdmin gate with target-scoped authorization and
removes the public server-side GitHub credential execution path.

## Problem

The current import endpoints require `SiteAdmin` for every operation. That
does not model a repository migration correctly: GitHub and AGS are separate
systems, and a global AGS administrator should not be required merely because
an actor can read a GitHub repository and can manage the intended AGS target.

The current plan controller also creates two jobs per repository: a server
worker that receives a one-shot GitHub token and mirrors data, followed by a
separate staged-batch job. This duplicates work and gives one migration two
different recovery boundaries.

## Goals

- One durable `ImportJob` per source-to-target repository migration.
- Authorize the current AGS principal, whether it is a human or an agent.
- Keep source-provider credentials out of AGS APIs, database rows, logs, and
  server Git remotes.
- Support organization-wide batch migration through `migration.yaml`, durable
  checkpoints, idempotent shards, bounded concurrency, and per-repository
  reports.
- Avoid accidental writes to an existing target repository.

## Non-goals

- Treating a GitHub role as an AGS role.
- Allowing an ordinary AGS organization member to create repositories in an
  existing organization.
- Implicitly replacing a non-empty target repository.
- Replaying GitHub Actions runs or importing source secret values.

## Authorization Model

Two independent checks must pass.

### Source authorization

`ags-migrate` holds the GitHub token and preflights every selected resource
class. A GitHub Owner or Migrator role may authorize migration in GitHub, but
the token must still successfully read the repository, Git, Wiki, and every
selected metadata endpoint. The role is never sent to or interpreted by AGS.

### Target authorization

The AGS access token identifies the current principal. Human and agent
accounts use the same policy:

| Target mode | Required AGS access |
| --- | --- |
| `create` in the principal's personal namespace | principal owns that namespace |
| `create` in an existing organization | organization Owner |
| `existing_empty` | repository Admin and an empty target |
| `replace` | repository Admin plus explicit destructive confirmation |

AGS currently has organization Owners and repository Admin permissions, but
does not yet have a scoped `Migrator` role. A future organization-scoped
Migrator may be added without changing this contract; it must not be confused
with GitHub's Migrator role.

SiteAdmin remains an emergency operational override through the existing AGS
permission services. It is not a requirement for ordinary migrations.

For `existing_empty`, “empty” means no Git/Wiki history or repository data
that the importer could conflict with. Existing AGS collaborators, team grants,
and pending repository invitations are allowed as deliberate cutover
preconfiguration. The importer records source access as handoff data and never
overwrites those target-side permissions.

Every later job operation rechecks live target access. `initiator_id` is an
audit field, not a permanent authorization grant. Import source-user mapping
also requires the target organization Owner because it changes durable
organization historical identity attribution.

## API and Job Lifecycle

`POST /api/ext/v1/imports/github` creates a staged job only. Its public request
does not accept `source_token`, `source_token_ref`, or `start`.

1. The API authenticates the AGS human or agent.
2. It authorizes and reserves the exact target. For `create`, it creates the
   target repository through the normal AGS repository service and applies
   non-secret source settings captured locally: description, visibility,
   default branch, and Issue/Wiki enablement. For an existing target, it
   verifies repository Admin and target mode before reservation.
3. It creates exactly one `ImportJob`, bound to the resolved target repository.
4. `ags-migrate` performs source capture and Git/Wiki/LFS transfer outside AGS,
   uploads checksummed batches/assets, then requests apply and verify.
5. AGS applies target data in dependency phases, records URL mappings and
   reports, and permits resumable idempotent retries.

An active target reservation prevents two jobs from targeting the same
repository. It is released in the same transaction as successful completion,
terminal failure, or an authorized cancellation; normal target-empty checks
still prevent any unsafe retry or overwrite.

## Batch Execution

`migration.yaml` remains the organization-level manifest. It may list many
repositories and declares bounded repository concurrency, source API request
concurrency, data classes, target modes, and cutover policy.

The executor has one global source-provider rate limiter and retry policy for
429/secondary limits and retryable 5xx/network failures. It checkpoints
source cursors and content hashes. Snapshot shards are bounded by both bytes
and item count, checksummed, and uploaded idempotently.

Apply is dependency-aware rather than upload-order-dependent:

1. Git and Wiki repositories
2. labels and milestones
3. issues and pull requests
4. issue comments and pull-request reviews
5. review comments
6. pull-request Stacks
7. releases and release assets
8. branch protection, hooks, workflow metadata, and access-handoff records

Independent repositories may run concurrently. Within one repository, only
independent shards in a completed dependency phase may run concurrently.

Large release assets and Git LFS media use resumable binary transport with
checksums. LFS is a cutover blocker until media transfer and target checkout
are verified.

## Verification

Tests must prove the following:

- human and agent can create an allowed target and import it;
- an organization Owner can import into that organization;
- Repo Admin, but not Write/Maintain, can use `existing_empty`;
- unauthorized users cannot create, inspect, upload to, apply, or map a job;
- concurrent jobs cannot reserve one target;
- raw GitHub tokens never appear in API request models, job rows, logs, or Git
  remote configuration;
- one repository creates exactly one ImportJob;
- retry, source rate limit, interrupted upload, and resume preserve a correct
  final result;
- Git, Wiki, LFS, assets, PR history, reviews, comments, Stacks, identity
  mapping, and source/target counts are verified in a multi-repository run.
