mirror of
https://github.com/Sea-Haven-Industries/.github.git
synced 2026-09-30 07:03:11 +00:00
Merge branch 'main' into chore/cd-concurrency-groups
This commit is contained in:
commit
eed8c97e79
17 changed files with 190 additions and 283 deletions
35
.github/workflows/cd-cdk.yaml
vendored
35
.github/workflows/cd-cdk.yaml
vendored
|
|
@ -82,7 +82,18 @@ jobs:
|
||||||
|
|
||||||
- name: Publish .NET project
|
- name: Publish .NET project
|
||||||
if: ${{ inputs.dotnet-publish-project != '' }}
|
if: ${{ inputs.dotnet-publish-project != '' }}
|
||||||
run: dotnet publish ${{ inputs.dotnet-publish-project }} --configuration Release --runtime linux-arm64 --self-contained false --output $(dirname ${{ inputs.dotnet-publish-project }})/bin/Release/net8.0/linux-arm64/publish
|
# Env-var indirection (not inline expression interpolation) so shell
|
||||||
|
# metacharacters in the input are never parsed as script; the input is a
|
||||||
|
# single project path, so it stays quoted (no word-split) — an unquoted
|
||||||
|
# $(dirname ...) split the output path on whitespace.
|
||||||
|
env:
|
||||||
|
DOTNET_PUBLISH_PROJECT: ${{ inputs.dotnet-publish-project }}
|
||||||
|
run: |
|
||||||
|
dotnet publish "$DOTNET_PUBLISH_PROJECT" \
|
||||||
|
--configuration Release \
|
||||||
|
--runtime linux-arm64 \
|
||||||
|
--self-contained false \
|
||||||
|
--output "$(dirname "$DOTNET_PUBLISH_PROJECT")/bin/Release/net8.0/linux-arm64/publish"
|
||||||
|
|
||||||
- uses: actions/setup-node@v7
|
- uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
|
|
@ -102,10 +113,13 @@ jobs:
|
||||||
|
|
||||||
- name: Install Python dependencies
|
- name: Install Python dependencies
|
||||||
if: ${{ inputs.python-version != '' }}
|
if: ${{ inputs.python-version != '' }}
|
||||||
|
# NUL-delimited read loop rather than `for req in $(find ...)`: the
|
||||||
|
# command-substitution form word-splits and globs every path it finds.
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
for req in $(find . -name requirements.txt -not -path '*/node_modules/*'); do
|
while IFS= read -r -d '' req; do
|
||||||
pip install -r "$req"
|
pip install -r "$req"
|
||||||
done
|
done < <(find . -name requirements.txt -not -path '*/node_modules/*' -print0)
|
||||||
|
|
||||||
- uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6
|
- uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6
|
||||||
with:
|
with:
|
||||||
|
|
@ -143,11 +157,22 @@ jobs:
|
||||||
# $STACKS deliberately word-splits multiple selectors.
|
# $STACKS deliberately word-splits multiple selectors.
|
||||||
env:
|
env:
|
||||||
STACKS: ${{ inputs.stacks }}
|
STACKS: ${{ inputs.stacks }}
|
||||||
run: npx -y cdk deploy $STACKS --require-approval never
|
run: |
|
||||||
|
# $STACKS is deliberately unquoted: it carries one or more
|
||||||
|
# space-separated CDK stack selectors (default "--all") that must reach
|
||||||
|
# `cdk deploy` as separate argv entries. Quoting it would collapse them
|
||||||
|
# into one bogus selector and break every multi-stack deploy.
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
npx -y cdk deploy $STACKS --require-approval never
|
||||||
|
|
||||||
- name: Post-deploy script
|
- name: Post-deploy script
|
||||||
if: ${{ inputs.post-deploy-script != '' }}
|
if: ${{ inputs.post-deploy-script != '' }}
|
||||||
run: bash ${{ inputs.post-deploy-script }}
|
# Env-var indirection (not inline expression interpolation) so shell
|
||||||
|
# metacharacters in the input are never parsed as script; the input is a
|
||||||
|
# single script path, so $POST_DEPLOY_SCRIPT is quoted (no word-split).
|
||||||
|
env:
|
||||||
|
POST_DEPLOY_SCRIPT: ${{ inputs.post-deploy-script }}
|
||||||
|
run: bash "$POST_DEPLOY_SCRIPT"
|
||||||
|
|
||||||
- name: Post-deploy health check
|
- name: Post-deploy health check
|
||||||
if: ${{ inputs.stack-name != '' }}
|
if: ${{ inputs.stack-name != '' }}
|
||||||
|
|
|
||||||
15
.github/workflows/cd-sam.yaml
vendored
15
.github/workflows/cd-sam.yaml
vendored
|
|
@ -89,11 +89,22 @@ jobs:
|
||||||
run: sam build --template ${{ inputs.sam-template }}
|
run: sam build --template ${{ inputs.sam-template }}
|
||||||
|
|
||||||
- name: SAM deploy
|
- name: SAM deploy
|
||||||
|
# Env-var indirection (not inline expression interpolation) so shell
|
||||||
|
# metacharacters in the secret are never parsed as script; unquoted
|
||||||
|
# $PARAM_OVERRIDES deliberately word-splits multiple Key=Value pairs.
|
||||||
|
env:
|
||||||
|
PARAM_OVERRIDES: ${{ secrets.parameter-overrides }}
|
||||||
run: |
|
run: |
|
||||||
PARAMS=""
|
PARAMS=""
|
||||||
if [ -n "${{ secrets.parameter-overrides }}" ]; then
|
if [ -n "$PARAM_OVERRIDES" ]; then
|
||||||
PARAMS="--parameter-overrides ${{ secrets.parameter-overrides }}"
|
PARAMS="--parameter-overrides $PARAM_OVERRIDES"
|
||||||
fi
|
fi
|
||||||
|
# $PARAMS is deliberately unquoted: it is either empty (no overrides,
|
||||||
|
# so no flag at all) or "--parameter-overrides Key=Value [Key=Value…]",
|
||||||
|
# which must reach `sam deploy` as separate argv entries. Quoting it
|
||||||
|
# would pass one empty or one concatenated argument and break every
|
||||||
|
# parameterised deploy.
|
||||||
|
# shellcheck disable=SC2086
|
||||||
sam deploy \
|
sam deploy \
|
||||||
--stack-name ${{ inputs.stack-name }} \
|
--stack-name ${{ inputs.stack-name }} \
|
||||||
--template-file .aws-sam/build/template.yaml \
|
--template-file .aws-sam/build/template.yaml \
|
||||||
|
|
|
||||||
7
.github/workflows/ci-python-sam.yaml
vendored
7
.github/workflows/ci-python-sam.yaml
vendored
|
|
@ -72,13 +72,16 @@ jobs:
|
||||||
|
|
||||||
- name: Install Python dependencies
|
- name: Install Python dependencies
|
||||||
if: ${{ inputs.run-tests || inputs.run-cdk-synth }}
|
if: ${{ inputs.run-tests || inputs.run-cdk-synth }}
|
||||||
|
# NUL-delimited read loop rather than `for req in $(find ...)`: the
|
||||||
|
# command-substitution form word-splits and globs every path it finds.
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ inputs.run-tests }}" = "true" ]; then
|
if [ "${{ inputs.run-tests }}" = "true" ]; then
|
||||||
pip install pytest
|
pip install pytest
|
||||||
fi
|
fi
|
||||||
for req in $(find . -name requirements.txt -not -path './.aws-sam/*'); do
|
while IFS= read -r -d '' req; do
|
||||||
pip install -r "$req"
|
pip install -r "$req"
|
||||||
done
|
done < <(find . -name requirements.txt -not -path './.aws-sam/*' -print0)
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: ${{ inputs.run-tests }}
|
if: ${{ inputs.run-tests }}
|
||||||
|
|
|
||||||
22
.github/workflows/ci.yaml
vendored
22
.github/workflows/ci.yaml
vendored
|
|
@ -23,11 +23,19 @@ name: ci
|
||||||
# supply-chain surface auditable. Bump ACTIONLINT_VERSION + ACTIONLINT_SHA256
|
# supply-chain surface auditable. Bump ACTIONLINT_VERSION + ACTIONLINT_SHA256
|
||||||
# together (checksum from the release's *_checksums.txt).
|
# together (checksum from the release's *_checksums.txt).
|
||||||
#
|
#
|
||||||
# actionlint's shellcheck integration is disabled (`-shellcheck=`) for now: it
|
# actionlint's shellcheck integration is ON (it defaults to the `shellcheck` on
|
||||||
# reports 4 pre-existing findings in the deploy/CI run-steps (SC2044 find-in-for
|
# PATH; the ubuntu-latest runner image ships shellcheck 0.9.0, so nothing extra
|
||||||
# loops, SC2046/SC2086 quoting, one of which is intentional word-splitting in the
|
# is installed). Do NOT re-add `-shellcheck=` — the empty value silently turns
|
||||||
# SAM deploy step). Those deserve a separate, tested cleanup rather than being
|
# the whole shell-linting half of this gate back off.
|
||||||
# bundled into the gate that unblocks the repo. Re-enable shellcheck once fixed.
|
#
|
||||||
|
# Two run-steps carry a narrowly-scoped `# shellcheck disable=SC2086` on the
|
||||||
|
# single line above the command, because the unquoted expansion there is the
|
||||||
|
# point: `sam deploy … $PARAMS` (cd-sam.yaml) and `cdk deploy $STACKS`
|
||||||
|
# (cd-cdk.yaml) rely on word-splitting to turn one variable into several argv
|
||||||
|
# entries. Quoting them would collapse multiple parameter overrides or stack
|
||||||
|
# selectors into one argument and break those deploys. Every other finding was
|
||||||
|
# fixed in the shell rather than suppressed. Suppressions stay per-line and
|
||||||
|
# commented — never file-wide, and never by weakening this invocation.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
@ -56,5 +64,7 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Lint workflows
|
- name: Lint workflows
|
||||||
run: ./actionlint -color -shellcheck=
|
run: |
|
||||||
|
shellcheck --version
|
||||||
|
./actionlint -color
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
||||||
138
.github/workflows/compliance-audit.yaml
vendored
138
.github/workflows/compliance-audit.yaml
vendored
|
|
@ -1,138 +0,0 @@
|
||||||
# DEPRECATED (2026-06-10): The weekly org-wide compliance audit has been retired.
|
|
||||||
# The workflow is disabled in the Actions tab (state: disabled_manually) and the
|
|
||||||
# scheduled trigger has been removed so it cannot run automatically. Repo
|
|
||||||
# compliance is now handled via the Claude Code App on pull requests and the
|
|
||||||
# engineering handbook directly. Left in place (manual-dispatch only) for
|
|
||||||
# historical reference; safe to delete in a future cleanup.
|
|
||||||
name: Compliance Audit (DEPRECATED)
|
|
||||||
|
|
||||||
on:
|
|
||||||
# schedule removed on deprecation — no longer runs weekly.
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
get-repos:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
repos: ${{ steps.list.outputs.repos }}
|
|
||||||
steps:
|
|
||||||
- name: Generate GitHub App token
|
|
||||||
id: app-token
|
|
||||||
uses: actions/create-github-app-token@v3
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.CLAUDE_CI_APP_ID }}
|
|
||||||
private-key: ${{ secrets.CLAUDE_CI_APP_PRIVATE_KEY }}
|
|
||||||
owner: Sea-Haven-Industries
|
|
||||||
|
|
||||||
- name: List org repos
|
|
||||||
id: list
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
||||||
run: |
|
|
||||||
EXCLUDE="shoc-frontend-new shoc-backend"
|
|
||||||
repos=$(gh repo list Sea-Haven-Industries \
|
|
||||||
--no-archived \
|
|
||||||
--json name \
|
|
||||||
--jq "[.[].name | select(. as \$n | \"$EXCLUDE\" | split(\" \") | index(\$n) | not)] | @json" \
|
|
||||||
--limit 100)
|
|
||||||
echo "repos=$repos" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
audit:
|
|
||||||
needs: get-repos
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
max-parallel: 3
|
|
||||||
matrix:
|
|
||||||
repo: ${{ fromJson(needs.get-repos.outputs.repos) }}
|
|
||||||
steps:
|
|
||||||
- name: Generate GitHub App token
|
|
||||||
id: app-token
|
|
||||||
uses: actions/create-github-app-token@v3
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.CLAUDE_CI_APP_ID }}
|
|
||||||
private-key: ${{ secrets.CLAUDE_CI_APP_PRIVATE_KEY }}
|
|
||||||
owner: Sea-Haven-Industries
|
|
||||||
repositories: ${{ matrix.repo }},engineering-handbook
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
repository: Sea-Haven-Industries/${{ matrix.repo }}
|
|
||||||
token: ${{ steps.app-token.outputs.token }}
|
|
||||||
|
|
||||||
- name: Checkout engineering handbook
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
repository: Sea-Haven-Industries/engineering-handbook
|
|
||||||
token: ${{ steps.app-token.outputs.token }}
|
|
||||||
path: .engineering-handbook
|
|
||||||
|
|
||||||
- name: Run compliance audit
|
|
||||||
id: audit
|
|
||||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
|
||||||
with:
|
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
||||||
prompt: |
|
|
||||||
Audit this repository for Sea Haven Industries compliance.
|
|
||||||
|
|
||||||
The engineering handbook in `.engineering-handbook/` is the authoritative source for all conventions. Read every markdown file in that directory to understand the full set of standards, then audit this repo against them.
|
|
||||||
|
|
||||||
Focus on these categories:
|
|
||||||
- **Naming:** kebab-case for all resource names in IaC templates, stack name matches repo name
|
|
||||||
- **Secrets:** no secrets in Lambda env vars or SSM, secrets belong in Secrets Manager with `stack-name/secret-name` naming
|
|
||||||
- **Lambda defaults:** Python 3.12+ or Node 22.x, arm64, explicit 60-day log retention in IaC
|
|
||||||
- **CI/CD:** pipeline exists with CI on PR and CD on push to main, using reusable workflows from `.github` repo
|
|
||||||
- **Git/GitHub:** branch protection on main, PR-based workflow, repo has a description
|
|
||||||
- **SAM layout:** template.yaml at root, samconfig.toml gitignored with .example committed, src/ directory structure
|
|
||||||
- **Project hygiene:** README describes architecture, .gitignore covers .env/.aws-sam/__pycache__, CloudFormation outputs include ARNs and URLs
|
|
||||||
|
|
||||||
Return structured output with:
|
|
||||||
- `has_violations`: true only when one or more actual compliance violations are found.
|
|
||||||
- `report`: a concise markdown report with pass/fail per applicable item.
|
|
||||||
|
|
||||||
Only flag actual violations — skip items that don't apply to this repo (e.g., skip Lambda checks if no Lambdas exist).
|
|
||||||
Do not create or modify files, issues, pull requests, or comments.
|
|
||||||
claude_args: |
|
|
||||||
--json-schema '{"type":"object","properties":{"has_violations":{"type":"boolean","description":"True when one or more actual compliance violations are found."},"report":{"type":"string","description":"Concise markdown report with pass/fail per applicable compliance item."}},"required":["has_violations","report"],"additionalProperties":false}'
|
|
||||||
|
|
||||||
- name: Create issue if violations found
|
|
||||||
if: ${{ fromJSON(steps.audit.outputs.structured_output).has_violations == true }}
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
||||||
AUDIT_RESULT: ${{ steps.audit.outputs.structured_output }}
|
|
||||||
run: |
|
|
||||||
gh label create compliance \
|
|
||||||
--repo "Sea-Haven-Industries/${{ matrix.repo }}" \
|
|
||||||
--description "Weekly compliance audit" \
|
|
||||||
--color "D93F0B" 2>/dev/null || true
|
|
||||||
existing=$(gh issue list \
|
|
||||||
--repo "Sea-Haven-Industries/${{ matrix.repo }}" \
|
|
||||||
--label "compliance" \
|
|
||||||
--state open \
|
|
||||||
--json number \
|
|
||||||
--jq 'length')
|
|
||||||
if [ "$existing" -eq 0 ]; then
|
|
||||||
report=$(jq -r '.report' <<< "$AUDIT_RESULT")
|
|
||||||
body_file=$(mktemp)
|
|
||||||
{
|
|
||||||
echo "The weekly compliance audit found violations in this repo."
|
|
||||||
echo
|
|
||||||
echo "## Audit report"
|
|
||||||
echo
|
|
||||||
printf '%s\n' "$report"
|
|
||||||
echo
|
|
||||||
echo "Check the [latest audit run](https://github.com/Sea-Haven-Industries/.github/actions/workflows/compliance-audit.yaml) for details."
|
|
||||||
} > "$body_file"
|
|
||||||
gh issue create \
|
|
||||||
--repo "Sea-Haven-Industries/${{ matrix.repo }}" \
|
|
||||||
--title "Compliance audit: violations found" \
|
|
||||||
--body-file "$body_file" \
|
|
||||||
--label "compliance"
|
|
||||||
fi
|
|
||||||
56
README.md
56
README.md
|
|
@ -30,9 +30,7 @@ Organization-level GitHub configuration for Sea Haven Industries.
|
||||||
|
|
||||||
**`.github/workflows/callable-dependency-review.yaml`** — Dependency review on PRs, failing on high severity. Requires Dependency Graph.
|
**`.github/workflows/callable-dependency-review.yaml`** — Dependency review on PRs, failing on high severity. Requires Dependency Graph.
|
||||||
|
|
||||||
**`.github/workflows/ci.yaml`** — Self-CI for this repo: actionlint (checksum-verified install) over all workflow files, emitting the required `ci / ci` status context.
|
**`.github/workflows/ci.yaml`** — Self-CI for this repo: actionlint (checksum-verified install) over all workflow files, emitting the required `ci / ci` status context. Its shellcheck integration is enabled, so `run:` bodies are shell-linted too; the two deploy steps that rely on intentional word-splitting (`sam deploy … $PARAMS`, `cdk deploy $STACKS`) carry a per-line, commented `# shellcheck disable=SC2086` rather than being quoted or globally exempted.
|
||||||
|
|
||||||
**`.github/workflows/compliance-audit.yaml`** — **DEPRECATED (2026-06-10).** The weekly scheduled org-wide audit has been retired: the schedule was removed and the workflow is disabled in the Actions tab (manual `workflow_dispatch` only, kept for historical reference). Repo compliance is now handled by the Claude Code App on pull requests and the engineering handbook directly. Safe to delete in a future cleanup.
|
|
||||||
|
|
||||||
### Workflow templates (`workflow-templates/`)
|
### Workflow templates (`workflow-templates/`)
|
||||||
|
|
||||||
|
|
@ -46,21 +44,13 @@ Third-party action refs across the org follow a tiered policy:
|
||||||
- **Common first-party actions** (`actions/checkout`, `actions/dependency-review-action`, `actions/github-script`) are pinned to a **major tag** (`@v7`, `@v5`, …) and kept current by Dependabot version updates gated by CI.
|
- **Common first-party actions** (`actions/checkout`, `actions/dependency-review-action`, `actions/github-script`) are pinned to a **major tag** (`@v7`, `@v5`, …) and kept current by Dependabot version updates gated by CI.
|
||||||
- **Org reusable workflows** are referenced at **`@main`** (`uses: Sea-Haven-Industries/.github/.github/workflows/…@main`). This is deliberate: caller and callable share one trust domain, and pinning callers to a SHA would freeze every consumer against central fixes. Templates in `workflow-templates/` follow the same `@main` convention.
|
- **Org reusable workflows** are referenced at **`@main`** (`uses: Sea-Haven-Industries/.github/.github/workflows/…@main`). This is deliberate: caller and callable share one trust domain, and pinning callers to a SHA would freeze every consumer against central fixes. Templates in `workflow-templates/` follow the same `@main` convention.
|
||||||
|
|
||||||
### 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`)
|
### 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:
|
**`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).
|
- 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`.
|
- 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 shared **SAM CloudFormation execution role** `github-cfn-execution-role` (`SamCfnExecutionRole`) — passed as `cfn-role-arn` by every SAM `deploy.yaml` (see §3). CloudFormation assumes it to provision the SAM stacks' resources.
|
||||||
- The **`seahaven-lambda-execution-boundary`** managed policy.
|
- The **`seahaven-lambda-execution-boundary`** managed policy.
|
||||||
- The **`seahaven-cfn-exec-iam-management`** managed policy (`SamCfnIamManagementPolicy`), attached to `github-cfn-execution-role`. It holds that role's boundary-gated IAM statements plus the Deny backstops that keep the permissions boundary from being detached, rewritten, or applied to the deploy substrate's own roles. It lives in a managed policy rather than inline because the role's inline policies sit at 10,006 of IAM's hard 10,240-byte per-role limit; attached managed policies have a separate 6,144-byte budget.
|
- The **`seahaven-cfn-exec-iam-management`** managed policy (`SamCfnIamManagementPolicy`), attached to `github-cfn-execution-role`. It holds that role's boundary-gated IAM statements plus the Deny backstops that keep the permissions boundary from being detached, rewritten, or applied to the deploy substrate's own roles. It lives in a managed policy rather than inline because the role's inline policies sit at 10,006 of IAM's hard 10,240-byte per-role limit; attached managed policies have a separate 6,144-byte budget.
|
||||||
|
|
||||||
|
|
@ -121,39 +111,17 @@ Recovery in either case is an administrator action, not a pipeline retry: clear
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
### 1. Create a GitHub App
|
### 1. Org-level secrets
|
||||||
|
|
||||||
1. Go to **Organization Settings > Developer settings > GitHub Apps > New GitHub App**
|
Managed under **Organization Settings > Secrets and variables > Actions**. Each is set to **selected repositories** visibility — grant it to a repo before a workflow there can read it.
|
||||||
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
|
| Secret | Value | Consumed by |
|
||||||
|
|--------|-------|-------------|
|
||||||
|
| `ANTHROPIC_API_KEY` | Anthropic API key | `reviewer-eval.yml` in `open-swe` |
|
||||||
|
|
||||||
1. From the app's settings page, click **Install App**
|
The CI and CD workflows below need no org secret — CD authenticates to AWS via OIDC using the per-repo `AWS_DEPLOY_ROLE_ARN` secret (see §3).
|
||||||
2. Select `Sea-Haven-Industries`
|
|
||||||
3. Choose **All repositories**
|
|
||||||
|
|
||||||
### 3. Add org-level secrets
|
### 2. Add CI to a repo
|
||||||
|
|
||||||
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:
|
Create `.github/workflows/ci.yaml` in the target repo. Examples:
|
||||||
|
|
||||||
|
|
@ -218,7 +186,7 @@ jobs:
|
||||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main
|
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Add CD to a repo
|
### 3. Add CD to a repo
|
||||||
|
|
||||||
Create `.github/workflows/deploy.yaml` in the target repo. Requires `AWS_DEPLOY_ROLE_ARN` repo secret.
|
Create `.github/workflows/deploy.yaml` in the target repo. Requires `AWS_DEPLOY_ROLE_ARN` repo secret.
|
||||||
|
|
||||||
|
|
@ -323,7 +291,3 @@ Enable optional steps as repos adopt them:
|
||||||
| `run-typecheck` | `true` | Repo has `tsconfig.json` |
|
| `run-typecheck` | `true` | Repo has `tsconfig.json` |
|
||||||
| `run-cdk-synth` | `true` | Repo is CDK-based |
|
| `run-cdk-synth` | `true` | Repo is CDK-based |
|
||||||
| `run-sam-validate` | `true` (Python) / `false` (TS) | Repo has a SAM template |
|
| `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.
|
|
||||||
|
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ORG="Sea-Haven-Industries"
|
|
||||||
BRANCH="add-claude-review"
|
|
||||||
WORKFLOW_PATH=".github/workflows/claude-review.yaml"
|
|
||||||
COMMIT_MSG="Add Claude Code review workflow"
|
|
||||||
|
|
||||||
WORKFLOW_CONTENT='name: Claude Code Review
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
review:
|
|
||||||
uses: Sea-Haven-Industries/.github/.github/workflows/claude-code-review.yaml@main
|
|
||||||
secrets:
|
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
||||||
'
|
|
||||||
|
|
||||||
SKIP_REPOS=(".github" "shoc-frontend-new" "shoc-backend")
|
|
||||||
|
|
||||||
should_skip() {
|
|
||||||
local repo="$1"
|
|
||||||
for skip in "${SKIP_REPOS[@]}"; do
|
|
||||||
if [[ "$repo" == "$skip" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Fetching non-archived repos from $ORG..."
|
|
||||||
repos=$(gh repo list "$ORG" --no-archived --json name --jq '.[].name' --limit 100)
|
|
||||||
|
|
||||||
for repo in $repos; do
|
|
||||||
if should_skip "$repo"; then
|
|
||||||
echo "SKIP $repo (in skip list)"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- $repo ---"
|
|
||||||
|
|
||||||
if gh api "repos/$ORG/$repo/contents/$WORKFLOW_PATH" --jq '.sha' > /dev/null 2>&1; then
|
|
||||||
echo "SKIP $repo (workflow already exists)"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
default_branch=$(gh api "repos/$ORG/$repo" --jq '.default_branch')
|
|
||||||
|
|
||||||
encoded=$(echo -n "$WORKFLOW_CONTENT" | base64)
|
|
||||||
|
|
||||||
gh api "repos/$ORG/$repo/git/refs" \
|
|
||||||
-f "ref=refs/heads/$BRANCH" \
|
|
||||||
-f "sha=$(gh api "repos/$ORG/$repo/git/ref/heads/$default_branch" --jq '.object.sha')" \
|
|
||||||
2>/dev/null || true
|
|
||||||
|
|
||||||
gh api "repos/$ORG/$repo/contents/$WORKFLOW_PATH" \
|
|
||||||
-X PUT \
|
|
||||||
-f "message=$COMMIT_MSG" \
|
|
||||||
-f "content=$encoded" \
|
|
||||||
-f "branch=$BRANCH" \
|
|
||||||
> /dev/null
|
|
||||||
|
|
||||||
pr_url=$(gh pr create \
|
|
||||||
--repo "$ORG/$repo" \
|
|
||||||
--base "$default_branch" \
|
|
||||||
--head "$BRANCH" \
|
|
||||||
--title "$COMMIT_MSG" \
|
|
||||||
--body "Adds a thin workflow that delegates PR reviews to the central reusable workflow in \`Sea-Haven-Industries/.github\`. Requires the \`ANTHROPIC_API_KEY\` org secret to be set." \
|
|
||||||
2>/dev/null || echo "PR already exists")
|
|
||||||
|
|
||||||
echo "DONE $repo → $pr_url"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Rollout complete. Review and merge the PRs, then delete the feature branches."
|
|
||||||
7
workflow-templates/ci-dotnet.properties.json
Normal file
7
workflow-templates/ci-dotnet.properties.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "Sea Haven — CI (.NET)",
|
||||||
|
"description": "Runs dotnet restore, build (Release), and dotnet test for a solution or project via the org reusable workflow.",
|
||||||
|
"iconName": "octicon-checklist",
|
||||||
|
"categories": ["C#", "Continuous integration"],
|
||||||
|
"filePatterns": ["\\.csproj$", "\\.sln$"]
|
||||||
|
}
|
||||||
14
workflow-templates/ci-dotnet.yml
Normal file
14
workflow-templates/ci-dotnet.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
name: CI (.NET)
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
# Job id MUST stay `ci`: the reusable's job is also `ci`, so the check
|
||||||
|
# context resolves to the required `ci / ci`.
|
||||||
|
#
|
||||||
|
# Every input is optional. Common overrides: `solution` (defaults to *.sln
|
||||||
|
# in the working directory), `working-directory`, and `dotnet-version`
|
||||||
|
# (defaults to 8.0.x). This reusable has no `node-version` input.
|
||||||
|
uses: Sea-Haven-Industries/.github/.github/workflows/ci-dotnet.yaml@555d07c3a240689a81668026787eba089df4c975 # main
|
||||||
7
workflow-templates/ci-python-app.properties.json
Normal file
7
workflow-templates/ci-python-app.properties.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "Sea Haven — CI (Python / app)",
|
||||||
|
"description": "Runs ruff check, ruff format --check, a pytest collect-only import check, and an optional subproject suite via the org reusable workflow. For Python repos that do not deploy via SAM or CDK.",
|
||||||
|
"iconName": "octicon-checklist",
|
||||||
|
"categories": ["Python", "Continuous integration"],
|
||||||
|
"filePatterns": ["requirements.*\\.txt$", "pyproject\\.toml$"]
|
||||||
|
}
|
||||||
14
workflow-templates/ci-python-app.yml
Normal file
14
workflow-templates/ci-python-app.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
name: CI (Python / app)
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
# Job id MUST stay `ci`: the reusable's aggregator job is also `ci`, so the
|
||||||
|
# check context resolves to the required `ci / ci`.
|
||||||
|
#
|
||||||
|
# Every input is optional. Common overrides: `source-dirs` (ruff targets),
|
||||||
|
# `requirements` (non-default requirements file), `subproject-dir` (a
|
||||||
|
# self-contained suite that must run in its own working directory).
|
||||||
|
uses: Sea-Haven-Industries/.github/.github/workflows/ci-python-app.yaml@555d07c3a240689a81668026787eba089df4c975 # main
|
||||||
7
workflow-templates/ci-static.properties.json
Normal file
7
workflow-templates/ci-static.properties.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "Sea Haven — CI (Static Site)",
|
||||||
|
"description": "Validates static HTML/CSS/JS sites (S3 + CloudFront repos): htmlhint, JSON-LD parsing, sitemap.xml well-formedness, and internal link resolution via the org reusable workflow.",
|
||||||
|
"iconName": "octicon-checklist",
|
||||||
|
"categories": ["HTML", "Continuous integration"],
|
||||||
|
"filePatterns": ["index\\.html$"]
|
||||||
|
}
|
||||||
18
workflow-templates/ci-static.yml
Normal file
18
workflow-templates/ci-static.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
name: CI (Static Site)
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
# Job id MUST stay `ci`: the reusable's job is also `ci`, so the check
|
||||||
|
# context resolves to the required `ci / ci`.
|
||||||
|
uses: Sea-Haven-Industries/.github/.github/workflows/ci-static.yaml@555d07c3a240689a81668026787eba089df4c975 # main
|
||||||
|
with:
|
||||||
|
# Pinned explicitly (handbook): local dev is Node 24 / npm 11, which
|
||||||
|
# generates lockfileVersion 3. Being explicit avoids lockfile drift.
|
||||||
|
node-version: "24"
|
||||||
|
# Defaults to source mode — the checks run against the repo root. For a
|
||||||
|
# templated site (Eleventy, Astro), add `build-command` plus `check-dir`
|
||||||
|
# so the checks validate the BUILT output that actually ships; otherwise
|
||||||
|
# they pass vacuously against source templates that contain no HTML.
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "Sea Haven — CI (TypeScript / frontend)",
|
||||||
|
"description": "Runs the Sea Haven standards gate, format:check, lint, build, unit tests, and a Playwright browser smoke for bundled Vite/React/Vue apps via the org reusable workflow.",
|
||||||
|
"iconName": "octicon-checklist",
|
||||||
|
"categories": ["TypeScript", "JavaScript", "Continuous integration"],
|
||||||
|
"filePatterns": ["package\\.json$", "vite\\.config\\.[jt]s$", "playwright\\.config\\.[jt]s$"]
|
||||||
|
}
|
||||||
14
workflow-templates/ci-typescript-frontend.yml
Normal file
14
workflow-templates/ci-typescript-frontend.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
name: CI (TypeScript / frontend)
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
# Job id MUST stay `ci`: the reusable's job is also `ci`, so the check
|
||||||
|
# context resolves to the required `ci / ci`.
|
||||||
|
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-frontend.yaml@555d07c3a240689a81668026787eba089df4c975 # main
|
||||||
|
with:
|
||||||
|
# Pinned explicitly (handbook): local dev is Node 24 / npm 11, which
|
||||||
|
# generates lockfileVersion 3. Being explicit avoids lockfile drift.
|
||||||
|
node-version: "24"
|
||||||
7
workflow-templates/mobile-ios-deploy.properties.json
Normal file
7
workflow-templates/mobile-ios-deploy.properties.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "Sea Haven — Deploy (iOS / TestFlight)",
|
||||||
|
"description": "Builds the iOS app with Fastlane and uploads it to TestFlight on push to main, using the org reusable cd-mobile-ios workflow. Requires the AWS_DEPLOY_ROLE_ARN, MATCH_PASSWORD, ASC_KEY_ID, ASC_ISSUER_ID and ASC_KEY_CONTENT repo secrets.",
|
||||||
|
"iconName": "octicon-rocket",
|
||||||
|
"categories": ["Deployment", "Mobile", "JavaScript"],
|
||||||
|
"filePatterns": ["Gemfile$", "app\\.json$", "metro\\.config\\.[cm]?js$"]
|
||||||
|
}
|
||||||
21
workflow-templates/mobile-ios-deploy.yml
Normal file
21
workflow-templates/mobile-ios-deploy.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
name: Deploy (iOS / TestFlight)
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
uses: Sea-Haven-Industries/.github/.github/workflows/cd-mobile-ios.yaml@555d07c3a240689a81668026787eba089df4c975 # main
|
||||||
|
with:
|
||||||
|
# Pinned explicitly (handbook): local dev is Node 24 / npm 11, which
|
||||||
|
# generates lockfileVersion 3. Being explicit avoids lockfile drift.
|
||||||
|
node-version: "24"
|
||||||
|
secrets:
|
||||||
|
# All five are required. deploy-role-arn is the repo's OIDC role, used
|
||||||
|
# here to read the fastlane match certificate store from S3; the four
|
||||||
|
# asc-*/match-* values come from App Store Connect and the match repo.
|
||||||
|
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
||||||
|
match-password: ${{ secrets.MATCH_PASSWORD }}
|
||||||
|
asc-key-id: ${{ secrets.ASC_KEY_ID }}
|
||||||
|
asc-issuer-id: ${{ secrets.ASC_ISSUER_ID }}
|
||||||
|
asc-key-content: ${{ secrets.ASC_KEY_CONTENT }}
|
||||||
Loading…
Add table
Reference in a new issue