.github/README.md
Adam Moussa 70439c3b21 Add multi-team approval gate reusable workflow
CODEOWNERS cannot require approval from two distinct teams on the same
files (multiple owners on a line is any-one/OR; separate lines are
last-match-wins). shoc-backend and shoc-frontend-new need every PR to be
approved by BOTH the external dev team and the internal-dev senior group,
so a custom gate is the only way to express that AND.

callable-required-review.yaml passes only when every team named in
all-of-teams has at least one approving review. Team membership is an org
read the default GITHUB_TOKEN cannot do, so the caller supplies a GitHub
App token (Members: read + Pull requests: read); no write scopes are
needed because the job's own pass/fail is the required status check. It
re-evaluates on pull_request and pull_request_review, and ignores author
self-reviews and non-decisive COMMENTED reviews.

Actions are SHA-pinned (Dependabot github-actions ecosystem already
covers this repo). The all-of-teams input is read via env, never
interpolated into the script body, to avoid Actions script injection.
2026-06-18 16:08:33 -04:00

263 lines
11 KiB
Markdown

# .github
Organization-level GitHub configuration for Sea Haven Industries.
## What's in here
### Reusable Workflows
**`.github/workflows/ci-python-sam.yaml`** — Reusable CI workflow for Python / SAM repos. Runs `ruff check` + `ruff format --check`, optional `pytest`, and optional `sam validate --lint`. Also usable for Python CDK repos by disabling SAM validate.
**`.github/workflows/ci-typescript-cdk.yaml`** — Reusable CI workflow for TypeScript / CDK repos. Runs `npm ci` + optional `tsc --noEmit`, optional ESLint, optional Jest, and optional `cdk synth`. Also supports Node.js SAM repos via an optional `sam validate` step.
**`.github/workflows/cd-sam.yaml`** — Reusable CD workflow for SAM repos. Runs `sam build` + `sam deploy` with OIDC credentials and a CloudFormation execution role. Triggers via `workflow_call` from per-repo `deploy.yaml` on push to `main`.
**`.github/workflows/cd-cdk.yaml`** — Reusable CD workflow for CDK repos (TypeScript and Python). Runs `cdk deploy --all` with OIDC credentials. Supports optional Python setup for Python CDK repos and QEMU emulation for cross-platform Docker builds.
**`.github/workflows/callable-required-review.yaml`** — Reusable PR review gate that requires an approving review from **every** team named in its `all-of-teams` input (a compound AND that CODEOWNERS cannot express). Wired as a required status check, it blocks merge until each listed team has signed off. Resolves team membership with a caller-supplied GitHub App token (`app_id` / `app_private_key`; App needs Organization `Members: read` + repository `Pull requests: read`). Re-evaluates on `pull_request` and `pull_request_review`. Planned first use: `shoc-backend` / `shoc-frontend-new` with `all-of-teams: "luby,internal-dev"`. The required-status-check context for a caller job id `required-review` is `required-review / required-review`.
**`.github/workflows/compliance-audit.yaml`** — Scheduled weekly audit (Mondays 10am ET) that checks all org repos for compliance with Sea Haven conventions. Creates GitHub issues on repos with violations. Can also be triggered manually via `workflow_dispatch`. Uses the `claude-code-ci` GitHub App + `ANTHROPIC_API_KEY` (see Setup).
### PR Reviews
PR reviews are handled by the **official Claude Code GitHub App** (installed org-wide, enabled as a required check in the org ruleset) — there is **no review workflow in this repo**. The earlier custom `claude-code-review.yaml` reusable workflow and its per-repo wrapper were retired on 2026-05-13 when the App took over.
### Scripts
**`scripts/rollout-review-workflow.sh`** — **Legacy / superseded.** One-time script that pushed the old PR-review wrapper workflow to all org repos. Obsolete since reviews moved to the official Claude Code App (2026-05-13); retained only for historical reference.
### AWS deploy roles & IAM (`oidc-deploy-roles.yaml`)
**`oidc-deploy-roles.yaml`** is a **bootstrap CloudFormation stack** (`github-oidc-deploy-roles`, us-east-1, account 328440206208) that owns the IAM the CI/CD workflows assume. It contains:
- The GitHub Actions **OIDC provider** (conditional — already exists in the account).
- One **OIDC deploy role per repo** (`githubdeploy-<repo>`), assumed by that repo's `deploy.yaml` via OIDC and passed in as `AWS_DEPLOY_ROLE_ARN`. CDK repos use these to assume the `cdk-hnb659fds-*` bootstrap roles; SAM repos use these to run `sam deploy`.
- The shared **SAM CloudFormation execution role** `github-cfn-execution-role` (`SamCfnExecutionRole`) — passed as `cfn-role-arn` by every SAM `deploy.yaml` (see §5). CloudFormation assumes it to provision the SAM stacks' resources.
- The **`seahaven-lambda-execution-boundary`** managed policy.
> ⚠️ **This stack has no CD pipeline — it is deployed manually.** (It defines the very roles the pipelines use, so it can't deploy itself.)
```bash
# Review IAM changes FIRST (IAM changes also require the cross-family review per the handbook):
aws cloudformation deploy \
--region us-east-1 \
--stack-name github-oidc-deploy-roles \
--template-file oidc-deploy-roles.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--s3-bucket cdk-hnb659fds-assets-328440206208-us-east-1 \
--no-execute-changeset
# inspect the printed change-set, then drop --no-execute-changeset to apply.
```
`--s3-bucket` is **required** — the template is larger than the 51,200-byte inline limit.
#### `github-cfn-execution-role` is scoped (no `*FullAccess`)
The execution role carries **no blanket `*FullAccess`/`IAMFullAccess`** — only per-service inline policies. Its `iam:CreateRole` / `iam:AttachRolePolicy` / `iam:PutRolePolicy` are conditioned on `iam:PermissionsBoundary == seahaven-lambda-execution-boundary`, so it can only create roles that carry the boundary (it cannot mint an unconstrained admin role). **Adding a new AWS service to a SAM stack means adding that service's provisioning actions to this role**, or the deploy fails.
#### `seahaven-lambda-execution-boundary` is the Lambda runtime ceiling
Every SAM-created Lambda execution role gets this boundary attached — SAM stacks set it on `Globals.Function`:
```yaml
Globals:
Function:
PermissionsBoundary: arn:aws:iam::328440206208:policy/seahaven-lambda-execution-boundary
```
A function's effective permissions are the **intersection** of its own role policy and this boundary. **A new runtime permission must also be added to the boundary, or it is silently denied at runtime** (the deploy still succeeds — the failure only shows when the function runs). SAM does **not** support a custom `Path` on auto-generated function roles, so the boundary *condition* (not a role path) is the escalation guard.
#### Order of operations when changing the exec role or boundary
1. Deploy the boundary change first.
2. Redeploy the SAM stacks so their roles pick it up (while the exec role still permits it).
3. *Then* tighten the exec role.
Wrong order breaks every SAM deploy. (History: INFRA-103 established the boundary, INFRA-97 scoped the role.) CDK repos are unaffected — they deploy via `cdk-hnb659fds-*` roles, not this execution role.
## Setup
### 1. Create a GitHub App
1. Go to **Organization Settings > Developer settings > GitHub Apps > New GitHub App**
2. Name it `claude-code-ci` (or similar)
3. Set Homepage URL to your org URL
4. Disable Webhook (uncheck "Active")
5. Set these **Repository permissions:**
- **Contents:** Read and write
- **Issues:** Read and write
- **Metadata:** Read-only
- **Pull requests:** Read and write
6. Set **Where can this app be installed?** to "Only on this account"
7. Click **Create GitHub App**
8. Note the **App ID** from the app's settings page
9. Under **Private keys**, click **Generate a private key** — save the `.pem` file
### 2. Install the App
1. From the app's settings page, click **Install App**
2. Select `Sea-Haven-Industries`
3. Choose **All repositories**
### 3. Add org-level secrets
Go to **Organization Settings > Secrets and variables > Actions** and add:
| Secret | Value |
|--------|-------|
| `ANTHROPIC_API_KEY` | Your Claude API key |
| `CLAUDE_CI_APP_ID` | The App ID from step 1 |
| `CLAUDE_CI_APP_PRIVATE_KEY` | The full contents of the `.pem` file from step 1 |
### 4. Add CI to a repo
Create `.github/workflows/ci.yaml` in the target repo. Examples:
**Python SAM repo** (e.g., afterhours-shift-manager, expense-approval-bot):
```yaml
name: CI
on:
pull_request:
branches: [main]
jobs:
ci:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-python-sam.yaml@main
```
**TypeScript CDK repo** (e.g., seahaven-door-unlock-api, seahaven-slack-bot):
```yaml
name: CI
on:
pull_request:
branches: [main]
jobs:
ci:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main
```
**Node.js SAM repo** (e.g., payments-dashboard):
```yaml
name: CI
on:
pull_request:
branches: [main]
jobs:
ci:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main
with:
run-typecheck: false
run-cdk-synth: false
run-sam-validate: true
```
**Mixed stack** (e.g., exec-aide — TypeScript CDK + Python Lambdas):
```yaml
name: CI
on:
pull_request:
branches: [main]
jobs:
python:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-python-sam.yaml@main
with:
source-dirs: "src"
run-sam-validate: false
typescript:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main
```
### 5. Add CD to a repo
Create `.github/workflows/deploy.yaml` in the target repo. Requires `AWS_DEPLOY_ROLE_ARN` repo secret.
**SAM repo** (e.g., afterhours-shift-manager):
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-sam.yaml@main
with:
stack-name: afterhours-shift-manager
cfn-role-arn: arn:aws:iam::328440206208:role/github-cfn-execution-role
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
```
> The `cfn-role-arn` (`github-cfn-execution-role`) is scoped and boundary-gated — adding a new AWS service or a new Lambda runtime permission to a SAM stack may require updating that role and/or `seahaven-lambda-execution-boundary` first. See [AWS deploy roles & IAM](#aws-deploy-roles--iam-oidc-deploy-rolesyaml).
**TypeScript CDK repo** (e.g., seahaven-door-unlock-api):
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@main
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
```
**Python CDK repo** (e.g., po-ingest):
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@main
with:
python-version: "3.12"
cdk-dir: cdk
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
```
**CDK repo with arm64 Docker builds** (e.g., exec-aide):
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@main
with:
enable-qemu: true
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
```
Enable optional steps as repos adopt them:
| Input | Default | Turn on when... |
|-------|---------|-----------------|
| `run-tests` | `false` | Repo has `pytest` tests or Jest tests |
| `run-lint` | `false` | Repo has an ESLint config |
| `run-typecheck` | `true` | Repo has `tsconfig.json` |
| `run-cdk-synth` | `true` | Repo is CDK-based |
| `run-sam-validate` | `true` (Python) / `false` (TS) | Repo has a SAM template |
### 6. PR reviews
PR reviews run via the **official Claude Code GitHub App** — install it on the org and enable it as a required check in the ruleset. No per-repo workflow or rollout is needed; the legacy `rollout-review-workflow.sh` is retained only for historical reference.