Skip to content

Repo & Pipeline Setup

Already completed. This was done during initial CampusCore setup. You only need this if you're re-creating the GitHub repo or changing the admin account.

How the GitHub repository's repo-level secrets and variables are configured. These are shared across all client environments.


Prerequisites


Set Repo-Level Secrets

# Admin role ARN - allows the deploy workflow to authenticate via OIDC
gh secret set ADMIN_AWS_ROLE_ARN --body "arn:aws:iam::774222656146:role/CampusCore-Admin-Role"

# Initial superuser - created automatically on first deploy for each client
# This is always the CampusCore team's admin account, shared across all environments
gh secret set DJANGO_SUPERUSER_EMAIL --body "admin@campus-core.ai"
gh secret set DJANGO_SUPERUSER_PASSWORD --body "<strong-password>"

Set Repo-Level Variables

# Route53 hosted zone ID for campuscoreai.com - shared across all environments
gh variable set CAMPUSCORE_HOSTED_ZONE_ID --body "Z03938461UVED2L50LENJ"

Main Branch Rules

main is guarded by one repository ruleset, main-required-checks (id 19521735). Three status checks are required, all of them jobs of .github/workflows/pr-validation.yml: Frontend (typecheck, lint, test, build), validate, and Migration graph and model state. Merging is a squash merge, one per pull request.

A pull request is checked against main as of the moment its run started, and GitHub does not re-run it when main moves. Two branches that each add a migration on the same parent are therefore both green, and the second one to land breaks migrate for everyone. Migration graph and model state catches that shape whenever the run is fresh, and always catches a model edit with no migration, but it cannot see a sibling that merged after the run. Closing that last gap needs a merge queue, which GitHub offers for private repositories only on Enterprise Cloud; this organization is on the Team plan, so a merge_queue rule on the ruleset is rejected with Invalid rule 'merge_queue'. Until that changes, re-run the check on a pull request that has been open across someone else's merge before merging it.

Repository admins bypass the ruleset (bypass_actors: repository role 5, always).

The workflow has no paths filter, and must not get one: a run GitHub skips on a path filter never reports its checks, so the pull request sits at Pending and only the admin bypass can merge it. That bites only a workflow whose jobs are required checks; infra-validation.yml and terraform-validate.yml filter on infrastructure/** at the workflow level and are fine, because nothing in them is required. Path filtering happens inside the workflow instead. A first job, changes, lists the pull request's files with dorny/paths-filter and each required job runs only when its area changed; a job skipped by its if: reports as a successful check, so a docs-only pull request goes green with nothing run. Frontend keys on campuscore_app/web/**, Migration graph and model state on the rest of campuscore_app/, and validate runs unless every changed file is prose (docs/, docs-site/, work/, releases/, .claude/, root-level *.md) or SPA source under web/src/ other than the generated types, because validate owns the contract-drift checks. Editing the workflow file runs all three. If changes itself fails, all three run: a detection failure can never turn into a green run that checked nothing.

What the required checks gate

Check Step What it gates
Frontend pnpm typecheck The SPA compiles under strict TypeScript
Frontend pnpm lint ESLint on the SPA
Frontend pnpm test SPA unit tests
Frontend pnpm test:browser SPA tests in real Chromium and WebKit, with touch and with a mouse
Frontend pnpm build The Vite bundle builds, and emits no sourcemaps (collectstatic fails on them)
validate uv lock --check uv.lock matches pyproject.toml; every image installs from the lock
validate Committed tailwind.css matches a clean rebuild Deployments serve the committed stylesheet verbatim
validate OpenAPI schema and generated TS types are in sync A Pydantic contract change that was never regenerated would 422 at runtime
validate Upload contract is in sync The SPA's upload allowlist matches the server's file-format registry
validate check_deploy_contract.py The typed Settings model reconciles with the ECS task definition and the Terraform outputs
validate ruff check Python lint
validate ty_ratchet.py No net-new type diagnostics beyond scripts/ty_baseline.json
validate pytest apps/ campus_core/ The Python test suite
Migration graph and model state check_migration_lock_timeout.py Every changed migration that runs DDL bounds its lock wait, so a deploy cannot hang behind a long-running reader
Migration graph and model state makemigrations --check --dry-run --noinput The migration graph has one leaf and the models match the migrations, judged on the merge with main as of the run

The Frontend job installs Chromium and WebKit before the browser tests, keeping the browser downloads in the Actions cache under the pinned playwright version. On a developer machine, scripts/gate.sh runs the same tests in the browser_tests compose service on the official Playwright image, which is about 2.5 GB and is pulled by the first gate run on that machine.

Re-applying the ruleset

The ruleset is edited through the API rather than the settings UI, so the change is reviewable and repeatable. A PUT replaces the whole ruleset, so build the body from the current one with jq instead of retyping the contexts and bypass actors.

gh api repos/CampusCoreAI/campuscore/rulesets/19521735 > /tmp/rs.json
jq '{name,target,enforcement,conditions,bypass_actors,rules}
    | .rules = [.rules[] | if .type=="required_status_checks"
        then .parameters.required_status_checks += [{"context":"<new check name>"}] else . end]' \
   /tmp/rs.json > /tmp/rs_new.json
gh api -X PUT repos/CampusCoreAI/campuscore/rulesets/19521735 --input /tmp/rs_new.json

Add a required context only once the check has reported at least once on a real pull request. A context the repository has never produced blocks every open pull request on a check with no job to satisfy it.


How the Pipeline Uses These

The deploy workflow (.github/workflows/deploy-aws.yml) uses a two-role authentication model:

1. GitHub Actions authenticates via OIDC → assumes CampusCore-Admin-Role (admin account)
2. Initializes Terraform backend (reads/writes state in campuscore-tfstate-{client})
3. Chains into client's CampusCore-Deploy-Role via sts:AssumeRole
4. Provisions infrastructure in client's account (ECS, RDS, S3, etc.)
5. Returns to admin role for Terraform state writes

Repo-Level vs Environment-Level

Level Name Type Purpose
Repo ADMIN_AWS_ROLE_ARN Secret Our admin account's IAM role (OIDC auth)
Repo DJANGO_SUPERUSER_EMAIL Secret Initial admin email (created on first deploy)
Repo DJANGO_SUPERUSER_PASSWORD Secret Initial admin password
Repo CAMPUSCORE_HOSTED_ZONE_ID Variable Route53 hosted zone ID for campuscoreai.com
Environment AWS_ROLE_ARN Variable Client's deploy role ARN (per-client)
Environment TF_STATE_BUCKET Variable S3 bucket for this client's Terraform state
Environment AWS_REGION Variable AWS region (defaults to us-east-1)
Environment All secrets Secrets DB creds, API keys, etc. (per-client)

Environment-level configuration is set up per-client in Step 02 - Pipeline Setup.


Verify the Pipeline Configuration

After setting the secret, verify it's accessible:

# List repo-level secrets (should show ADMIN_AWS_ROLE_ARN, DJANGO_SUPERUSER_EMAIL, DJANGO_SUPERUSER_PASSWORD)
gh secret list

# List repo-level variables (should show CAMPUSCORE_HOSTED_ZONE_ID)
gh variable list

See also: Admin Account Setup