Skip to content

Commit 6cbf5c5

Browse files
hyperpolymathclaude
andcommitted
ci: add dogfood-gate.yml — validates hyperpolymath tool usage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 23b4786 commit 6cbf5c5

1 file changed

Lines changed: 309 additions & 0 deletions

File tree

.github/workflows/dogfood-gate.yml

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
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@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@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: Setup Deno
124+
uses: denoland/setup-deno@v2
125+
with:
126+
deno-version: v2.x
127+
128+
- name: Clone and run empty-linter
129+
id: lint
130+
run: |
131+
git clone --depth 1 https://github.com/hyperpolymath/empty-linter.git "$HOME/empty-linter" 2>/dev/null || true
132+
if [ -f "$HOME/empty-linter/deno.json" ]; then
133+
cd "$HOME/empty-linter"
134+
# Build ReScript output if needed
135+
if [ -f "rescript.json" ]; then
136+
deno task build 2>/dev/null || true
137+
fi
138+
set +e
139+
deno run --allow-read src/cli/Main.res.js "$GITHUB_WORKSPACE" > /tmp/empty-lint-results.txt 2>&1
140+
EL_EXIT=$?
141+
set -e
142+
FINDINGS=$(wc -l < /tmp/empty-lint-results.txt)
143+
echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT"
144+
echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT"
145+
echo "ready=true" >> "$GITHUB_OUTPUT"
146+
147+
# Emit annotations
148+
while IFS= read -r line; do
149+
if echo "$line" | grep -q ':'; then
150+
echo "::warning::$line"
151+
fi
152+
done < /tmp/empty-lint-results.txt
153+
else
154+
echo "::notice::empty-linter not available — skipping"
155+
echo "ready=false" >> "$GITHUB_OUTPUT"
156+
fi
157+
158+
- name: Write summary
159+
run: |
160+
if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then
161+
FINDINGS="${{ steps.lint.outputs.findings }}"
162+
if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then
163+
echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY"
164+
echo "" >> "$GITHUB_STEP_SUMMARY"
165+
echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY"
166+
else
167+
echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY"
168+
echo "" >> "$GITHUB_STEP_SUMMARY"
169+
echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY"
170+
fi
171+
else
172+
echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY"
173+
echo "" >> "$GITHUB_STEP_SUMMARY"
174+
echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY"
175+
fi
176+
177+
# ---------------------------------------------------------------------------
178+
# Job 4: Groove manifest check (for repos that should expose services)
179+
# ---------------------------------------------------------------------------
180+
groove-check:
181+
name: Groove manifest check
182+
runs-on: ubuntu-latest
183+
184+
steps:
185+
- name: Checkout repository
186+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
187+
188+
- name: Check for Groove manifest
189+
id: groove
190+
run: |
191+
# Check for static or dynamic Groove endpoints
192+
HAS_MANIFEST="false"
193+
HAS_GROOVE_CODE="false"
194+
195+
if [ -f ".well-known/groove/manifest.json" ]; then
196+
HAS_MANIFEST="true"
197+
# Validate the manifest JSON
198+
if ! jq empty .well-known/groove/manifest.json 2>/dev/null; then
199+
echo "::error file=.well-known/groove/manifest.json::Invalid JSON in Groove manifest"
200+
else
201+
SVC_ID=$(jq -r '.service_id // "unknown"' .well-known/groove/manifest.json)
202+
echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT"
203+
fi
204+
fi
205+
206+
# Check for Groove endpoint code (Rust, Elixir, Zig, V)
207+
if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then
208+
HAS_GROOVE_CODE="true"
209+
fi
210+
211+
# Check if this repo likely serves HTTP (has server/listener code)
212+
HAS_SERVER="false"
213+
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
214+
HAS_SERVER="true"
215+
fi
216+
217+
echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT"
218+
echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT"
219+
echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT"
220+
221+
if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then
222+
echo "::warning::This repo has server code but no Groove endpoint. Add .well-known/groove/manifest.json for service discovery."
223+
fi
224+
225+
- name: Write summary
226+
run: |
227+
echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY"
228+
echo "" >> "$GITHUB_STEP_SUMMARY"
229+
echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY"
230+
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
231+
echo "| Static manifest (.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY"
232+
echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY"
233+
echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY"
234+
235+
# ---------------------------------------------------------------------------
236+
# Job 5: Dogfooding summary
237+
# ---------------------------------------------------------------------------
238+
dogfood-summary:
239+
name: Dogfooding compliance summary
240+
runs-on: ubuntu-latest
241+
needs: [a2ml-validate, k9-validate, empty-lint, groove-check]
242+
if: always()
243+
244+
steps:
245+
- name: Checkout repository
246+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
247+
248+
- name: Generate dogfooding scorecard
249+
run: |
250+
SCORE=0
251+
MAX=5
252+
253+
# A2ML manifest present?
254+
if find . -name '*.a2ml' -not -path './.git/*' | head -1 | grep -q .; then
255+
SCORE=$((SCORE + 1))
256+
A2ML_STATUS=":white_check_mark:"
257+
else
258+
A2ML_STATUS=":x:"
259+
fi
260+
261+
# K9 contracts present?
262+
if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then
263+
SCORE=$((SCORE + 1))
264+
K9_STATUS=":white_check_mark:"
265+
else
266+
K9_STATUS=":x:"
267+
fi
268+
269+
# .editorconfig present?
270+
if [ -f ".editorconfig" ]; then
271+
SCORE=$((SCORE + 1))
272+
EC_STATUS=":white_check_mark:"
273+
else
274+
EC_STATUS=":x:"
275+
fi
276+
277+
# Groove manifest or code?
278+
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
279+
SCORE=$((SCORE + 1))
280+
GROOVE_STATUS=":white_check_mark:"
281+
else
282+
GROOVE_STATUS=":ballot_box_with_check:"
283+
fi
284+
285+
# VeriSimDB integration?
286+
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
287+
SCORE=$((SCORE + 1))
288+
VSDB_STATUS=":white_check_mark:"
289+
else
290+
VSDB_STATUS=":ballot_box_with_check:"
291+
fi
292+
293+
cat <<EOF >> "$GITHUB_STEP_SUMMARY"
294+
## Dogfooding Scorecard
295+
296+
**Score: ${SCORE}/${MAX}**
297+
298+
| Tool/Format | Status | Notes |
299+
|-------------|--------|-------|
300+
| A2ML manifest (0-AI-MANIFEST.a2ml) | ${A2ML_STATUS} | Required for all RSR repos |
301+
| K9 contracts | ${K9_STATUS} | Required for repos with config files |
302+
| .editorconfig | ${EC_STATUS} | Required for all repos |
303+
| Groove endpoint | ${GROOVE_STATUS} | Required for service repos |
304+
| VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos |
305+
306+
---
307+
*Generated by the [Dogfood Gate](https://github.com/hyperpolymath/rsr-template-repo) workflow.*
308+
*Dogfooding is guinea pig fooding — we test our tools on ourselves.*
309+
EOF

0 commit comments

Comments
 (0)