Skip to content

Commit a5082ae

Browse files
fix(ci): restore dogfood-gate.yml content (prior sweep commit truncated the file) (#9)
The earlier standards#85 pin commit accidentally wrote a one-line placeholder instead of the workflow body. This restores the full dogfood-gate.yml with a2ml-validate-action @59145c7 and k9-validate-action @2d96f43 correctly pinned. Refs hyperpolymath/standards#85 Generated with Claude Code
1 parent 9558e6e commit a5082ae

1 file changed

Lines changed: 380 additions & 1 deletion

File tree

.github/workflows/dogfood-gate.yml

Lines changed: 380 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,380 @@
1-
@/tmp/n_a2mlhaskell.yml
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# dogfood-gate.yml — Hyperpolymath Dogfooding Quality Gate
5+
# Validates that the repo uses hyperpolymath's own formats and tools.
6+
# Companion to static-analysis-gate.yml (security) — this is for format compliance.
7+
name: Dogfood Gate
8+
9+
on:
10+
pull_request:
11+
branches: ['**']
12+
push:
13+
branches: [main, master]
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
# ---------------------------------------------------------------------------
20+
# Job 1: A2ML manifest validation
21+
# ---------------------------------------------------------------------------
22+
a2ml-validate:
23+
name: Validate A2ML manifests
24+
runs-on: ubuntu-latest
25+
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
29+
30+
- name: Check for A2ML files
31+
id: detect
32+
run: |
33+
COUNT=$(find . -name '*.a2ml' -not -path './.git/*' | wc -l)
34+
echo "count=$COUNT" >> "$GITHUB_OUTPUT"
35+
if [ "$COUNT" -eq 0 ]; then
36+
echo "::warning::No .a2ml manifest files found. Every RSR repo should have 0-AI-MANIFEST.a2ml"
37+
fi
38+
39+
- name: Validate A2ML manifests
40+
if: steps.detect.outputs.count > 0
41+
uses: hyperpolymath/a2ml-validate-action@59145c7d1039fa3059b3ecacdb50ee23d7505898 # main
42+
with:
43+
path: '.'
44+
strict: 'false'
45+
46+
- name: Write summary
47+
run: |
48+
A2ML_COUNT="${{ steps.detect.outputs.count }}"
49+
if [ "$A2ML_COUNT" -eq 0 ]; then
50+
cat <<'EOF' >> "$GITHUB_STEP_SUMMARY"
51+
## A2ML Validation
52+
53+
:warning: **No .a2ml files found.** Every RSR-compliant repo should have at least `0-AI-MANIFEST.a2ml`.
54+
55+
Create one with: `a2mliser init` or copy from [rsr-template-repo](https://github.com/hyperpolymath/rsr-template-repo).
56+
EOF
57+
else
58+
echo "## A2ML Validation" >> "$GITHUB_STEP_SUMMARY"
59+
echo "" >> "$GITHUB_STEP_SUMMARY"
60+
echo "Scanned **${A2ML_COUNT}** .a2ml file(s). See step output for details." >> "$GITHUB_STEP_SUMMARY"
61+
fi
62+
63+
# ---------------------------------------------------------------------------
64+
# Job 2: K9 contract validation
65+
# ---------------------------------------------------------------------------
66+
k9-validate:
67+
name: Validate K9 contracts
68+
runs-on: ubuntu-latest
69+
70+
steps:
71+
- name: Checkout repository
72+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
73+
74+
- name: Check for K9 files
75+
id: detect
76+
run: |
77+
COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l)
78+
CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \
79+
-not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \
80+
-not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l)
81+
echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT"
82+
echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT"
83+
if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then
84+
echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts."
85+
fi
86+
87+
- name: Validate K9 contracts
88+
if: steps.detect.outputs.k9_count > 0
89+
uses: hyperpolymath/k9-validate-action@2d96f43c538964b097d159ed3a56ba5b5ceca227 # main
90+
with:
91+
path: '.'
92+
strict: 'false'
93+
94+
- name: Write summary
95+
run: |
96+
K9_COUNT="${{ steps.detect.outputs.k9_count }}"
97+
CFG_COUNT="${{ steps.detect.outputs.config_count }}"
98+
if [ "$K9_COUNT" -eq 0 ]; then
99+
cat <<'EOF' >> "$GITHUB_STEP_SUMMARY"
100+
## K9 Contract Validation
101+
102+
:warning: **No K9 contract files found.** Repos with configuration files should have K9 contracts.
103+
104+
Generate contracts with: `k9iser generate .`
105+
EOF
106+
else
107+
echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY"
108+
echo "" >> "$GITHUB_STEP_SUMMARY"
109+
echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY"
110+
fi
111+
112+
# ---------------------------------------------------------------------------
113+
# Job 3: Empty-linter — invisible character detection
114+
# ---------------------------------------------------------------------------
115+
empty-lint:
116+
name: Empty-linter (invisible characters)
117+
runs-on: ubuntu-latest
118+
119+
steps:
120+
- name: Checkout repository
121+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
122+
123+
- name: Scan for invisible characters
124+
id: lint
125+
run: |
126+
# Inline invisible character detection (from empty-linter's core patterns).
127+
# Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens,
128+
# non-breaking spaces, null bytes, and other invisible Unicode in source files.
129+
set +e
130+
PATTERNS='\xc2\xa0|\xe2\x80\x8b|\xe2\x80\x8c|\xe2\x80\x8d|\xef\xbb\xbf|\xc2\xad|\xe2\x80\x8e|\xe2\x80\x8f|\xe2\x80\xaa|\xe2\x80\xab|\xe2\x80\xac|\xe2\x80\xad|\xe2\x80\xae|\x00'
131+
find "$GITHUB_WORKSPACE" \
132+
-not -path '*/.git/*' -not -path '*/node_modules/*' \
133+
-not -path '*/.deno/*' -not -path '*/target/*' \
134+
-not -path '*/_build/*' -not -path '*/deps/*' \
135+
-not -path '*/external_corpora/*' -not -path '*/.lake/*' \
136+
-type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \
137+
-o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \
138+
-o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \
139+
-o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \
140+
-o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \
141+
-exec grep -Prl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null
142+
EL_EXIT=$?
143+
set -e
144+
145+
FINDINGS=$(wc -l < /tmp/empty-lint-results.txt 2>/dev/null || echo 0)
146+
echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT"
147+
echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT"
148+
echo "ready=true" >> "$GITHUB_OUTPUT"
149+
150+
# Emit annotations for each file with invisible chars
151+
while IFS= read -r filepath; do
152+
[ -z "$filepath" ] && continue
153+
REL_PATH="${filepath#$GITHUB_WORKSPACE/}"
154+
echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)"
155+
done < /tmp/empty-lint-results.txt
156+
157+
- name: Write summary
158+
run: |
159+
if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then
160+
FINDINGS="${{ steps.lint.outputs.findings }}"
161+
if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then
162+
echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY"
163+
echo "" >> "$GITHUB_STEP_SUMMARY"
164+
echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY"
165+
else
166+
echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY"
167+
echo "" >> "$GITHUB_STEP_SUMMARY"
168+
echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY"
169+
fi
170+
else
171+
echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY"
172+
echo "" >> "$GITHUB_STEP_SUMMARY"
173+
echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY"
174+
fi
175+
176+
# ---------------------------------------------------------------------------
177+
# Job 4: Groove manifest check (for repos that should expose services)
178+
# ---------------------------------------------------------------------------
179+
groove-check:
180+
name: Groove manifest check
181+
runs-on: ubuntu-latest
182+
183+
steps:
184+
- name: Checkout repository
185+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
186+
187+
- name: Check for Groove manifest
188+
id: groove
189+
run: |
190+
# Check for static or dynamic Groove endpoints
191+
HAS_MANIFEST="false"
192+
HAS_GROOVE_CODE="false"
193+
194+
if [ -f ".well-known/groove/manifest.json" ]; then
195+
HAS_MANIFEST="true"
196+
# Validate the manifest JSON
197+
if ! jq empty .well-known/groove/manifest.json 2>/dev/null; then
198+
echo "::error file=.well-known/groove/manifest.json::Invalid JSON in Groove manifest"
199+
else
200+
SVC_ID=$(jq -r '.service_id // "unknown"' .well-known/groove/manifest.json)
201+
echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT"
202+
fi
203+
fi
204+
205+
# Check for Groove endpoint code (Rust, Elixir, Zig, V)
206+
if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then
207+
HAS_GROOVE_CODE="true"
208+
fi
209+
210+
# Check if this repo likely serves HTTP (has server/listener code)
211+
HAS_SERVER="false"
212+
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
213+
HAS_SERVER="true"
214+
fi
215+
216+
echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT"
217+
echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT"
218+
echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT"
219+
220+
if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then
221+
echo "::warning::This repo has server code but no Groove endpoint. Add .well-known/groove/manifest.json for service discovery."
222+
fi
223+
224+
- name: Write summary
225+
run: |
226+
echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY"
227+
echo "" >> "$GITHUB_STEP_SUMMARY"
228+
echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY"
229+
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
230+
echo "| Static manifest (.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY"
231+
echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY"
232+
echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY"
233+
234+
# ---------------------------------------------------------------------------
235+
# Job 5: eclexiaiser manifest validation
236+
# ---------------------------------------------------------------------------
237+
eclexiaiser-validate:
238+
name: Validate eclexiaiser manifest
239+
runs-on: ubuntu-latest
240+
241+
steps:
242+
- name: Checkout repository
243+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
244+
245+
- name: Check and validate eclexiaiser manifest
246+
id: eclex
247+
run: |
248+
if [ ! -f "eclexiaiser.toml" ]; then
249+
# Check if repo has a Containerfile — if so, recommend eclexiaiser
250+
if [ -f "Containerfile" ]; then
251+
echo "::warning::Containerfile present but no eclexiaiser.toml. Run \`eclexiaiser init\` to scaffold energy/carbon budgets."
252+
fi
253+
echo "has_manifest=false" >> "$GITHUB_OUTPUT"
254+
exit 0
255+
fi
256+
257+
echo "has_manifest=true" >> "$GITHUB_OUTPUT"
258+
259+
# Validate TOML structure using Python 3.11+ tomllib
260+
python3 -c "
261+
import tomllib, sys
262+
with open('eclexiaiser.toml', 'rb') as f:
263+
data = tomllib.load(f)
264+
project = data.get('project', {})
265+
if not project.get('name', '').strip():
266+
print('ERROR: project.name is required', file=sys.stderr)
267+
sys.exit(1)
268+
functions = data.get('functions', [])
269+
if not functions:
270+
print('ERROR: at least one [[functions]] entry is required', file=sys.stderr)
271+
sys.exit(1)
272+
for fn in functions:
273+
if not fn.get('name', '').strip():
274+
print('ERROR: function name cannot be empty', file=sys.stderr)
275+
sys.exit(1)
276+
if not fn.get('source', '').strip():
277+
print(f'ERROR: function {fn[\"name\"]} has no source path', file=sys.stderr)
278+
sys.exit(1)
279+
print(f'Valid: {project[\"name\"]} ({len(functions)} function(s))')
280+
" || {
281+
echo "::error file=eclexiaiser.toml::Invalid eclexiaiser.toml — see step output for details"
282+
exit 1
283+
}
284+
285+
- name: Write summary
286+
run: |
287+
if [ "${{ steps.eclex.outputs.has_manifest }}" = "true" ]; then
288+
echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY"
289+
echo "" >> "$GITHUB_STEP_SUMMARY"
290+
echo ":white_check_mark: **eclexiaiser.toml** present and valid." >> "$GITHUB_STEP_SUMMARY"
291+
else
292+
echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY"
293+
echo "" >> "$GITHUB_STEP_SUMMARY"
294+
echo ":ballot_box_with_check: No eclexiaiser.toml. Add one with \`eclexiaiser init\` for energy/carbon tracking." >> "$GITHUB_STEP_SUMMARY"
295+
fi
296+
297+
# ---------------------------------------------------------------------------
298+
# Job 6: Dogfooding summary
299+
# ---------------------------------------------------------------------------
300+
dogfood-summary:
301+
name: Dogfooding compliance summary
302+
runs-on: ubuntu-latest
303+
needs: [a2ml-validate, k9-validate, empty-lint, groove-check, eclexiaiser-validate]
304+
if: always()
305+
306+
steps:
307+
- name: Checkout repository
308+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
309+
310+
- name: Generate dogfooding scorecard
311+
run: |
312+
SCORE=0
313+
MAX=6
314+
315+
# A2ML manifest present?
316+
if find . -name '*.a2ml' -not -path './.git/*' | head -1 | grep -q .; then
317+
SCORE=$((SCORE + 1))
318+
A2ML_STATUS=":white_check_mark:"
319+
else
320+
A2ML_STATUS=":x:"
321+
fi
322+
323+
# K9 contracts present?
324+
if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then
325+
SCORE=$((SCORE + 1))
326+
K9_STATUS=":white_check_mark:"
327+
else
328+
K9_STATUS=":x:"
329+
fi
330+
331+
# .editorconfig present?
332+
if [ -f ".editorconfig" ]; then
333+
SCORE=$((SCORE + 1))
334+
EC_STATUS=":white_check_mark:"
335+
else
336+
EC_STATUS=":x:"
337+
fi
338+
339+
# Groove manifest or code?
340+
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
341+
SCORE=$((SCORE + 1))
342+
GROOVE_STATUS=":white_check_mark:"
343+
else
344+
GROOVE_STATUS=":ballot_box_with_check:"
345+
fi
346+
347+
# VeriSimDB integration?
348+
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
349+
SCORE=$((SCORE + 1))
350+
VSDB_STATUS=":white_check_mark:"
351+
else
352+
VSDB_STATUS=":ballot_box_with_check:"
353+
fi
354+
355+
# eclexiaiser energy tracking?
356+
if [ -f "eclexiaiser.toml" ]; then
357+
SCORE=$((SCORE + 1))
358+
ECLEX_STATUS=":white_check_mark:"
359+
else
360+
ECLEX_STATUS=":ballot_box_with_check:"
361+
fi
362+
363+
cat <<EOF >> "$GITHUB_STEP_SUMMARY"
364+
## Dogfooding Scorecard
365+
366+
**Score: ${SCORE}/${MAX}**
367+
368+
| Tool/Format | Status | Notes |
369+
|-------------|--------|-------|
370+
| A2ML manifest (0-AI-MANIFEST.a2ml) | ${A2ML_STATUS} | Required for all RSR repos |
371+
| K9 contracts | ${K9_STATUS} | Required for repos with config files |
372+
| .editorconfig | ${EC_STATUS} | Required for all repos |
373+
| Groove endpoint | ${GROOVE_STATUS} | Required for service repos |
374+
| VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos |
375+
| eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services |
376+
377+
---
378+
*Generated by the [Dogfood Gate](https://github.com/hyperpolymath/rsr-template-repo) workflow.*
379+
*Dogfooding is guinea pig fooding — we test our tools on ourselves.*
380+
EOF

0 commit comments

Comments
 (0)