c0a16f53ad
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Failing after 2s
Generic: Repo Health / Scripts governance (push) Successful in 3s
Generic: Repo Health / Repository health (push) Failing after 3s
Platform: MokoStandards CI / Gate 1: Code Quality (push) Failing after 23s
Platform: MokoStandards CI / Gate 2: Unit Tests (8.1) (push) Has been skipped
Platform: MokoStandards CI / Gate 2: Unit Tests (8.2) (push) Has been skipped
Platform: MokoStandards CI / Gate 2: Unit Tests (8.3) (push) Has been skipped
Platform: MokoStandards CI / Gate 3: Self-Health Check (push) Has been skipped
Platform: MokoStandards CI / Gate 4: Governance (push) Has been skipped
Platform: MokoStandards CI / Gate 5: Template Integrity (push) Has been skipped
Platform: MokoStandards CI / CI Summary (push) Failing after 1s
5-gate pipeline that dogfoods the platform's own tools:
Gate 1: Code Quality — PHPCS (PSR-12), PHPStan (L5), Psalm
Gate 2: Unit Tests — PHPUnit across PHP 8.1/8.2/8.3 matrix
Gate 3: Self-Health — runs bin/moko health against its own repo
Gate 4: Governance — SPDX headers, secret detection, version consistency
Gate 5: Template Integrity — YAML lint on workflow templates, gitignore
validation, PHP syntax on all validate/ scripts
The standards engine must pass its own standards.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
432 lines
18 KiB
YAML
432 lines
18 KiB
YAML
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# FILE INFORMATION
|
|
# DEFGROUP: Gitea.Workflow
|
|
# INGROUP: MokoStandards.CI
|
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
# PATH: /.gitea/workflows/ci-platform.yml
|
|
# VERSION: 01.00.00
|
|
# BRIEF: MokoStandards Platform CI — the standards engine validates itself
|
|
#
|
|
# +========================================================================+
|
|
# | MOKOSTANDARDS PLATFORM CI |
|
|
# +========================================================================+
|
|
# | |
|
|
# | This is NOT a generic CI workflow. This is the self-validation |
|
|
# | pipeline for the central MokoStandards enterprise platform. |
|
|
# | |
|
|
# | It dogfoods every tool the platform ships to governed repos: |
|
|
# | |
|
|
# | Gate 1 — Code Quality phpcs (PSR-12), phpstan (L5), psalm |
|
|
# | Gate 2 — Unit Tests phpunit with coverage threshold |
|
|
# | Gate 3 — Self-Health bin/moko health against its own repo |
|
|
# | Gate 4 — Governance Checks headers, secrets, structure, versions |
|
|
# | Gate 5 — Template Lint validate workflow templates parse clean |
|
|
# | |
|
|
# | If it doesn't pass its own checks, it can't enforce them. |
|
|
# | |
|
|
# +========================================================================+
|
|
|
|
name: "Platform: MokoStandards CI"
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- dev
|
|
- dev/**
|
|
- rc/**
|
|
paths-ignore:
|
|
- '**.md'
|
|
- 'wiki/**'
|
|
- '.gitea/ISSUE_TEMPLATE/**'
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
- dev
|
|
- dev/**
|
|
- rc/**
|
|
workflow_dispatch:
|
|
inputs:
|
|
full_suite:
|
|
description: 'Run full validation suite (including slow checks)'
|
|
required: false
|
|
default: 'true'
|
|
type: boolean
|
|
|
|
concurrency:
|
|
group: ci-platform-${{ github.repository }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
PHP_VERSION: '8.2'
|
|
|
|
jobs:
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# Gate 1 — Code Quality
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
code-quality:
|
|
name: "Gate 1: Code Quality"
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup PHP ${{ env.PHP_VERSION }}
|
|
run: |
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq php${{ env.PHP_VERSION }}-cli php${{ env.PHP_VERSION }}-mbstring \
|
|
php${{ env.PHP_VERSION }}-xml php${{ env.PHP_VERSION }}-curl php${{ env.PHP_VERSION }}-zip \
|
|
php${{ env.PHP_VERSION }}-intl >/dev/null 2>&1
|
|
php -v
|
|
|
|
- name: Install Composer dependencies
|
|
run: |
|
|
composer install --no-interaction --prefer-dist
|
|
echo "Dependencies installed: $(composer show | wc -l) packages"
|
|
|
|
- name: "PHP Syntax Check"
|
|
run: |
|
|
ERRORS=0
|
|
CHECKED=0
|
|
while IFS= read -r -d '' file; do
|
|
CHECKED=$((CHECKED + 1))
|
|
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
|
echo "::error file=${file}::PHP syntax error"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
done < <(find lib/ validate/ automation/ cli/ src/ deploy/ -name "*.php" -print0 2>/dev/null)
|
|
|
|
{
|
|
echo "### PHP Syntax"
|
|
echo "Checked ${CHECKED} files — ${ERRORS} error(s)"
|
|
} >> $GITHUB_STEP_SUMMARY
|
|
|
|
[ "$ERRORS" -eq 0 ] || exit 1
|
|
|
|
- name: "PHPCS (PSR-12)"
|
|
run: |
|
|
vendor/bin/phpcs --standard=phpcs.xml --report=summary lib/ validate/ automation/ 2>&1 || {
|
|
echo "::error::PHPCS found coding standard violations"
|
|
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
|
echo "Coding standard violations detected. Run \`composer phpcs\` locally." >> $GITHUB_STEP_SUMMARY
|
|
exit 1
|
|
}
|
|
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
|
echo "PSR-12 compliance: passed" >> $GITHUB_STEP_SUMMARY
|
|
|
|
- name: "PHPStan (Level 5)"
|
|
run: |
|
|
vendor/bin/phpstan analyse -c phpstan.neon --no-progress --error-format=github 2>&1 || {
|
|
echo "::error::PHPStan found type errors"
|
|
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
|
echo "Static analysis errors detected. Run \`composer phpstan\` locally." >> $GITHUB_STEP_SUMMARY
|
|
exit 1
|
|
}
|
|
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
|
echo "Static analysis (level 5): passed" >> $GITHUB_STEP_SUMMARY
|
|
|
|
- name: "Psalm"
|
|
continue-on-error: true
|
|
run: |
|
|
if [ -f "psalm.xml" ]; then
|
|
vendor/bin/psalm --config=psalm.xml --no-progress --output-format=github 2>&1 || {
|
|
echo "### Psalm" >> $GITHUB_STEP_SUMMARY
|
|
echo "Psalm found issues (advisory — not blocking)." >> $GITHUB_STEP_SUMMARY
|
|
}
|
|
fi
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# Gate 2 — Unit Tests
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
tests:
|
|
name: "Gate 2: Unit Tests"
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
needs: code-quality
|
|
|
|
strategy:
|
|
matrix:
|
|
php: ['8.1', '8.2', '8.3']
|
|
fail-fast: false
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup PHP ${{ matrix.php }}
|
|
run: |
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq php${{ matrix.php }}-cli php${{ matrix.php }}-mbstring \
|
|
php${{ matrix.php }}-xml php${{ matrix.php }}-curl php${{ matrix.php }}-zip \
|
|
php${{ matrix.php }}-intl >/dev/null 2>&1
|
|
php -v
|
|
|
|
- name: Install dependencies
|
|
run: composer install --no-interaction --prefer-dist
|
|
|
|
- name: "PHPUnit (PHP ${{ matrix.php }})"
|
|
run: |
|
|
vendor/bin/phpunit --testdox 2>&1
|
|
{
|
|
echo "### PHPUnit (PHP ${{ matrix.php }})"
|
|
echo "All tests passed."
|
|
} >> $GITHUB_STEP_SUMMARY
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# Gate 3 — Self-Health (Dogfood)
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
self-health:
|
|
name: "Gate 3: Self-Health Check"
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
needs: code-quality
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup PHP
|
|
run: |
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq php${{ env.PHP_VERSION }}-cli php${{ env.PHP_VERSION }}-mbstring \
|
|
php${{ env.PHP_VERSION }}-xml php${{ env.PHP_VERSION }}-curl php${{ env.PHP_VERSION }}-zip >/dev/null 2>&1
|
|
|
|
- name: Install dependencies
|
|
run: composer install --no-interaction --prefer-dist
|
|
|
|
- name: "Run bin/moko health against self"
|
|
run: |
|
|
php bin/moko health -- --path . --json > /tmp/health-report.json 2>&1 || true
|
|
SCORE=$(cat /tmp/health-report.json | python3 -c "import sys,json; print(json.load(sys.stdin).get('percentage', 0))" 2>/dev/null || echo "0")
|
|
LEVEL=$(cat /tmp/health-report.json | python3 -c "import sys,json; print(json.load(sys.stdin).get('level', 'unknown'))" 2>/dev/null || echo "unknown")
|
|
|
|
{
|
|
echo "### Self-Health Report"
|
|
echo ""
|
|
echo "| Metric | Value |"
|
|
echo "|---|---|"
|
|
echo "| Score | ${SCORE}% |"
|
|
echo "| Level | ${LEVEL} |"
|
|
echo ""
|
|
echo "The platform must pass its own health check to enforce it on others."
|
|
} >> $GITHUB_STEP_SUMMARY
|
|
|
|
# Platform must score at least 80%
|
|
python3 -c "exit(0 if float('${SCORE}') >= 80.0 else 1)" || {
|
|
echo "::error::Self-health score ${SCORE}% is below 80% threshold"
|
|
exit 1
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# Gate 4 — Governance Checks
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
governance:
|
|
name: "Gate 4: Governance"
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
needs: code-quality
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup PHP
|
|
run: |
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq php${{ env.PHP_VERSION }}-cli php${{ env.PHP_VERSION }}-mbstring \
|
|
php${{ env.PHP_VERSION }}-xml php${{ env.PHP_VERSION }}-curl >/dev/null 2>&1
|
|
|
|
- name: Install dependencies
|
|
run: composer install --no-interaction --prefer-dist
|
|
|
|
- name: "License headers (SPDX)"
|
|
run: |
|
|
MISSING=0
|
|
CHECKED=0
|
|
while IFS= read -r -d '' file; do
|
|
CHECKED=$((CHECKED + 1))
|
|
if ! head -n 20 "$file" | grep -q "SPDX-License-Identifier:"; then
|
|
echo "::warning file=${file}::Missing SPDX header"
|
|
MISSING=$((MISSING + 1))
|
|
fi
|
|
done < <(find lib/ validate/ cli/ src/ automation/ deploy/ -name "*.php" -print0 2>/dev/null)
|
|
|
|
{
|
|
echo "### License Headers"
|
|
echo "Checked ${CHECKED} files — ${MISSING} missing SPDX headers"
|
|
} >> $GITHUB_STEP_SUMMARY
|
|
|
|
# Advisory — warn but don't fail (yet)
|
|
[ "$MISSING" -eq 0 ] || echo "::warning::${MISSING} files missing SPDX license headers"
|
|
|
|
- name: "Secret detection"
|
|
run: |
|
|
FOUND=0
|
|
# Check for common secret patterns in source files
|
|
while IFS= read -r -d '' file; do
|
|
if grep -qEi '(password|secret|token|apikey|api_key)\s*[:=]\s*["\x27][^\s]{8,}' "$file" 2>/dev/null; then
|
|
echo "::error file=${file}::Potential hardcoded secret detected"
|
|
FOUND=$((FOUND + 1))
|
|
fi
|
|
done < <(find lib/ validate/ cli/ src/ automation/ deploy/ -name "*.php" -print0 2>/dev/null)
|
|
|
|
{
|
|
echo "### Secret Detection"
|
|
if [ "$FOUND" -eq 0 ]; then
|
|
echo "No hardcoded secrets detected."
|
|
else
|
|
echo "${FOUND} potential secrets found."
|
|
fi
|
|
} >> $GITHUB_STEP_SUMMARY
|
|
|
|
[ "$FOUND" -eq 0 ] || exit 1
|
|
|
|
- name: "Version consistency"
|
|
run: |
|
|
# Extract version from composer.json
|
|
COMPOSER_VER=$(python3 -c "import json; print(json.load(open('composer.json'))['version'])")
|
|
# Extract version from README.md
|
|
README_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
|
|
|
|
{
|
|
echo "### Version Consistency"
|
|
echo "| Source | Version |"
|
|
echo "|---|---|"
|
|
echo "| composer.json | ${COMPOSER_VER} |"
|
|
echo "| README.md | ${README_VER:-not found} |"
|
|
} >> $GITHUB_STEP_SUMMARY
|
|
|
|
if [ -n "$README_VER" ] && [ "$COMPOSER_VER" != "$README_VER" ]; then
|
|
echo "::warning::Version mismatch: composer.json=${COMPOSER_VER} vs README.md=${README_VER}"
|
|
fi
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# Gate 5 — Template Integrity
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
templates:
|
|
name: "Gate 5: Template Integrity"
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
needs: code-quality
|
|
if: github.event_name != 'push' || github.event.inputs.full_suite != 'false'
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: "Validate workflow templates"
|
|
run: |
|
|
ERRORS=0
|
|
CHECKED=0
|
|
|
|
# Check all YAML workflow templates parse cleanly
|
|
while IFS= read -r -d '' file; do
|
|
CHECKED=$((CHECKED + 1))
|
|
if ! python3 -c "import yaml; yaml.safe_load(open('${file}'))" 2>/dev/null; then
|
|
echo "::error file=${file}::Invalid YAML"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
done < <(find templates/workflows/ -name "*.yml" -o -name "*.yaml" 2>/dev/null | tr '\n' '\0')
|
|
|
|
# Also check the live workflows
|
|
while IFS= read -r -d '' file; do
|
|
CHECKED=$((CHECKED + 1))
|
|
if ! python3 -c "import yaml; yaml.safe_load(open('${file}'))" 2>/dev/null; then
|
|
echo "::error file=${file}::Invalid YAML"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
done < <(find .gitea/workflows/ -name "*.yml" -o -name "*.yaml" 2>/dev/null | tr '\n' '\0')
|
|
|
|
{
|
|
echo "### Template Integrity"
|
|
echo "Validated ${CHECKED} YAML files — ${ERRORS} parse errors"
|
|
} >> $GITHUB_STEP_SUMMARY
|
|
|
|
[ "$ERRORS" -eq 0 ] || exit 1
|
|
|
|
- name: "Validate gitignore templates"
|
|
run: |
|
|
TEMPLATES=0
|
|
for GI in templates/configs/gitignore templates/configs/gitignore.dolibarr templates/configs/.gitignore.joomla; do
|
|
if [ -f "$GI" ]; then
|
|
TEMPLATES=$((TEMPLATES + 1))
|
|
# Verify required entries
|
|
for REQUIRED in ".claude/" "TODO.md" "*.min.css" "*.min.js" "wiki/"; do
|
|
if ! grep -q "$REQUIRED" "$GI"; then
|
|
echo "::error file=${GI}::Missing required entry: ${REQUIRED}"
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
|
|
echo "### Gitignore Templates" >> $GITHUB_STEP_SUMMARY
|
|
echo "Validated ${TEMPLATES} gitignore templates." >> $GITHUB_STEP_SUMMARY
|
|
|
|
- name: "Validate PHP validation scripts"
|
|
run: |
|
|
ERRORS=0
|
|
CHECKED=0
|
|
while IFS= read -r -d '' file; do
|
|
CHECKED=$((CHECKED + 1))
|
|
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
|
echo "::error file=${file}::Validation script has syntax error"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
done < <(find validate/ -name "*.php" -print0 2>/dev/null)
|
|
|
|
{
|
|
echo "### Validation Scripts"
|
|
echo "Checked ${CHECKED} scripts — ${ERRORS} syntax errors"
|
|
} >> $GITHUB_STEP_SUMMARY
|
|
|
|
[ "$ERRORS" -eq 0 ] || { echo "::error::Validation scripts must be error-free"; exit 1; }
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# Summary
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
summary:
|
|
name: "CI Summary"
|
|
runs-on: ubuntu-latest
|
|
needs: [code-quality, tests, self-health, governance, templates]
|
|
if: always()
|
|
|
|
steps:
|
|
- name: Check gate results
|
|
run: |
|
|
{
|
|
echo "# MokoStandards Platform CI"
|
|
echo ""
|
|
echo "| Gate | Job | Status |"
|
|
echo "|---|---|---|"
|
|
echo "| 1 | Code Quality | ${{ needs.code-quality.result }} |"
|
|
echo "| 2 | Unit Tests | ${{ needs.tests.result }} |"
|
|
echo "| 3 | Self-Health | ${{ needs.self-health.result }} |"
|
|
echo "| 4 | Governance | ${{ needs.governance.result }} |"
|
|
echo "| 5 | Templates | ${{ needs.templates.result }} |"
|
|
echo ""
|
|
echo "> *The standards engine must pass its own standards.*"
|
|
} >> $GITHUB_STEP_SUMMARY
|
|
|
|
# Fail if any required gate failed
|
|
if [ "${{ needs.code-quality.result }}" = "failure" ] || \
|
|
[ "${{ needs.tests.result }}" = "failure" ] || \
|
|
[ "${{ needs.self-health.result }}" = "failure" ] || \
|
|
[ "${{ needs.governance.result }}" = "failure" ] || \
|
|
[ "${{ needs.templates.result }}" = "failure" ]; then
|
|
echo "::error::One or more CI gates failed"
|
|
exit 1
|
|
fi
|