diff --git a/.cicd-conformance.yaml b/.cicd-conformance.yaml new file mode 100644 index 0000000..6ccaca5 --- /dev/null +++ b/.cicd-conformance.yaml @@ -0,0 +1,39 @@ +# CON-CICD-001 conformance manifest — deployable repo +# Generated by goblin_infra/scripts/scaffold-conformance-manifest.sh on 2026-05-01T23:52:16Z +# All stages start `implemented: false`. Owning agent flips each to true as the +# stage gets a passing test, and updates evidence_link. +constitution: CON-CICD-001 +repo: android +non_deployable: false +runtime: unknown +framework_ref: 65212ac2cf2596cf4a41848af77bc8a9f61601b0 +policy_ref: 95a56ae69942b5575cf20f3d8131c872c6746c32 +owner: TBD +deploy_workflows_detected: false +stages: + plan_validation: + implemented: false + evidence_link: null + pre_merge_gates: + implemented: false + evidence_link: null + build: + implemented: false + evidence_link: null + promote: + implemented: false + evidence_link: null + deploy: + implemented: false + evidence_link: null + verify: + implemented: false + evidence_link: null + evidence: + implemented: false + evidence_link: null + rollback: + implemented: false + evidence_link: null +notes: | + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..862a5bf --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,78 @@ + + +## Summary + + + +## Type of Change + +- [ ] Bug fix (non-breaking change that fixes an issue) +- [ ] New feature (non-breaking change that adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) +- [ ] Documentation update +- [ ] Refactor (no functional changes) +- [ ] Chore (dependencies, config, etc.) + +## TDD Evidence + +CON-CICD-001 §3.2 requires a failing test → minimal change → passing test +paper trail for every plan execution. Fill out the section that applies; delete the others. + +### Plan-level test (preferred) + +- Failing test (commit / file / line): +- Minimal change (commit): +- Passing test (commit / CI run URL): + +### No new behaviour (refactor / docs / ops) + +- Why no new behaviour: +- Existing test suite still green: + +## Testing + +- [ ] Unit tests pass locally +- [ ] Integration tests pass locally (if applicable) +- [ ] Manual testing completed + +## Constitution Compliance + +- [ ] CON-CICD-001 §3.3: no `--update-secrets` / `--update-env-vars` / `--set-env-vars` / `--set-secrets` flags in deploy workflows (terraform owns Cloud Run env config). +- [ ] `.cicd-conformance.yaml` updated if any stage's `implemented` flag changed. +- [ ] Husky canonical hooks unchanged in this PR (or, if changed, updated via `goblin_infra/scripts/propagate-husky-hooks.sh --apply`). +- [ ] CON-TESTING-001 (Testing Strategy) satisfied or N/A. + +## Rollback Plan + + + +## Checklist + +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my code +- [ ] I have commented my code where necessary +- [ ] I have updated documentation as needed +- [ ] My changes generate no new warnings +- [ ] Any dependent changes have been merged + +## Reviewer Checklist + +- [ ] Risk classification (low / medium / high) acknowledged. +- [ ] If high-risk (live customer traffic): explicit founder ack noted in comments. +- [ ] Evidence artefacts uploaded (where applicable). + +## Screenshots (if applicable) + + + +## Notes for Reviewer + + diff --git a/.husky/post-merge b/.husky/post-merge new file mode 100755 index 0000000..72ffcc2 --- /dev/null +++ b/.husky/post-merge @@ -0,0 +1,49 @@ +#!/usr/bin/env sh +# ============================================================================= +# CANONICAL post-merge — goblin/toreva fleet +# +# Source of truth: goblin_infra/husky-canonical/post-merge +# DO NOT EDIT IN-REPO. Re-run: goblin_infra/scripts/propagate-husky-hooks.sh --apply +# +# Fires after: git pull / git merge (local merges only — not GitHub-side). +# +# Behavior: +# - If the merge landed on main/master, print a deploy reminder. +# - If GOBLIN_HUSKY_POSTMERGE_TRIGGER_DEPLOY=1 is set AND `gh` CLI is +# available AND there is a deploy-prod.yml workflow, invoke it. +# +# Note: the canonical "main → deploy" path is GitHub Actions +# `on: push: branches: [main]` in each repo's .github/workflows/deploy-*.yml. +# This hook is local notification + opt-in manual trigger only. +# ============================================================================= +set -e + +HUSKY_DIR="$(dirname "$0")" + +current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')" + +case "$current_branch" in + main|master) + echo "" + echo "ℹ post-merge: local $current_branch updated." + echo " → GitHub Actions will trigger deploy on push (if .github/workflows/deploy-*.yml configured)." + + if [ "${GOBLIN_HUSKY_POSTMERGE_TRIGGER_DEPLOY:-}" = "1" ]; then + if command -v gh >/dev/null 2>&1; then + # Find any deploy-prod*.yml workflow and trigger it against main. + wf="$(ls .github/workflows/deploy-prod*.yml 2>/dev/null | head -1)" + if [ -n "$wf" ]; then + echo " → GOBLIN_HUSKY_POSTMERGE_TRIGGER_DEPLOY=1 → running: gh workflow run $(basename "$wf")" + gh workflow run "$(basename "$wf")" --ref "$current_branch" || \ + echo " (gh workflow run failed — continue anyway)" + fi + fi + fi + echo "" + ;; +esac + +# --- Repo-local extension ----------------------------------------------------- +if [ -x "$HUSKY_DIR/local/post-merge.sh" ]; then + "$HUSKY_DIR/local/post-merge.sh" || exit $? +fi diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..e86837f --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,64 @@ +#!/usr/bin/env sh +# ============================================================================= +# CANONICAL pre-commit — goblin/toreva fleet +# +# Source of truth: goblin_infra/husky-canonical/pre-commit +# DO NOT EDIT IN-REPO. Re-run: goblin_infra/scripts/propagate-husky-hooks.sh --apply +# +# Scope: staged files only. Never scan the full repo (context budget rule — +# see feedback_narrow_vs_broad_dispatch_pattern). +# +# Checks: +# 1. Block secret-bearing files (secrets/state belong in managed stores) +# 2. Block high-signal secret patterns in staged diff +# 3. Call repo-local extension .husky/local/pre-commit.sh if present +# ============================================================================= +set -e + +HUSKY_DIR="$(dirname "$0")" + +# --- 1. Block secret-bearing files (templates allowed) ------------------------ +if git diff --cached --name-only --diff-filter=ACMR \ + | grep -E '(^|/)(\.env($|\.)|[^/]+\.tfvars(\.json)?$|[^/]+\.tfstate($|\.))' \ + | grep -vE '\.(example|sample|template)$'; then + echo "✗ refusing to commit secret-bearing files" + echo " .env* -> move secrets to Secret Manager" + echo " *.tfvars -> commit only placeholder templates such as *.tfvars.example" + echo " *.tfstate -> never commit Terraform state; use the configured backend" + exit 1 +fi + +# --- 2. Block high-signal secret patterns in staged content ------------------ +# NOTE: final grep uses -q so the matching line (containing the actual secret) +# is NOT echoed back to the developer's terminal / scrollback / screen share. +# Patterns (prefix → provider): +# AIza… Google API +# (AKIA|ASIA| AWS access key family: long-lived (AKIA), STS temporary +# AROA|ANPA| (ASIA), role (AROA), managed-policy (ANPA), IAM user +# AIDA) (AIDA) +# gh[psruo]_… GitHub token (PAT / OAuth / user / server / refresh) +# github_pat_… GitHub fine-grained personal access token +# sk_live_… Stripe live key +# sk-…{48,255} Anthropic (sk-ant-*) / OpenAI (sk-proj-*, legacy); +# flanked by non-key chars + length-bounded so long base64 +# blobs / minified JS containing "sk-" don't false-positive +# xox[baprs]-… Slack token +# xapp-… Slack app-level token +# -----BEGIN… PEM-encoded private key +# "private_key" GCP service-account JSON +if git diff --cached -U0 --diff-filter=ACMR \ + | grep -E '^\+' \ + | grep -vE '^\+\+\+' \ + | grep -qE 'AIza[0-9A-Za-z_-]{35}|(AKIA|ASIA|AROA|ANPA|AIDA)[0-9A-Z]{16}|gh[psruo]_[A-Za-z0-9_]{36}|github_pat_[A-Za-z0-9_]{82}|sk_live_[0-9A-Za-z]{20,}|(^|[^A-Za-z0-9_-])sk-[A-Za-z0-9_-]{48,255}([^A-Za-z0-9_-]|$)|xox[baprs]-[0-9A-Za-z-]{10,}|xapp-[0-9A-Za-z-]{10,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|"private_key"[[:space:]]*:[[:space:]]*"-----BEGIN'; then + echo "✗ staged diff contains what looks like an API key, service-account JSON, or private key" + echo " (the matching line is intentionally NOT printed — check \`git diff --cached\` yourself)" + echo " if a false positive, unstage and open an issue to refine the pattern" + exit 1 +fi + +# --- 3. Repo-local extension -------------------------------------------------- +# Repo-specific checks (lint, typecheck, unit tests on staged files, etc.) +# live in .husky/local/pre-commit.sh and are preserved across re-propagation. +if [ -x "$HUSKY_DIR/local/pre-commit.sh" ]; then + "$HUSKY_DIR/local/pre-commit.sh" || exit $? +fi diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..4469baa --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,36 @@ +#!/usr/bin/env sh +# ============================================================================= +# CANONICAL pre-push — goblin/toreva fleet +# +# Source of truth: goblin_infra/husky-canonical/pre-push +# DO NOT EDIT IN-REPO. Re-run: goblin_infra/scripts/propagate-husky-hooks.sh --apply +# +# Policy: pushes to main/master are blocked at local level as belt-and-braces +# over GitHub branch protection. Pushes to feature branches are allowed. +# +# No local bypass is provided. Direct writes to protected integration branches +# must go through a PR and protected merge automation. +# ============================================================================= +set -e + +HUSKY_DIR="$(dirname "$0")" + +protected_branch_push=0 +while IFS=' ' read -r local_ref local_sha remote_ref remote_sha; do + case "$remote_ref" in + refs/heads/main|refs/heads/master) + protected_branch_push=1 + ;; + esac +done + +if [ "$protected_branch_push" = "1" ]; then + echo "✗ refusing to push directly to main/master" + echo " → open a PR from a feature branch instead" + exit 1 +fi + +# --- Repo-local extension ----------------------------------------------------- +if [ -x "$HUSKY_DIR/local/pre-push.sh" ]; then + "$HUSKY_DIR/local/pre-push.sh" || exit $? +fi