Skip to content

Commit fc3d42a

Browse files
hyperpolymathclaude
andcommitted
chore: add pipeline drift check script + Justfile recipe
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 43e6d2c commit fc3d42a

2 files changed

Lines changed: 181 additions & 0 deletions

File tree

Justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,10 @@ validate-state:
11861186
echo "No .machine_readable/STATE.a2ml found"; \
11871187
fi
11881188
1189+
# Validate pipeline doc/A2ML synchronization drift
1190+
validate-pipeline-drift:
1191+
./scripts/check-pipeline-drift.sh .
1192+
11891193
# Validate AI installation guide completeness (finishbot pre-release check)
11901194
validate-ai-install:
11911195
#!/usr/bin/env bash

scripts/check-pipeline-drift.sh

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/bin/bash
2+
# SPDX-License-Identifier: PMPL-1.0-or-later
3+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
4+
#
5+
# Pipeline drift guard for nextgen-typing.
6+
# Ensures canonical pipeline docs and 6a2 machine-readable state stay aligned.
7+
8+
set -euo pipefail
9+
10+
REPO_ROOT="${1:-.}"
11+
ERRORS=0
12+
13+
README="$REPO_ROOT/README.adoc"
14+
ARCHITECTURE="$REPO_ROOT/docs/ARCHITECTURE.adoc"
15+
PIPELINE="$REPO_ROOT/docs/PIPELINE.adoc"
16+
STATE="$REPO_ROOT/.machine_readable/6a2/STATE.a2ml"
17+
META="$REPO_ROOT/.machine_readable/6a2/META.a2ml"
18+
ECOSYSTEM="$REPO_ROOT/.machine_readable/6a2/ECOSYSTEM.a2ml"
19+
20+
CANON_CHAIN="katagoria → typell → typed-wasm → PanLL"
21+
CHAIN_REGEX='katagoria[[:space:]]*→[[:space:]]*typell[[:space:]]*→[[:space:]]*typed-wasm[[:space:]]*→[[:space:]]*PanLL'
22+
CHAIN_REGEX_WITH_OPTIONAL_ROLES='katagoria([[:space:]]*\([^)]*\))?[[:space:]]*→[[:space:]]*typell([[:space:]]*\([^)]*\))?[[:space:]]*→[[:space:]]*typed-wasm([[:space:]]*\([^)]*\))?[[:space:]]*→[[:space:]]*PanLL([[:space:]]*\([^)]*\))?'
23+
STALE_REGEX='Not yet created|not yet updated|Does not exist yet|🚧 Planned|has not been created yet'
24+
25+
# ANSI colors
26+
RED='\033[0;31m'
27+
GREEN='\033[0;32m'
28+
BLUE='\033[0;34m'
29+
NC='\033[0m'
30+
31+
log_info() {
32+
echo -e "${BLUE}INFO${NC}: $*" >&2
33+
}
34+
35+
log_pass() {
36+
echo -e "${GREEN}PASS${NC}: $*" >&2
37+
}
38+
39+
log_error() {
40+
echo -e "${RED}ERROR${NC}: $*" >&2
41+
ERRORS=$((ERRORS + 1))
42+
}
43+
44+
require_file() {
45+
local file="$1"
46+
if [ -f "$file" ]; then
47+
log_pass "Found: ${file#$REPO_ROOT/}"
48+
else
49+
log_error "Missing required file: ${file#$REPO_ROOT/}"
50+
fi
51+
}
52+
53+
extract_quoted_value() {
54+
local key="$1"
55+
local file="$2"
56+
grep -E "^[[:space:]]*$key[[:space:]]*=" "$file" | head -1 | sed -E 's/^[^"]*"([^"]+)".*/\1/' || true
57+
}
58+
59+
check_contains_regex() {
60+
local file="$1"
61+
local regex="$2"
62+
local description="$3"
63+
if grep -Eq "$regex" "$file"; then
64+
log_pass "$description"
65+
else
66+
log_error "$description"
67+
fi
68+
}
69+
70+
check_katagoria_active_row() {
71+
local file="$1"
72+
local row_regex="$2"
73+
local label="$3"
74+
if awk -v row_re="$row_regex" '
75+
$0 ~ row_re {
76+
getline
77+
if ($0 ~ /^\|[[:space:]]*.*Active/) ok=1
78+
}
79+
END { exit(ok ? 0 : 1) }
80+
' "$file"; then
81+
log_pass "$label"
82+
else
83+
log_error "$label"
84+
fi
85+
}
86+
87+
echo ""
88+
log_info "Pipeline drift guard: validating canonical files and cross-file invariants"
89+
echo ""
90+
91+
require_file "$README"
92+
require_file "$ARCHITECTURE"
93+
require_file "$PIPELINE"
94+
require_file "$STATE"
95+
require_file "$META"
96+
require_file "$ECOSYSTEM"
97+
98+
if [ "$ERRORS" -gt 0 ]; then
99+
echo ""
100+
echo -e "${RED}Drift guard failed due to missing files.${NC}" >&2
101+
exit 1
102+
fi
103+
104+
echo ""
105+
log_info "Checking canonical chain and invariant language"
106+
echo ""
107+
108+
check_contains_regex "$README" "$CHAIN_REGEX" "README contains canonical main chain"
109+
check_contains_regex "$ARCHITECTURE" "$CHAIN_REGEX" "ARCHITECTURE contains canonical main chain"
110+
check_contains_regex "$PIPELINE" "$CHAIN_REGEX" "PIPELINE contains canonical main chain"
111+
check_contains_regex "$STATE" "$CHAIN_REGEX_WITH_OPTIONAL_ROLES" "STATE purpose contains canonical main chain"
112+
check_contains_regex "$ECOSYSTEM" "$CHAIN_REGEX" "ECOSYSTEM purpose contains canonical main chain"
113+
114+
PIPE_CHAIN="$(extract_quoted_value "chain" "$PIPELINE")"
115+
if [ "$PIPE_CHAIN" = "$CANON_CHAIN" ]; then
116+
log_pass "PIPELINE chain value exactly matches canonical chain"
117+
else
118+
log_error "PIPELINE chain mismatch: expected '$CANON_CHAIN', got '$PIPE_CHAIN'"
119+
fi
120+
121+
check_contains_regex "$README" "open-ended" "README contains open-ended TypeLL framing"
122+
check_contains_regex "$ARCHITECTURE" "Key Invariant: TypeLL Is Open-Ended" "ARCHITECTURE contains TypeLL key invariant section"
123+
check_contains_regex "$PIPELINE" "open-ended progressive" "PIPELINE contains open-ended progressive invariant"
124+
check_contains_regex "$META" 'title = "TypeLL is open-ended progressive, not capped at 10 levels"' "META ADR-002 encodes open-ended TypeLL framing"
125+
126+
check_katagoria_active_row "$README" '^[|][[:space:]]*katagoria[[:space:]]*$' "README marks katagoria row as Active"
127+
check_katagoria_active_row "$ARCHITECTURE" '^[|][[:space:]]*`katagoria`[[:space:]]*$' "ARCHITECTURE marks katagoria row as Active"
128+
129+
echo ""
130+
log_info "Checking date synchronization across canonical state files"
131+
echo ""
132+
133+
STATE_DATE="$(extract_quoted_value "last-updated" "$STATE")"
134+
META_DATE="$(extract_quoted_value "last-updated" "$META")"
135+
ECOSYSTEM_DATE="$(extract_quoted_value "last-updated" "$ECOSYSTEM")"
136+
PIPELINE_VERSION="$(extract_quoted_value "version" "$PIPELINE")"
137+
ARCH_DATE="$(grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' "$ARCHITECTURE" | head -1 || true)"
138+
139+
if [ -z "$STATE_DATE" ] || [ -z "$META_DATE" ] || [ -z "$ECOSYSTEM_DATE" ] || [ -z "$PIPELINE_VERSION" ] || [ -z "$ARCH_DATE" ]; then
140+
log_error "One or more date/version fields are missing (STATE/META/ECOSYSTEM last-updated, PIPELINE version, ARCHITECTURE header date)"
141+
else
142+
if [ "$META_DATE" = "$STATE_DATE" ] && [ "$ECOSYSTEM_DATE" = "$STATE_DATE" ] && [ "$PIPELINE_VERSION" = "$STATE_DATE" ] && [ "$ARCH_DATE" = "$STATE_DATE" ]; then
143+
log_pass "Dates synchronized: STATE/META/ECOSYSTEM last-updated == PIPELINE version == ARCHITECTURE header date ($STATE_DATE)"
144+
else
145+
log_error "Date drift detected: STATE=$STATE_DATE META=$META_DATE ECOSYSTEM=$ECOSYSTEM_DATE PIPELINE.version=$PIPELINE_VERSION ARCHITECTURE.date=$ARCH_DATE"
146+
fi
147+
fi
148+
149+
echo ""
150+
log_info "Checking for known stale phrases"
151+
echo ""
152+
153+
STALE_HITS="$(grep -nE "$STALE_REGEX" "$README" "$ARCHITECTURE" "$PIPELINE" "$STATE" "$META" "$ECOSYSTEM" || true)"
154+
if [ -n "$STALE_HITS" ]; then
155+
log_error "Stale status phrases detected in canonical files:"
156+
echo "$STALE_HITS" >&2
157+
else
158+
log_pass "No stale status phrases found in canonical files"
159+
fi
160+
161+
if grep -Fq 'docs/PIPELINE.adoc' "$README"; then
162+
log_pass "README links to docs/PIPELINE.adoc"
163+
else
164+
log_error "README is missing a link/reference to docs/PIPELINE.adoc"
165+
fi
166+
167+
echo ""
168+
echo "═══════════════════════════════════════════════════════════════════════════════" >&2
169+
echo "PIPELINE DRIFT GUARD SUMMARY" >&2
170+
echo "═══════════════════════════════════════════════════════════════════════════════" >&2
171+
if [ "$ERRORS" -eq 0 ]; then
172+
echo -e "${GREEN}PASS${NC}: no pipeline drift detected." >&2
173+
exit 0
174+
else
175+
echo -e "${RED}FAIL${NC}: $ERRORS issue(s) detected." >&2
176+
exit 1
177+
fi

0 commit comments

Comments
 (0)