Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .cicd-conformance.yaml
Original file line number Diff line number Diff line change
@@ -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: |

78 changes: 78 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!--
Canonical PR template — propagated from
goblin_infra/templates/.github/PULL_REQUEST_TEMPLATE.md
via scripts/propagate-pr-templates.sh
Constitution: CON-CICD-001
Do not edit this file directly in downstream repos; edit the canonical
template here and re-run the propagator. Local repo extensions belong in
.github/PULL_REQUEST_TEMPLATE/<name>.md (selectable via the GitHub UI).
-->

## Summary

<!-- Brief description of what this PR does — focus on intent, not the diff. -->

## 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: <!-- e.g. comment-only change, doc update, hook propagation -->
- Existing test suite still green: <!-- CI run URL -->

## 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

<!-- Specific revert command, prior image tag, or terraform state to restore. -->

## 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)

<!-- Add screenshots for UI changes -->

## Notes for Reviewer

<!-- Any additional context for Paul -->
49 changes: 49 additions & 0 deletions .husky/post-merge
Original file line number Diff line number Diff line change
@@ -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
64 changes: 64 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -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
Loading