Create Easy Mode starter template repos
Part of epic "Build sponsored Easy Mode app hosting" (PRD task 13412, architecture task 13413). Architecture child **C3 Templates**.
## Original requirement (verbatim)
> Deploy: auto on push to main, from a repo Jaah creates and owns (temporarily) in Jaah's existing GitHub org, from a template carrying the deploy workflow (per-app OIDC role scoped to that app's resources only). Repo hand-over to the client later (transfer + trust removal).
> 7. Template repo + deploy workflow: Vite front-end + Express or FastAPI back-end Dockerfile, Lambda Web Adapter, deploy.yml via OIDC, migration step against the app's database.
PRD D10 (binding): Two starter templates: Node (Vite + Express) and Python (Vite + FastAPI).
## Approach (settled by the Architect — the pinned seam with the provisioning child C6)
Repos `jaah-ai/easy-template-node` (Vite + Express) and `jaah-ai/easy-template-python` (Vite + FastAPI), marked "template" on GitHub, each self-tested by a CI job. Created with the GitHub admin credential below (via `jaah-forge` or its API, never `gh`). Layout: `web/` (Vite) and `api/` (back-end + Dockerfile).
- Actions **variables** (repo level, set later by C6): `AWS_REGION, AWS_ROLE_ARN, APP_SLUG, SITE_BUCKET, SITE_PREFIX, ECR_REPOSITORY, LAMBDA_FUNCTION, CF_DISTRIBUTION_ID, CF_TENANT_ID, APP_URL`. No secrets.
- `.github/workflows/deploy.yml`: on push to `main`, `environment: production`, `permissions: id-token: write`; `aws-actions/configure-aws-credentials` with `AWS_ROLE_ARN`; build `web/` → `aws s3 sync dist s3://$SITE_BUCKET/$SITE_PREFIX --delete`; `docker buildx --platform linux/arm64` `api/` → push `$ECR_REPOSITORY:$GITHUB_SHA`; `lambda update-function-code` + wait; **migrate**: `aws lambda invoke --payload '{"jaah":"migrate","key":…}'` (Lambda Web Adapter pass-through `AWS_LWA_PASS_THROUGH_PATH=/events` → the app runs migrations, returns 200 or fails the job); `create-invalidation-for-distribution-tenant /*`; smoke `curl $APP_URL/api/health`. The deploy job also logs the decoded OIDC `sub` claim only (C6 pins the role trust to the observed form). The workflow skips cleanly (no failure) when the variables are unset, so the template's own pushes stay green.
- Back-end: listens 8080, routes under `/api/*`, `GET /api/health` → 200 with a DB round-trip (reads `DATABASE_URL`). **Every** path, `/events` included, requires the origin key: HTTP paths via header `x-jaah-origin == JAAH_ORIGIN_KEY`; the migrate pass-through via payload `{"jaah":"migrate","key":<JAAH_ORIGIN_KEY>}` (the workflow reads the key with `lambda:GetFunctionConfiguration`). `Dockerfile` copies `public.ecr.aws/awsguru/aws-lambda-adapter:<pinned>` into `/opt/extensions/`. A tiny example table + migration so the health check proves read/write.
- `.github/workflows/test.yml`, job id **`test`**, on `pull_request` to `main` and `push` to `fleet/**`: runs the template's gate full command (below); it is the ruleset's required check C6 sets. Green on the template.
- Gate commands (C6's code-side template catalogue uses exactly these; the repos must pass them): node — quick `npm ci && npm test`, full `npm ci && npm test && npm run build`; python — quick `pip install -r api/requirements.txt && pytest -q`, full `pip install -r api/requirements.txt && pytest -q && (cd web && npm ci && npm run build)`.
- Each template has a short README for the client (what lives where, how deploy works).
- Keyless-call assertion: "C3 CI asserts a keyless `POST /events` through the function URL is 403" — realized in C3's CI against the container run locally under the Lambda Web Adapter (no function URL exists before C6); the real function-URL check is part of C6's deploy oracle.
## Acceptance criteria (verbatim, BA)
- **AC-24 Templates.** Two template repos (Node: Vite + Express; Python: Vite + FastAPI) each carry a back-end Dockerfile running under the Lambda Web Adapter, a `deploy.yml` assuming the per-app role through OIDC, and a migration step against the app's database; each deploys green from a fresh copy without edits. → verify: AC-11 for each template. (This child owns the repo content; the end-to-end deploy is proven in C6.)
Credentials & reach:
- AC-9, AC-11, AC-19, C3 template creation, C6 GitHub writes — `me/users/roman@gmind.tech/github_admin` (classic token, `repo, workflow, admin:org`; admin of `jaah-ai`); hand-over target org `ruzhukov-genai` (admin, so transfer needs no acceptance) — verified — `GET /user/memberships/orgs/jaah-ai` = admin/active.
Out of scope: a separate GitHub org; a separate app apex domain; custom domains, registrar purchase and paid hosting after sponsorship (draft task 10233 stays as that later work); the always-on shared-box hosting tier. Do NOT use `jaah-apps-github-actions-deploy` or any shared AWS role in the templates; do NOT put any secret in the repos or Actions variables; do NOT change the jaah repo beyond what the templates need (control-plane code is C6).
Done means:
- Both repos exist in `jaah-ai`, `is_template: true` (cite `jaah-forge api get repos/jaah-ai/easy-template-node` and `…-python`).
- Each repo's `test` job is green on its default branch and on a PR (cite run URLs); it runs the gate full command above and the keyless-403 assertions (`POST /events` and `GET /api/health` without the key → 403; with the key → 200 and a DB round-trip against a Postgres service container; migrate payload with a wrong key → refused).
- `deploy.yml` matches the pinned steps and variables exactly (file:line per step); Dockerfile pins the Lambda Web Adapter version and builds for arm64.
- A fresh copy made from each template passes its gate commands without edits (cite a local `generate` + run). End-to-end deploy (AC-11/AC-24 verify) — deferred to C6, which needs the infra and quota.
Outcome
Created private templates jaah-ai/easy-template-node and -python (is_template). The test check is green on main and on a PR, including LWA keyless-403 checks; fresh copies pass the gate. Docs pointer is in PR #7900 (quick gate green, queued). Real AWS deploy deferred to C6. Memory: none applied.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)