This is the org-special Cobenian/.github repo — the single source of truth
that all Cobenian repos inherit from, implementing STANDARDS.md §9.
The canonical, org-wide normative docs (RFC 2119). Every product cites these.
| Doc | Governs |
|---|---|
STANDARDS.md |
How repos are built and shipped (GitHub, CI, CD) |
PLATFORM-ADOPTION.md |
How a product consumes the platform (inbound: product → Accounts/Foundry/Data) |
PRODUCT-SURFACE.md |
How a product exposes itself (outbound: REST, MCP, OAuth2 — caller → product) |
IN-APP-ADMIN.md |
The in-app admin surface each product owns (tier 3 of 3) |
DELIVERY-CONTRACT.md |
How a product gets a message to somebody (Accounts Notify + Foundry Comms conform to one shape) |
.github/
workflows/
elixir-ci.yml # REUSABLE CI (workflow_call) — the heavy lifting
fly-deploy.yml # REUSABLE deploy (workflow_call) — retry + --wait-timeout + smoke
aws-ecs-deploy.yml # REUSABLE AWS ECS deploy: build, scan gate, roll, verify + watch (@v2)
aws-guard.yml # REUSABLE guard check: runs the caller's main bin/guard on a PR (@v2)
aws-automerge.yml # REUSABLE automerge of bot PRs once CI + guard pass (@v2)
aws-toolchain-check.yml # REUSABLE weekly Elixir/OTP/Debian image check (@v2)
mutation.yml # REUSABLE weekly mutation sample (workflow_call)
test-improvement.yml # REUSABLE weekly unit test improvement (workflow_call)
tools/ # mutation-sample, test-improvement-verify — checked out by those two
tests/ # self-test.yml runs these
PULL_REQUEST_TEMPLATE.md # org-default PR template (inherited)
ISSUE_TEMPLATE/
bug_report.md # org-default (inherited)
feature_request.md # org-default (inherited)
templates/ # COPY these into each consumer repo (not inherited)
ci.yml # thin caller of the reusable CI
fly-deploy.yml # thin caller of the reusable deploy (workflow_run trigger + app inputs)
mutation.yml # thin caller of the weekly mutation sample
test-improvement.yml # thin caller of the weekly unit test improvement
aws-deploy.yml # AWS apps: → .github/workflows/deploy.yml
aws-guard.yml # AWS apps: → .github/workflows/guard.yml
aws-automerge.yml # AWS apps: → .github/workflows/automerge.yml
aws-refresh.yml # AWS apps: → .github/workflows/refresh.yml
dependabot-app.yml # → .github/dependabot.yml (apps)
dependabot-library.yml # → .github/dependabot.yml (libraries)
CODEOWNERS # → .github/CODEOWNERS (not inheritable)
credo.exs # → .credo.exs (apps; ships the fleet log-attribution guard)
PULL_REQUEST_TEMPLATE.md and ISSUE_TEMPLATE/* apply to any repo that doesn't
define its own — no per-repo copy needed.
elixir-ci.yml (CI) and fly-deploy.yml (deploy) are consumed via
uses: Cobenian/.github/.github/workflows/<name>@v1. Tag releases (v1, v1.1,
…) so consumers pin and bump deliberately; v1 is the moving major pointer.
Deploy inputs are documented in STANDARDS.md §4.
AWS apps (ECS on Fargate) call the aws-*.yml workflows at @v2; Fly apps stay on
@v1. Each tag is moved separately, so an AWS-only change ships by moving v2 alone and never
reaches a Fly app. A change both need (a new Elixir in elixir-ci.yml, say) moves both
tags; the release notes say which moved. v1 freezes when the Fly apps are retired. The aws-*
workflows were extracted from cobenian-company's own loop so every AWS app shares one copy.
What the @v2 workflows check, and their inputs (each input is also described in the workflow):
aws-ecs-deploy.yml—app,url,deploy-role,stack-name(all required),aws-region(us-east-1),alerts-topic,ci-check,max-severity(HIGH),head-sha,smoke-path(/sign-in),watch-minutes(10),rebuild(false).- Deploys only when the
ci-checkcheck run passed and was reported by GitHub Actions (app.slug == "github-actions"), so a same-named check posted by anything else doesn't count. - Blocks the image when ECR's scan finds anything at or above
max-severity:CRITICAL,HIGH(high or critical, the default) orMEDIUM. ECR basic scanning sees OS packages only; Hex packages are covered by CI'shex.audit/deps.audit. - Builds the new revision from the task definition CloudFormation manages in
stack-name(exactly oneAWS::ECS::TaskDefinition), never from the service's live one, changing only the image; registers it with the original's tags plusapp=<app>; and refuses to roll if the service runs a different task-definition family. The deploy role needscloudformation:DescribeStackResourceson the stack andecs:TagResourcefor tag-on-register; it can requireaws:RequestTag/app. The SNS alert is best-effort, so a role withoutsns:Publishstill gets theincidentissue.
- Deploys only when the
aws-guard.yml—guard-command(bin/guard),approver(brweber2),approval-label(bryan-approved). Every PR is judged by the guard script, whoever opened it: there is no pass for a person's own PR (agents open theirs asbrweber2). A held change needs the approval below.aws-automerge.yml—bots(app/dependabot),environment(engineer),app-id-secret,app-key-secret,guard-command,approver,approval-label. It finds the PR by the run's head sha (never the branch name, which apull_request_targetrun may report as the base).
Approval (guard and automerge, identical code): a held change is approved only when the
label is on the PR, the head is still the judged sha on a branch of this repo, and the issue
events show a labeled event for the label by the approver, created after the branch's latest
push. The latest push comes from GitHub's repository activity API
(GET /repos/{repo}/activity?ref=refs/heads/<branch>), which records each push and force-push
with a server timestamp and the sha it moved to; commit author/committer dates, which the commit's
author chooses, are never used. All pages of events are read. Anything unreadable fails closed.
ci.yml, fly-deploy.yml (both thin callers now), dependabot.yml, CODEOWNERS.
.github/workflows/ci.yml (from templates/ci.yml):
name: CI
on:
push: { branches: [main] }
pull_request: { branches: [main] }
permissions: { contents: read }
jobs:
build-and-test:
uses: Cobenian/.github/.github/workflows/elixir-ci.yml@v1
with:
profile: app # app | library
umbrella: false # true for Companion
postgres: true # false for DB-less libraries
nonblocking: "" # e.g. "credo,dialyzer" while ratcheting a live repo
secrets: inherit| Input | Default | Notes |
|---|---|---|
profile |
— (required) | app | library; library skips Sobelow |
umbrella |
false |
set sobelow_root / migrations_path to the child app paths |
postgres |
true |
starts Postgres + runs ecto.create/migrate; false for DB-less libraries |
nonblocking |
"" |
comma list → those checks run report-only. Recognized: credo, sobelow, hex_audit, deps_audit |
dialyzer_blocking |
false |
Dialyzer is report-only by default; flip to true once PLTs are stable |
sobelow_root |
. |
apps/<web_app> for umbrellas |
migrations_path |
priv/repo/migrations |
duplicate-timestamp check target |
elixir-version |
1.20.4 |
|
otp-version |
29.1.2 |
|
postgres_image |
postgres:17 |
|
gitleaks_version |
8.21.2 |
CLI binary (the action needs a paid org license) |
audit_ignore_max_days |
90 |
furthest an .audit-ignore.yml entry may expire (1–365). See STANDARDS.md §5.1 |
Two reusable workflows, one pair of tools, every app (Elixir or Python):
| Workflow | When (caller's cron) | Does |
|---|---|---|
mutation.yml |
Monday 06:17 UTC | Breaks one operator at a time in the files listed in .github/mutation/paths.txt, runs the tests that name each file, posts which breaks no test noticed to the mutation-sample issue. Report-only. |
test-improvement.yml |
Tuesday 07:17 UTC | Same ISO-week seed. A model drafts tests for the missed breaks; tools/test-improvement-verify decides from the record (only tests changed, nothing removed, suite green, every earlier catch kept, at least one new catch) whether to open ONE pull request for a person. |
- Tools live here only (
tools/mutation-sample,tools/test-improvement-verify). The workflows check them out atjob.workflow_sha, so movingv1moves workflow and tools together. Tested byself-test.yml. - Per repo: copy
templates/mutation.ymlandtemplates/test-improvement.yml, setlanguage(andpostgres,umbrella), and write.github/mutation/paths.txt: 3-10 files where a wrong operator would cost money, leak access or corrupt data, each with a#comment saying why. - Not configured is a failure. No paths file, an empty one, or (for test improvement) no
CLAUDE_CODE_OAUTH_TOKENsecret fails the run with "not configured" in the job summary. A green run always did something. - Test improvement runs on the Claude subscription, never the API. Claude Code authenticates with
CLAUDE_CODE_OAUTH_TOKEN(made withclaude setup-token, an org secret shared with each repository).ANTHROPIC_API_KEYis deliberately not a fallback and is removed from the drafting step's environment: a fallback would resume billing the API, unseen, the day the token lapsed. - Model:
TEST_IMPROVEMENT_MODELvariable, defaultclaude-sonnet-5. - A pull request opened by the job does not trigger CI (GitHub will not start workflows for a pull request
GITHUB_TOKENopened); close and reopen it to run CI.
Because CI is a reusable workflow, the status-check context is nested:
build-and-test / Build and test (not bare Build and test). Set branch
protection's required context to that nested string. Verify with:
gh api repos/Cobenian/<repo>/commits/<sha>/check-runs --jq '.check_runs[].name'The deploy trigger keys off the workflow name (CI), which is unaffected.
After the build, the reusable CI re-runs the Elixir scanners in machine-readable mode,
folds their results into one JSON file, and uploads it as the workflow-run artifact
cobenian-scan-posture. Foundry's GitHub connector reads that artifact off the
completed run and emits connectors.scan_result.received so Cobenian Panel's app &
security monitor can fold CI-computed posture per repo + commit (issue
#106 Option A;
cobenian-foundry SPEC-CON-003 §14).
-
No configuration, no secret, no endpoint. The connector pulls the artifact via the GitHub App install it already has — CI needs no
FOUNDRY_INGEST_TOKENand makes no network call to Foundry. (This replaces the reverted POST-with-bearer emitter, PR #11 → reverted by #12; only the transport changed.) -
Always-on & never a gate. The build/upload steps are
continue-on-error: true+if: always()and internally guarded, so they can never fail a caller's build and still run when an earlier blocking scanner failed. The existing blocking scanner steps are unchanged and still own pass/fail. -
Artifact contract — the single JSON file's body:
A scanner that did not run or failed is omitted — never fabricated as clean. Per cobenian-foundry CON-GH-015, no secret values, secret locations, or source snippets are ever emitted (gitleaks/Sobelow contribute counts only); the raw scanner reports stay on the runner and are never uploaded.
| Repo | profile | umbrella | postgres | deploy |
|---|---|---|---|---|
| cobenian-accounts | app | false | true | yes |
| cobenian-cadence | app | false | true | yes |
| cobenian-agents (Oversight) | app | false | true | yes |
| cobenian_companion_umbrella | app | true | true | yes |
| cobenian-core-accounts | library | false | false | no |
{ "source": "github", // links to the repo/workflow_run events "repo": "<owner>/<repo>", "head_sha": "…", "run_url": "…", "workflow_run_id": "…", "scanned_at": "<ISO8601>", "sobelow": { "findings": N, "by_confidence": { "high": N, "medium": N, "low": N } }, "mix_audit": [ { "package": "…", "id": "CVE/GHSA", "severity": "…", "title": "…" } ], "hex_audit": [ { "package": "…", "version": "…", "reason": "…" } ], "gitleaks": { "secrets_found": N }, // COUNT ONLY — never a value or location "hex_outdated": [ { "package": "…", "current": "…", "latest": "…" } ] }