bigpowers 2.73.4 → 2.73.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/package.json +1 -1
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/scripts/check-spec-version-gap.sh +37 -9
- package/scripts/lib/golden-suite-agent.sh +80 -0
- package/scripts/lib/golden-suite-gates.sh +105 -0
- package/scripts/lib/golden-suite-report.sh +157 -0
- package/scripts/lib/golden-suite-run.sh +79 -0
- package/scripts/lib/validate-okf-kinds.sh +272 -0
- package/scripts/run-golden-suite.sh +4 -488
- package/scripts/run-verification-gates.sh +4 -488
- package/scripts/validate-okf.sh +2 -318
package/scripts/validate-okf.sh
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# story: e38s09 e45s02
|
|
3
3
|
# validate-okf.sh — kind-aware, fail-closed provenance gate for OKF bundles.
|
|
4
|
-
# Validates story-metrics, spec-migration, migration-registry, concept,
|
|
5
|
-
# and verification-report bundles.
|
|
6
|
-
# Multi-dir default: specs/metrics/ + specs/migrations/.
|
|
7
4
|
set -euo pipefail
|
|
8
5
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
6
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/validate-okf-kinds.sh"
|
|
9
7
|
|
|
10
8
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "validate-okf: not inside a git repo"; exit 1; }
|
|
11
9
|
EXIT_CODE=0
|
|
12
|
-
|
|
13
10
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; NC='\033[0m'
|
|
14
11
|
|
|
15
12
|
usage_okf() {
|
|
@@ -36,321 +33,8 @@ USAGEEOF
|
|
|
36
33
|
exit 0
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
# ---- Helpers ------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
val_nullish() { [ -z "$1" ] || [ "$1" = "null" ] || [ "$1" = "None" ]; }
|
|
42
|
-
val_nullish_or_empty() { val_nullish "$1" || [ "$1" = "[]" ]; }
|
|
43
|
-
|
|
44
|
-
parse_frontmatter() {
|
|
45
|
-
local file="$1"
|
|
46
|
-
$PYTHON -c "
|
|
47
|
-
import yaml, sys, json
|
|
48
|
-
|
|
49
|
-
class SafeEncoder(json.JSONEncoder):
|
|
50
|
-
def default(self, obj):
|
|
51
|
-
try:
|
|
52
|
-
return super().default(obj)
|
|
53
|
-
except TypeError:
|
|
54
|
-
return str(obj)
|
|
55
|
-
|
|
56
|
-
content = open('$file').read()
|
|
57
|
-
parts = content.split('---')
|
|
58
|
-
if len(parts) < 3:
|
|
59
|
-
sys.exit(1)
|
|
60
|
-
try:
|
|
61
|
-
fm = yaml.safe_load(parts[1])
|
|
62
|
-
if fm is None:
|
|
63
|
-
sys.exit(1)
|
|
64
|
-
print(json.dumps(fm, cls=SafeEncoder))
|
|
65
|
-
except Exception as e:
|
|
66
|
-
print(f'YAML parse error: {e}', file=sys.stderr)
|
|
67
|
-
sys.exit(1)
|
|
68
|
-
" 2>/dev/null
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# ---- Kind-specific validators ------------------------------------------
|
|
72
|
-
|
|
73
|
-
validate_story_metrics() {
|
|
74
|
-
local file="$1" fm="$2" name="$3"
|
|
75
|
-
local errs=0
|
|
76
|
-
for key in id epic bcps commit_range source generated_at generator; do
|
|
77
|
-
local val
|
|
78
|
-
val="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
79
|
-
if val_nullish "$val"; then
|
|
80
|
-
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
81
|
-
errs=$((errs + 1))
|
|
82
|
-
fi
|
|
83
|
-
done
|
|
84
|
-
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
85
|
-
|
|
86
|
-
local source
|
|
87
|
-
source="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('source',''))" 2>/dev/null)"
|
|
88
|
-
if ! echo "measured estimated backfilled" | grep -qw "$source"; then
|
|
89
|
-
printf "${RED}FAIL${NC} %s: invalid source '%s' — must be measured|estimated|backfilled\n" "$name" "$source"
|
|
90
|
-
EXIT_CODE=1; return
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
local generator
|
|
94
|
-
generator="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('generator',''))" 2>/dev/null)"
|
|
95
|
-
if [ "$generator" != "scripts/record-cycle-time.sh" ]; then
|
|
96
|
-
printf "${RED}FAIL${NC} %s: wrong generator '%s' — expected scripts/record-cycle-time.sh\n" "$name" "$generator"
|
|
97
|
-
EXIT_CODE=1; return
|
|
98
|
-
fi
|
|
99
|
-
|
|
100
|
-
local commit_range
|
|
101
|
-
commit_range="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('commit_range',''))" 2>/dev/null)"
|
|
102
|
-
if ! git -C "$ROOT" log --oneline -1 "$commit_range" >/dev/null 2>&1; then
|
|
103
|
-
printf "${RED}FAIL${NC} %s: commit_range '%s' does not resolve\n" "$name" "$commit_range"
|
|
104
|
-
EXIT_CODE=1; return
|
|
105
|
-
fi
|
|
106
|
-
|
|
107
|
-
local eff
|
|
108
|
-
eff="$(echo "$fm" | $PYTHON -c "
|
|
109
|
-
import json,sys
|
|
110
|
-
d=json.load(sys.stdin)
|
|
111
|
-
e=d.get('effort',{}).get('effort_hours')
|
|
112
|
-
if e is None: sys.exit(1)
|
|
113
|
-
if e < 0: sys.exit(2)
|
|
114
|
-
print(e)
|
|
115
|
-
" 2>/dev/null)" || {
|
|
116
|
-
local rc=$?
|
|
117
|
-
if [ "$rc" -eq 1 ]; then
|
|
118
|
-
printf "${RED}FAIL${NC} %s: effort.effort_hours missing or null\n" "$name"
|
|
119
|
-
else
|
|
120
|
-
printf "${RED}FAIL${NC} %s: effort_hours out of bounds\n" "$name"
|
|
121
|
-
fi
|
|
122
|
-
EXIT_CODE=1; return
|
|
123
|
-
}
|
|
124
|
-
printf "${GREEN}PASS${NC} %s (source=%s, generator=%s, commit_range resolves)\n" "$name" "$source" "$generator"
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
validate_spec_migration() {
|
|
128
|
-
local file="$1" fm="$2" name="$3"
|
|
129
|
-
local errs=0
|
|
130
|
-
for key in id title since_version order actions_needed; do
|
|
131
|
-
local val
|
|
132
|
-
val="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
133
|
-
if val_nullish_or_empty "$val"; then
|
|
134
|
-
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
135
|
-
errs=$((errs + 1))
|
|
136
|
-
fi
|
|
137
|
-
done
|
|
138
|
-
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
139
|
-
|
|
140
|
-
local fp_count
|
|
141
|
-
fp_count="$(echo "$fm" | $PYTHON -c "
|
|
142
|
-
import json,sys
|
|
143
|
-
d=json.load(sys.stdin)
|
|
144
|
-
fp=d.get('fingerprint',{}).get('any',[])
|
|
145
|
-
print(len(fp))
|
|
146
|
-
" 2>/dev/null)"
|
|
147
|
-
if [ "${fp_count:-0}" -eq 0 ]; then
|
|
148
|
-
printf "${RED}FAIL${NC} %s: fingerprint.any is empty — no detection criteria\n" "$name"
|
|
149
|
-
EXIT_CODE=1; return
|
|
150
|
-
fi
|
|
151
|
-
|
|
152
|
-
printf "${GREEN}PASS${NC} %s (actions=%s, fingerprint has %s check(s))\n" \
|
|
153
|
-
"$name" \
|
|
154
|
-
"$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('actions_needed',[])))" 2>/dev/null)" \
|
|
155
|
-
"$fp_count"
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
validate_migration_registry() {
|
|
159
|
-
local file="$1" fm="$2" name="$3"
|
|
160
|
-
local count
|
|
161
|
-
count="$(echo "$fm" | $PYTHON -c "
|
|
162
|
-
import json,sys
|
|
163
|
-
d=json.load(sys.stdin)
|
|
164
|
-
migrations=d.get('migrations',[])
|
|
165
|
-
print(len(migrations))
|
|
166
|
-
" 2>/dev/null)"
|
|
167
|
-
if [ "${count:-0}" -eq 0 ]; then
|
|
168
|
-
printf "${RED}FAIL${NC} %s: migrations list is empty\n" "$name"
|
|
169
|
-
EXIT_CODE=1; return
|
|
170
|
-
fi
|
|
171
|
-
|
|
172
|
-
local bad
|
|
173
|
-
bad="$(echo "$fm" | $PYTHON -c "
|
|
174
|
-
import json,sys
|
|
175
|
-
d=json.load(sys.stdin)
|
|
176
|
-
for m in d.get('migrations',[]):
|
|
177
|
-
if not all(k in m for k in ('id','file','status')):
|
|
178
|
-
print(m.get('id','?')); sys.exit(1)
|
|
179
|
-
" 2>/dev/null)" || {
|
|
180
|
-
printf "${RED}FAIL${NC} %s: migration '%s' missing id/file/status\n" "$name" "${bad:-?}"
|
|
181
|
-
EXIT_CODE=1; return
|
|
182
|
-
}
|
|
183
|
-
printf "${GREEN}PASS${NC} %s (%s migrations indexed)\n" "$name" "$count"
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
# ---- Concept validator (e45s02) ----
|
|
187
|
-
validate_concept() {
|
|
188
|
-
local file="$1" fm="$2" name="$3"
|
|
189
|
-
local errs=0
|
|
190
|
-
for key in id title category; do
|
|
191
|
-
local val
|
|
192
|
-
val="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
193
|
-
if val_nullish "$val"; then
|
|
194
|
-
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
195
|
-
errs=$((errs + 1))
|
|
196
|
-
fi
|
|
197
|
-
done
|
|
198
|
-
local ref_count
|
|
199
|
-
ref_count="$(echo "$fm" | $PYTHON -c "
|
|
200
|
-
import json,sys
|
|
201
|
-
d=json.load(sys.stdin)
|
|
202
|
-
r=d.get('references',[])
|
|
203
|
-
print(len(r) if isinstance(r,list) else 0)
|
|
204
|
-
" 2>/dev/null)"
|
|
205
|
-
if [ "${ref_count:-0}" -eq 0 ]; then
|
|
206
|
-
printf "${RED}FAIL${NC} %s: references[] is empty or missing\n" "$name"
|
|
207
|
-
errs=$((errs + 1))
|
|
208
|
-
fi
|
|
209
|
-
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
210
|
-
local cat
|
|
211
|
-
cat="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('category',''))" 2>/dev/null)"
|
|
212
|
-
printf "${GREEN}PASS${NC} %s (category=%s, %s reference(s))\n" "$name" "$cat" "$ref_count"
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
# ---- Verification-report validator (e45s02) ----
|
|
216
|
-
validate_verification_report() {
|
|
217
|
-
local file="$1" fm="$2" name="$3"
|
|
218
|
-
local errs=0
|
|
219
|
-
for key in score gate_status threshold total_pass total_fail generated_by; do
|
|
220
|
-
local val
|
|
221
|
-
val="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
222
|
-
if val_nullish "$val"; then
|
|
223
|
-
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
224
|
-
errs=$((errs + 1))
|
|
225
|
-
fi
|
|
226
|
-
done
|
|
227
|
-
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
228
|
-
|
|
229
|
-
local gs
|
|
230
|
-
gs="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('gate_status',''))" 2>/dev/null)"
|
|
231
|
-
if ! echo "pass fail concerns waived" | grep -qw "$gs"; then
|
|
232
|
-
printf "${RED}FAIL${NC} %s: invalid gate_status '%s' — must be pass|fail|concerns|waived\n" "$name" "$gs"
|
|
233
|
-
EXIT_CODE=1; return
|
|
234
|
-
fi
|
|
235
|
-
|
|
236
|
-
local score
|
|
237
|
-
score="$(echo "$fm" | $PYTHON -c "
|
|
238
|
-
import json,sys
|
|
239
|
-
d=json.load(sys.stdin)
|
|
240
|
-
s=d.get('score')
|
|
241
|
-
if s is None or not isinstance(s,(int,float)):
|
|
242
|
-
sys.exit(1)
|
|
243
|
-
if s < 0 or s > 100:
|
|
244
|
-
sys.exit(2)
|
|
245
|
-
print(s)
|
|
246
|
-
" 2>/dev/null)" || {
|
|
247
|
-
local rc=$?
|
|
248
|
-
if [ "$rc" -eq 1 ]; then
|
|
249
|
-
printf "${RED}FAIL${NC} %s: score missing or not numeric\n" "$name"
|
|
250
|
-
else
|
|
251
|
-
printf "${RED}FAIL${NC} %s: score out of bounds (0-100)\n" "$name"
|
|
252
|
-
fi
|
|
253
|
-
EXIT_CODE=1; return
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
printf "${GREEN}PASS${NC} %s (score=%.1f, gate=%s, threshold=%.1f)\n" \
|
|
257
|
-
"$name" "$score" "$gs" \
|
|
258
|
-
"$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('threshold',0))" 2>/dev/null)"
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
# ---- Receipts validation (e41s04) ----
|
|
262
|
-
validate_receipts() {
|
|
263
|
-
local f="$ROOT/specs/receipts.json"
|
|
264
|
-
if [ ! -f "$f" ]; then printf "${YELLOW}SKIP${NC} receipts.json not found\n"; return; fi
|
|
265
|
-
$PYTHON -c "
|
|
266
|
-
import json, sys
|
|
267
|
-
d=json.load(open('$f'))
|
|
268
|
-
vs={'measured','estimated','backfilled','absent'}
|
|
269
|
-
ss=('compliance','golden_suite','metrics','traceability')
|
|
270
|
-
errs=0
|
|
271
|
-
for sec in ss:
|
|
272
|
-
data=d.get(sec)
|
|
273
|
-
if data is None: continue
|
|
274
|
-
src=data.get('source','')
|
|
275
|
-
if not src: print(f'FAIL: receipts.json — {sec}: missing source tag'); errs+=1; continue
|
|
276
|
-
if src not in vs: print(f'FAIL: receipts.json — {sec}: unknown source \"{src}\"'); errs+=1; continue
|
|
277
|
-
hv='value' in data
|
|
278
|
-
if src=='absent' and hv: print(f'FAIL: receipts.json — {sec}: tagged absent but carries value'); errs+=1
|
|
279
|
-
elif src!='absent' and not hv: print(f'FAIL: receipts.json — {sec}: tagged {src} but missing value'); errs+=1
|
|
280
|
-
if errs==0: print('PASS receipts.json')
|
|
281
|
-
else: print(f'{errs} violation(s) in receipts.json')
|
|
282
|
-
sys.exit(errs)
|
|
283
|
-
" || { EXIT_CODE=1; return; }
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
# ---- Main validation dispatcher ----
|
|
287
|
-
validate_bundle() {
|
|
288
|
-
local file="$1"
|
|
289
|
-
local name; name="$(basename "$file")"
|
|
290
|
-
local fm
|
|
291
|
-
fm="$(parse_frontmatter "$file")" || {
|
|
292
|
-
printf "${RED}FAIL${NC} %s: cannot parse YAML frontmatter\n" "$name"
|
|
293
|
-
EXIT_CODE=1
|
|
294
|
-
return
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
# ── OKF v0.1 Generic Frontmatter Conformance (e39s10) ──
|
|
298
|
-
# Check required 'type' field
|
|
299
|
-
local fm_type
|
|
300
|
-
fm_type="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('type',''))" 2>/dev/null)"
|
|
301
|
-
if [ -z "$fm_type" ]; then
|
|
302
|
-
printf "${RED}FAIL${NC} %s: OKF v0.1 — missing required 'type' field\n" "$name"
|
|
303
|
-
EXIT_CODE=1
|
|
304
|
-
return
|
|
305
|
-
fi
|
|
306
|
-
|
|
307
|
-
# Reserved filenames: index.md must have type: Index
|
|
308
|
-
if [ "$name" = "index.md" ] && [ "$fm_type" != "Index" ]; then
|
|
309
|
-
printf "${RED}FAIL${NC} %s: OKF v0.1 — index.md must have type: Index (got '%s')\n" "$name" "$fm_type"
|
|
310
|
-
EXIT_CODE=1
|
|
311
|
-
return
|
|
312
|
-
fi
|
|
313
|
-
|
|
314
|
-
local kind
|
|
315
|
-
kind="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('okf_kind',''))" 2>/dev/null)"
|
|
316
|
-
case "$kind" in
|
|
317
|
-
story-metrics) validate_story_metrics "$file" "$fm" "$name" ;;
|
|
318
|
-
spec-migration) validate_spec_migration "$file" "$fm" "$name" ;;
|
|
319
|
-
migration-registry) validate_migration_registry "$file" "$fm" "$name" ;;
|
|
320
|
-
concept) validate_concept "$file" "$fm" "$name" ;;
|
|
321
|
-
verification-report) validate_verification_report "$file" "$fm" "$name" ;;
|
|
322
|
-
"")
|
|
323
|
-
printf "${YELLOW}SKIP${NC} %s: no okf_kind — not an OKF bundle\n" "$name"
|
|
324
|
-
;;
|
|
325
|
-
*)
|
|
326
|
-
printf "${YELLOW}SKIP${NC} %s: unknown okf_kind '%s'\n" "$name" "$kind"
|
|
327
|
-
;;
|
|
328
|
-
esac
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
scan_dir() {
|
|
332
|
-
local dir="$1"
|
|
333
|
-
if [ ! -d "$dir" ]; then
|
|
334
|
-
printf "${RED}FAIL${NC} directory not found: %s\n" "$dir"
|
|
335
|
-
EXIT_CODE=1
|
|
336
|
-
return
|
|
337
|
-
fi
|
|
338
|
-
local count=0
|
|
339
|
-
while IFS= read -r -d '' f; do
|
|
340
|
-
if head -1 "$f" 2>/dev/null | grep -q '^---$'; then
|
|
341
|
-
validate_bundle "$f"
|
|
342
|
-
count=$((count + 1))
|
|
343
|
-
fi
|
|
344
|
-
done < <(find "$dir" -maxdepth 1 -name '*.md' -print0 2>/dev/null || true)
|
|
345
|
-
if [ "$count" -eq 0 ]; then
|
|
346
|
-
printf "${YELLOW}SKIP${NC} no OKF bundles found in %s\n" "$dir"
|
|
347
|
-
fi
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
# ---- main ----
|
|
351
36
|
DIRS=()
|
|
352
37
|
BUNDLE=""
|
|
353
|
-
STAMPLINE="# e45s02 stamp -- concept and verification-report validators added"
|
|
354
38
|
|
|
355
39
|
if [ $# -eq 0 ]; then
|
|
356
40
|
DIRS=("$ROOT/specs/metrics" "$ROOT/specs/migrations")
|
|
@@ -371,7 +55,7 @@ if [ -n "$BUNDLE" ]; then
|
|
|
371
55
|
[ ! -f "$BUNDLE" ] && { printf "${RED}FAIL${NC} bundle not found: %s\n" "$BUNDLE"; exit 1; }
|
|
372
56
|
validate_bundle "$BUNDLE"
|
|
373
57
|
elif [ ${#DIRS[@]} -gt 0 ]; then
|
|
374
|
-
for d in "${DIRS[@]}"; do
|
|
58
|
+
for d in "${DIRS[@]}"; do okf_scan_dir "$d"; done
|
|
375
59
|
else
|
|
376
60
|
echo "validate-okf: nothing to validate (no --dir or --bundle)" >&2
|
|
377
61
|
exit 1
|