25 KiB
.github
Organization-level GitHub configuration for Sea Haven Industries.
Git and PR conventions
Branch naming
feature/, fix/, hotfix/, chore/, docs/, refactor/, release/ + kebab-case description. Branch names do not contain Jira keys.
Commit format
type(scope): description — lowercase, imperative, no trailing period, header ≤ 72 chars. Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, release. Breaking change: feat!: + BREAKING CHANGE: footer.
PR title
type(scope): description (DEV-123) — the Jira key is required at the end in parentheses. Active projects: DEV (product), PLAT (platform), SEC (security). INFRA is a closed archive. Jira-exempt only: Dependabot PRs and permission-controlled emergency reverts.
PR body
Exactly four headings in order: ## Summary, ## Validation, ## Tests, ## Notes. Use None. under Notes if empty.
Deploy path
The two sanctioned deploy paths are merge to main triggering the pipeline and workflow_dispatch on that same pipeline. No manual workstation deploys to production.
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/ci-typescript-frontend.yaml — Reusable CI workflow for bundled TypeScript front-end apps (Vite / React / Vue SPAs). Runs a Sea Haven standards gate (required npm scripts present, no AI-tool footers / hook bypasses / hardcoded secrets in added lines), then format:check, lint, build, vitest unit tests, and an optional Playwright browser smoke. Emits the single ci / ci status context — keep the caller job id ci.
.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/ci-python-app.yaml — Reusable CI for non-SAM Python apps (ruff check + format, optional pytest; no SAM validate).
.github/workflows/ci-dotnet.yaml — Reusable CI for .NET solutions (dotnet build, optional dotnet test; SDK version and solution path as inputs).
.github/workflows/ci-static.yaml — Reusable CI for static sites (e.g. Eleventy builds for seahaven-site).
.github/workflows/ci-mobile-ios.yaml — Reusable CI for React Native iOS apps: dependency install, typecheck, optional lint and unit tests, and an unsigned compile (nothing uploaded). Emits the aggregated ci / ci status context.
.github/workflows/cd-mobile-ios.yaml — Reusable CD for iOS apps via Fastlane to TestFlight (Node + Ruby setup inputs).
.github/workflows/cd-dotnet-eb.yaml — Reusable CD for .NET apps on AWS Elastic Beanstalk. Publishes the project, packages a bundle, uploads it, creates an application version, and updates an existing environment with OIDC credentials — it never creates an environment. Serialised per environment via a concurrency group, and the post-deploy check fails the job if EB rolls the deploy back. The caller owns branch-to-environment mapping.
.github/workflows/callable-pr-policy.yaml — Reusable PR metadata gate. Validates PR title convention (type/scope/Jira key), branch naming, four-section body, commit subjects, AI attribution footers, and workflow file pin compliance — all via GitHub API, no checkout. Emits policy / pr when the caller job is named policy. Optional secrets JIRA_CLOUD_ID, JIRA_SERVICE_ACCOUNT_EMAIL, and JIRA_API_TOKEN must all be set for human PRs; Dependabot skips Jira/branch/body but still runs commit and workflow supply-chain checks. Emergency revert PRs may skip Jira with the emergency-revert label applied by a human collaborator with maintain or admin permission.
The supply-chain check operates in diff mode: for modified or renamed workflow files, the gate fetches the base-branch version at pr.base.sha and reports only violations whose normalized fingerprint is absent from the base. Added files must be fully compliant. Historical drift already present in the base branch is handled by the drift audit/remediation backlog, not by this gate. A failure to fetch the base version is a POLICY-INFRA error and the file is not silently grandfathered.
Workflow file constraints enforced by the supply-chain scanner. Changed workflow files scanned by this policy must use block-style structural keys and inline
run:/uses:values. The scanner fails closed on YAML forms it cannot safely resolve: flow-style step mappings (- { uses: ... },- { run: ... }), sequence-item anchor declarations (- &anchor { uses: ... }and the multiline form- &anchorfollowed by a flow mapping on the next line), escaped or Unicode-encoded structural keys in double-quoted strings ("u\u0073es","r\u0075n"), and YAML aliases or anchors onrun:,uses:, orpermissions:values (run: *cmd,uses: &anchor ...).docker://action refs must carry an immutable sha256 digest pin (docker://<image>@sha256:<64 lowercase hex>); mutable tags and bare image names are rejected. Use the literal unquoted key forms and inline values in all workflow steps.
.github/workflows/callable-labeler.yaml — Org-wide PR auto-labeler. Label rules live inline here (single source of truth) — consumer repos need only a thin caller with contents: read, pull-requests: write, and issues: write; no per-repo labeler.yml.
.github/workflows/callable-dependency-review.yaml — Dependency review on PRs, failing on high severity. Requires Dependency Graph.
.github/workflows/release.yaml — Reusable release workflow: creates an annotated git tag at a commit and publishes a GitHub Release pointing at it. The version is an input (not read from a manifest).
.github/workflows/release-on-merge.yaml — Repo automation (not callable): cuts a tag and GitHub Release for this repo whenever a merge to main changes a reusable workflow, so Dependabot has a release to advance consumer SHA pins to (see the pinning policy below).
.github/workflows/policy.yaml — Intentionally absent from this PR. The self-caller must pin Sea-Haven-Industries/.github/.github/workflows/callable-pr-policy.yaml to a released 40-char SHA with a matching # vX.Y.Z comment; a mutable local ./ path reference is rejected by the supply-chain gate on modified workflow files. The follow-up PR can be opened once this PR merges and release-on-merge.yaml cuts the first release containing callable-pr-policy.yaml, then using gh api /repos/Sea-Haven-Industries/.github/commits/vX.Y.Z --jq .sha to obtain the pin.
.github/workflows/labeler.yaml — This repo's own thin caller of callable-labeler.yaml, so the labeler runs on .github's own PRs.
.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.
Workflow templates (workflow-templates/)
Starter workflows offered on the org's Actions → New workflow page: cdk-deploy, ci-dotnet, ci-mobile-ios, ci-node, ci-python, ci-python-app, ci-static, ci-typescript-frontend, dependency-review, dotnet-eb-deploy, labeler, mobile-ios-deploy, release, sam-deploy, triage. Each is a thin caller of the corresponding reusable workflow above (triage is standalone). Every template has a paired properties.json (name, description, icon, filePatterns for auto-suggestion). Replace any REPLACE-ME placeholders before enabling. Templates are not scanned by Dependabot, so refresh their pinned SHAs opportunistically when editing one.
A pr-policy starter template can be added to workflow-templates/ only after the PR that introduces callable-pr-policy.yaml merges and release-on-merge.yaml cuts the first release containing it. Until then, consumer repos must add the caller workflow manually (see §3).
Ref pinning policy
All workflow refs across the org are pinned to full commit SHAs:
-
Org reusable workflows are referenced at a full commit SHA of this repo with a trailing comment naming the ref or release the pin tracks:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-python-sam.yaml@<full-commit-sha> # v1.0.3Branch refs are mutable: a bad commit on this repo would flow instantly into every consumer's CI and deploy path, while a SHA pin turns the same change into a reviewable Dependabot PR. Two prerequisites keep pins advancing instead of freezing: every consumer repo's
dependabot.ymlmust include thegithub-actionsecosystem (weekly), and Dependabot must be granted access to this repo at the org level (Org Settings → Advanced Security → Global settings → "Grant Dependabot access to repositories"); without the grant, update jobs fail withgit_dependencies_not_reachableand pins freeze silently.release-on-merge.yamltags this repo on every reusable-workflow change so Dependabot has releases to diff against. When adding a caller by hand, pin to the latest release commit (gh api /repos/Sea-Haven-Industries/.github/commits/vX.Y.Z --jq .sha), annotate it with# vX.Y.Z, and let Dependabot advance it from there. -
Third-party and first-party actions (
actions/checkout,actions/setup-python,actions/labeler, …) — a subset are already SHA-pinned (e.g.actions/labeler,aws-actions/*,docker/setup-qemu-action,ruby/setup-ruby); the remainder (actions/checkout,actions/setup-node,actions/setup-python,actions/setup-dotnet,actions/dependency-review-action) currently use floating major-version tags. Full SHA pinning for this group is deferred (PLAT backlog); Dependabot will keep SHA and comment current once pins are set. -
Binary installs are checksum-verified (actionlint in
ci.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:
- The GitHub Actions OIDC provider (conditional — already exists in the account).
- One OIDC deploy role per repo (
githubdeploy-<repo>), assumed by that repo'sdeploy.yamlvia OIDC and passed in asAWS_DEPLOY_ROLE_ARN. CDK repos use these to assume thecdk-hnb659fds-*bootstrap roles; SAM repos use these to runsam deploy. - The shared SAM CloudFormation execution role
github-cfn-execution-role(SamCfnExecutionRole) — passed ascfn-role-arnby every SAMdeploy.yaml(see §3). CloudFormation assumes it to provision the SAM stacks' resources. - The
seahaven-lambda-execution-boundarymanaged policy. - The
seahaven-cfn-exec-iam-managementmanaged policy (SamCfnIamManagementPolicy), attached togithub-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.
Constraint for future maintainers.
github-cfn-execution-roleis explicitly denied from mutating the deploy substrate's own principals — itself, anygithubdeploy-*role, and anyseahaven-*managed policy. Those are owned by this stack and deployed manually with administrator credentials, so nothing legitimate needs that path. If you ever add automation that manages one of them, it must not run throughgithub-cfn-execution-roleor it will fail withAccessDenied.
Where the exec role's IAM statements live. All of them are in the attached
seahaven-cfn-exec-iam-managementmanaged policy — there is no inline copy. The role's inline policies were previously at 10,006 of the 10,240-byte limit, leaving no room to add anything; consolidating into the managed policy brought that to 8,261 bytes (1,979 free). If you need to add a permission to this role, prefer the managed policy: the inline budget is the scarce one.
⚠️ This stack has no CD pipeline — it is deployed manually. (It defines the very roles the pipelines use, so it can't deploy itself.)
# 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:
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
- Deploy the boundary change first.
- Redeploy the SAM stacks so their roles pick it up (while the exec role still permits it).
- Then tighten the exec role.
Wrong order breaks every SAM deploy. CDK repos are unaffected — they deploy via cdk-hnb659fds-* roles, not this execution role.
This ordering rule is about changing the boundary or the conditions that gate it. It does not apply to changes that only add permissions to the exec role.
Permissions boundaries can no longer be removed by CloudFormation
github-cfn-execution-role is explicitly denied iam:DeleteRolePermissionsBoundary. It can set the boundary (that is what SAM needs) but never remove one. Two consequences worth knowing before debugging a stuck stack:
- Removing
PermissionsBoundaryfrom an existing role fails by design. CloudFormation issuesDeleteRolePermissionsBoundaryfor that edit, getsAccessDenied, and the stack update rolls back. Removing the boundary from a SAM function is a security regression, so failing loudly is intended. - Rollback of an update that adds a boundary to an existing role would also fail, landing the stack in
UPDATE_ROLLBACK_FAILED. This is currently unreachable — all 26 IAM roles across the five SAM stacks already carry the boundary (verified 2026-07-27), so no update can add one. It becomes reachable again only if a role is created without the boundary and given one later.
Recovery from UPDATE_ROLLBACK_FAILED is an administrator action, not a pipeline retry: clear the wedged stack with aws cloudformation continue-update-rollback --stack-name <stack> --resources-to-skip <RoleLogicalId>, or replace the role by renaming its logical id. A completed update rollback lands in UPDATE_ROLLBACK_COMPLETE, which is stable and can accept a corrective update; cd-sam blocks only first-create ROLLBACK_COMPLETE and failed or in-progress states.
Setup
1. Org-level secrets
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.
| Secret | Value | Consumed by |
|---|---|---|
ANTHROPIC_API_KEY |
Anthropic API key | reviewer-eval.yml in open-swe |
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).
Three additional org-level secrets are required for the PR policy Jira check. Set each to selected repositories visibility and grant to each consumer repo:
| Secret | Value | Consumed by |
|---|---|---|
JIRA_CLOUD_ID |
Atlassian Cloud ID UUID (find in Jira Settings → Products → Jira Software) | callable-pr-policy.yaml |
JIRA_SERVICE_ACCOUNT_EMAIL |
Email of the service account with read access to DEV/PLAT/SEC projects | callable-pr-policy.yaml |
JIRA_API_TOKEN |
API token for that account (generated at id.atlassian.com/manage-profile/security/api-tokens) | callable-pr-policy.yaml |
2. 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):
name: CI
on:
pull_request:
branches: [main]
jobs:
ci:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-python-sam.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
TypeScript CDK repo (e.g., seahaven-door-unlock-api, seahaven-slack-bot):
name: CI
on:
pull_request:
branches: [main]
jobs:
ci:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
Node.js SAM repo (e.g., payments-dashboard):
name: CI
on:
pull_request:
branches: [main]
jobs:
ci:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
with:
run-typecheck: false
run-cdk-synth: false
run-sam-validate: true
Mixed stack (e.g., exec-aide — TypeScript CDK + Python Lambdas):
name: CI
on:
pull_request:
branches: [main]
jobs:
python:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-python-sam.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
with:
source-dirs: "src"
run-sam-validate: false
typescript:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
3. Add PR policy to a repo
Create .github/workflows/policy.yaml in the target repo. The Jira secrets must already be granted to the repo (see §1).
name: PR Policy
on:
pull_request:
types: [opened, reopened, synchronize, edited, labeled, unlabeled, ready_for_review]
concurrency:
group: policy-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
issues: read
pull-requests: read
jobs:
policy:
uses: Sea-Haven-Industries/.github/.github/workflows/callable-pr-policy.yaml@<full-commit-sha> # vX.Y.Z
secrets:
JIRA_CLOUD_ID: ${{ secrets.JIRA_CLOUD_ID }}
JIRA_SERVICE_ACCOUNT_EMAIL: ${{ secrets.JIRA_SERVICE_ACCOUNT_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
Replace <full-commit-sha> with the SHA of the release that contains callable-pr-policy.yaml:
gh api /repos/Sea-Haven-Industries/.github/commits/vX.Y.Z --jq .sha
The check-run name is policy / pr. If your branch-protection ruleset requires this context, add it after the first PR passes.
Known platform limitation — GITHUB_TOKEN label and metadata events. When the
policyworkflow re-runs onlabeledoreditedevents, the metadata edits themselves (label adds, title edits) must be performed by a GitHub App or a PAT that owns its own event stream. Edits made throughGITHUB_TOKENdo not reliably emit a newpull_requestevent to trigger re-evaluation; the check stays in its prior state until the next push or manual re-run. Org automation that applies labels (such as theemergency-revertlabel) must therefore use a GitHub App token or a PAT — notGITHUB_TOKEN— or the policy gate will not re-run automatically after the label is applied. This is a GitHub platform constraint, not a deficiency that can be solved at the workflow level.
4. 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):
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-sam.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
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/orseahaven-lambda-execution-boundaryfirst. See AWS deploy roles & IAM.
TypeScript CDK repo (e.g., seahaven-door-unlock-api):
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
Python CDK repo (e.g., po-ingest):
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
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):
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
with:
enable-qemu: true
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
.NET app on Elastic Beanstalk (e.g., shoc-backend):
name: Deploy
on:
push:
branches: [dev]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-dotnet-eb.yaml@81cf168170f356d1423d7736f7ce93fd6611ad53 # v1.0.4
with:
project: Api.SeaHavenIndustries/Api.SeaHavenIndustries.csproj
eb-application: shoc-backend
eb-environment: shoc-backend-dev
procfile-command: dotnet Api.SeaHavenIndustries.dll
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
The environment must already exist — this workflow deploys a new application version to it and never creates one. Branch-to-environment mapping belongs in the caller: add one job per branch (e.g.
dev→…-dev,main→…-staging) rather than parameterising the reusable by branch.procfile-commandgenerates the Procfile the Amazon Linux .NET platform needs; omit it only if the repo commits its own Procfile into the publish output. Deploys are serialised per environment, and the job fails if Elastic Beanstalk rolls the version back.
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 |