-
Notifications
You must be signed in to change notification settings - Fork 2
177 lines (166 loc) · 7.63 KB
/
Copy pathvalidate-formats.yml
File metadata and controls
177 lines (166 loc) · 7.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
name: validate-formats
# Runs on every push and PR. Validates that the small set of structured
# files in the repo today actually parse — covers the gap between
# "secret-scan + link-check" and "we have real code with real tests."
# Dedicated runtime workflows should join this set for Python and Go;
# this workflow does NOT pretend to be a substitute for those tests.
#
# No untrusted GitHub event inputs are interpolated into run blocks;
# all paths come from `find` inside the runner.
on:
push:
branches: [main, dev]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
json:
name: JSON
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Validate every JSON file
run: |
set -euo pipefail
fail=0
while IFS= read -r -d '' f; do
if ! python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$f" 2>/dev/null; then
echo "::error file=$f::invalid JSON"
fail=1
fi
done < <(find . -path ./.git -prune -o -path ./.claude -prune -o -path ./artifacts -prune -o -name '*.json' -print0)
exit "$fail"
yaml:
name: YAML
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Validate every YAML file
run: |
set -euo pipefail
# Use Python's PyYAML — already on the runner; no extra install.
fail=0
while IFS= read -r -d '' f; do
if ! python3 -c "import yaml,sys; list(yaml.safe_load_all(open(sys.argv[1])))" "$f" 2>/dev/null; then
echo "::error file=$f::invalid YAML"
fail=1
fi
done < <(find . -path ./.git -prune -o -path ./.claude -prune -o -path ./artifacts -prune -o \( -name '*.yml' -o -name '*.yaml' \) -print0)
exit "$fail"
# NOTE: a markdown-table sanity job was prototyped here but pulled —
# the heuristic ("header has |, next line is only |/-/:/space") false-
# positived on legitimate tables every time the separator row contained
# an extra space or a stripped trailing pipe. The signal-to-noise ratio
# was too low to justify the gate. If we want this back, use a real
# Markdown parser (mdformat, remark) rather than a pipe-count heuristic.
spec-schema-sync:
name: Spec Schema Sync
# Round 3 (2026-04-28): the Mission Declaration v0.1 JSON Schema
# ships in two places — the canonical doc at
# docs/specs/mission-declaration-v0.1.schema.json and the package
# data at python/vibap/_specs/mission_declaration_v01.schema.json
# that the runtime loads via importlib.resources. Because the
# runtime trusts the embedded copy, a PR that desyncs them silently
# weakens validation. This gate diffs the two and fails the build
# on any drift.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Compare every embedded schema to its canonical doc
# Round 4 (FIX-R4-10, 2026-04-28): generalized from a single
# hardcoded pair to a glob over python/vibap/_specs/*.json so
# any future spec schema added under that directory is auto-
# gated. Filename mapping: an embedded file named
# ``some_thing_v01.schema.json`` is paired with the canonical
# at ``docs/specs/some-thing-v0.1.schema.json`` (underscores in
# the embedded name → hyphens in the canonical name; ``v01`` →
# ``v0.1``). Pairs that don't follow this convention are
# listed explicitly in EXTRA_PAIRS below.
run: |
set -euo pipefail
# Explicit overrides for non-conventional filenames go here:
declare -a EXTRA_PAIRS=()
fail=0
shopt -s nullglob
for embedded in python/vibap/_specs/*.schema.json; do
base=$(basename "$embedded" .schema.json)
# Convert e.g. ``mission_declaration_v01`` →
# ``mission-declaration-v0.1`` using portable shell tools.
# Step 1: ``tr '_' '-'`` rewrites every underscore as a hyphen
# (``mission_declaration_v01`` → ``mission-declaration-v01``).
# Step 2: sed inserts the dot between major + minor version
# digits (``-v01`` → ``-v0.1``).
# No heredoc-in-YAML — the round-5 FIX-R5-M2 shell-injection
# concern doesn't apply here because ``$base`` is the basename
# of a checked-in file already validated by the directory glob.
# No Python interpreter spawn either; simpler and portable
# across BSD sed (macOS) and GNU sed (Linux CI).
canonical_base=$(printf '%s' "$base" | tr '_' '-' \
| sed 's/-v\([0-9]\)\([0-9]\)$/-v\1.\2/')
canonical="docs/specs/${canonical_base}.schema.json"
if [ ! -f "$canonical" ]; then
echo "::error file=$embedded::no canonical pair found at $canonical"
fail=1
continue
fi
c_hash=$(python3 -c "import json,sys,hashlib; print(hashlib.sha256(json.dumps(json.load(open(sys.argv[1])), sort_keys=True).encode()).hexdigest())" "$canonical")
e_hash=$(python3 -c "import json,sys,hashlib; print(hashlib.sha256(json.dumps(json.load(open(sys.argv[1])), sort_keys=True).encode()).hexdigest())" "$embedded")
if [ "$c_hash" != "$e_hash" ]; then
echo "::error::Spec schema out of sync: $base"
echo " canonical: $canonical (sha256: $c_hash)"
echo " embedded: $embedded (sha256: $e_hash)"
echo ""
echo "To resync after editing the canonical doc:"
echo " cp $canonical $embedded"
echo ""
diff -u "$canonical" "$embedded" || true
fail=1
continue
fi
echo "✓ $base in sync (sha256: $c_hash)"
done
# Apply explicit overrides if any.
for pair in "${EXTRA_PAIRS[@]}"; do
canonical="${pair%%::*}"
embedded="${pair##*::}"
if [ ! -f "$canonical" ] || [ ! -f "$embedded" ]; then
echo "::error::EXTRA_PAIR file missing: $pair"
fail=1
continue
fi
c_hash=$(python3 -c "import json,sys,hashlib; print(hashlib.sha256(json.dumps(json.load(open(sys.argv[1])), sort_keys=True).encode()).hexdigest())" "$canonical")
e_hash=$(python3 -c "import json,sys,hashlib; print(hashlib.sha256(json.dumps(json.load(open(sys.argv[1])), sort_keys=True).encode()).hexdigest())" "$embedded")
if [ "$c_hash" != "$e_hash" ]; then
echo "::error::Override pair out of sync: $pair"
fail=1
fi
done
exit "$fail"
validate-formats:
name: validate-formats
if: ${{ always() }}
needs:
- json
- yaml
- spec-schema-sync
runs-on: ubuntu-latest
steps:
- name: Require every format and schema job
env:
JSON_RESULT: ${{ needs.json.result }}
YAML_RESULT: ${{ needs.yaml.result }}
SPEC_SCHEMA_SYNC: ${{ needs['spec-schema-sync'].result }}
run: |
set -euo pipefail
require_success() {
local job="$1"
local result="$2"
if [ "$result" != "success" ]; then
echo "::error::Required job $job concluded $result"
exit 1
fi
}
require_success json "$JSON_RESULT"
require_success yaml "$YAML_RESULT"
require_success spec-schema-sync "$SPEC_SCHEMA_SYNC"