# SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # dogfood-gate.yml — Hyperpolymath Dogfooding Quality Gate # Validates that the repo uses hyperpolymath's own formats and tools. # Companion to static-analysis-gate.yml (security) — this is for format compliance. name: Dogfood Gate on: pull_request: branches: ['**'] push: branches: [main, master] permissions: contents: read jobs: # --------------------------------------------------------------------------- # Job 1: A2ML manifest validation # --------------------------------------------------------------------------- a2ml-validate: name: Validate DEED manifests runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 - name: Check for A2ML files id: detect run: | COUNT=$(find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | wc -l) echo "count=$COUNT" >> "$GITHUB_OUTPUT" if [ "$COUNT" -eq 0 ]; then echo "::warning::No .a2ml/.deed manifest files found. Every RSR repo should have a repo deed (_chora.deed); legacy 0-AI-MANIFEST.a2ml accepted mid-migration — standards #837" fi - name: Validate A2ML manifests if: steps.detect.outputs.count > 0 run: bash .github/hooks/validate-a2ml.sh - name: Write summary run: | A2ML_COUNT="${{ steps.detect.outputs.count }}" if [ "$A2ML_COUNT" -eq 0 ]; then cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" ## A2ML Validation :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. Copy it from [rsr-template-repo](https://github.com/hyperpolymath/rsr-template-repo). EOF else echo "## A2ML Validation" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "Scanned **${A2ML_COUNT}** manifest file(s) (.deed, or legacy .a2ml). See step output for details." >> "$GITHUB_STEP_SUMMARY" fi # --------------------------------------------------------------------------- # Job 2: K9 contract validation # --------------------------------------------------------------------------- k9-validate: name: Validate K9 contracts runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 - name: Check for K9 files id: detect run: | COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT" echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT" if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts." fi - name: Validate K9 contracts if: steps.detect.outputs.k9_count > 0 run: bash .github/hooks/validate-k9.sh - name: Write summary run: | K9_COUNT="${{ steps.detect.outputs.k9_count }}" CFG_COUNT="${{ steps.detect.outputs.config_count }}" if [ "$K9_COUNT" -eq 0 ]; then cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" ## K9 Contract Validation :warning: **No K9 contract files found.** Generate contracts with `k9iser generate .`. Generate contracts with: `k9iser generate .` EOF else echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY" fi # --------------------------------------------------------------------------- # Job 3: Empty-linter — invisible character detection # --------------------------------------------------------------------------- empty-lint: name: Empty-linter (invisible characters) runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 - name: Scan for invisible characters id: lint run: | # Inline invisible character detection (from empty-linter's core patterns). # Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens, # non-breaking spaces, null bytes, and other invisible Unicode in source files. set +e PATTERNS='(*UTF)[\x00-\x08\x0B\x0C\x0E-\x1F\x{a0}\x{ad}\x{200b}-\x{200f}\x{202a}-\x{202f}\x{2060}\x{2066}-\x{2069}\x{feff}]' find "$GITHUB_WORKSPACE" \ -not -path '*/.git/*' -not -path '*/node_modules/*' \ -not -path '*/.deno/*' -not -path '*/target/*' \ -not -path '*/_build/*' -not -path '*/deps/*' \ -not -path '*/external_corpora/*' -not -path '*/.lake/*' \ -type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \ -o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \ -o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \ -o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \ -o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \ -exec grep -aPrl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null EL_EXIT=$? set -e FINDINGS=$(wc -l < /tmp/empty-lint-results.txt 2>/dev/null || echo 0) echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT" echo "ready=true" >> "$GITHUB_OUTPUT" # Emit annotations for each file with invisible chars while IFS= read -r filepath; do [ -z "$filepath" ] && continue REL_PATH="${filepath#$GITHUB_WORKSPACE/}" echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)" done < /tmp/empty-lint-results.txt - name: Write summary run: | if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then FINDINGS="${{ steps.lint.outputs.findings }}" if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY" else echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY" fi else echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY" fi # --------------------------------------------------------------------------- # Job 4: Groove manifest check (for repos that should expose services) # --------------------------------------------------------------------------- groove-check: name: Groove manifest check runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 - name: Check for Groove manifest id: groove run: | # Check for static or dynamic Groove endpoints HAS_MANIFEST="false" HAS_GROOVE_CODE="false" if [ -f ".well-known/groove/manifest.json" ]; then HAS_MANIFEST="true" # Validate the manifest JSON if ! jq empty .well-known/groove/manifest.json 2>/dev/null; then echo "::error file=.well-known/groove/manifest.json::Invalid JSON in Groove manifest" else SVC_ID=$(jq -r '.service_id // "unknown"' .well-known/groove/manifest.json) echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT" fi fi # Check for Groove endpoint code (Rust, Elixir, Zig, V) if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then HAS_GROOVE_CODE="true" fi # Check if this repo likely serves HTTP (has server/listener code) HAS_SERVER="false" if grep -rl 'TcpListener\|Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then HAS_SERVER="true" fi echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT" echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT" echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT" if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then echo "::warning::This repo has server code but no Groove endpoint. Add .well-known/groove/manifest.json for service discovery." fi - name: Write summary run: | echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" echo "| Static manifest (.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY" echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY" # --------------------------------------------------------------------------- # Job 5: eclexiaiser manifest validation # --------------------------------------------------------------------------- eclexiaiser-validate: name: Validate eclexiaiser manifest runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 - name: Check and validate eclexiaiser manifest id: eclex run: | if [ ! -f "eclexiaiser.toml" ]; then # Check if repo has a Containerfile — if so, recommend eclexiaiser if [ -f "Containerfile" ]; then echo "::warning::Containerfile present but no eclexiaiser.toml. Run \`eclexiaiser init\` to scaffold energy/carbon budgets." fi echo "has_manifest=false" >> "$GITHUB_OUTPUT" exit 0 fi echo "has_manifest=true" >> "$GITHUB_OUTPUT" # Validate TOML structure using Python 3.11+ tomllib python3 -c " import tomllib, sys with open('eclexiaiser.toml', 'rb') as f: data = tomllib.load(f) project = data.get('project', {}) if not project.get('name', '').strip(): print('ERROR: project.name is required', file=sys.stderr) sys.exit(1) functions = data.get('functions', []) if not functions: print('ERROR: at least one [[functions]] entry is required', file=sys.stderr) sys.exit(1) for fn in functions: if not fn.get('name', '').strip(): print('ERROR: function name cannot be empty', file=sys.stderr) sys.exit(1) if not fn.get('source', '').strip(): print(f'ERROR: function {fn[\"name\"]} has no source path', file=sys.stderr) sys.exit(1) print(f'Valid: {project[\"name\"]} ({len(functions)} function(s))') " || { echo "::error file=eclexiaiser.toml::Invalid eclexiaiser.toml — see step output for details" exit 1 } - name: Write summary run: | if [ "${{ steps.eclex.outputs.has_manifest }}" = "true" ]; then echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo ":white_check_mark: **eclexiaiser.toml** present and valid." >> "$GITHUB_STEP_SUMMARY" else echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo ":ballot_box_with_check: No eclexiaiser.toml. Add one with \`eclexiaiser init\` for energy/carbon tracking." >> "$GITHUB_STEP_SUMMARY" fi # --------------------------------------------------------------------------- # Job 6: Dogfooding summary # --------------------------------------------------------------------------- dogfood-summary: name: Dogfooding compliance summary runs-on: ubuntu-latest needs: [a2ml-validate, k9-validate, empty-lint, groove-check, eclexiaiser-validate] if: always() steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 - name: Generate dogfooding scorecard run: | SCORE=0 MAX=6 # A2ML manifest present? if find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | head -1 | grep -q .; then SCORE=$((SCORE + 1)) A2ML_STATUS=":white_check_mark:" else A2ML_STATUS=":x:" fi # K9 contracts present? if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then SCORE=$((SCORE + 1)) K9_STATUS=":white_check_mark:" else K9_STATUS=":x:" fi # .editorconfig present? if [ -f ".editorconfig" ]; then SCORE=$((SCORE + 1)) EC_STATUS=":white_check_mark:" else EC_STATUS=":x:" fi # Groove manifest or code? if [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then SCORE=$((SCORE + 1)) GROOVE_STATUS=":white_check_mark:" else GROOVE_STATUS=":ballot_box_with_check:" fi # VeriSimDB integration? if grep -rl 'verisimdb\|VeriSimDB' --include='*.toml' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.rs' --include='*.ex' . 2>/dev/null | head -1 | grep -q .; then SCORE=$((SCORE + 1)) VSDB_STATUS=":white_check_mark:" else VSDB_STATUS=":ballot_box_with_check:" fi # eclexiaiser energy tracking? if [ -f "eclexiaiser.toml" ]; then SCORE=$((SCORE + 1)) ECLEX_STATUS=":white_check_mark:" else ECLEX_STATUS=":ballot_box_with_check:" fi cat <> "$GITHUB_STEP_SUMMARY" ## Dogfooding Scorecard **Score: ${SCORE}/${MAX}** | Tool/Format | Status | Notes | |-------------|--------|-------| | DEED repo deed (`_chora.deed`) | ${A2ML_STATUS} | Required for all RSR repos | | K9 contracts | ${K9_STATUS} | Required for repos with config files | | .editorconfig | ${EC_STATUS} | Required for all repos | | Groove endpoint | ${GROOVE_STATUS} | Required for service repos | | VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos | | eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services | --- *Generated by the [Dogfood Gate](https://github.com/hyperpolymath/rsr-template-repo) workflow.* *Dogfooding is guinea pig fooding — we test our tools on ourselves.* EOF