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.
@@ -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 scan_dir "$d"; done
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