mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 20:11:31 +00:00
38f1200f26
Sprint 5 unified-master-audit closure. Pre-fix:
- api/openapi.yaml: 7,788 LOC of hand-authored spec.
- web/src/api/generated/: directory did NOT exist (the Phase-5
scaffolding never had its first generation run).
- scripts/ci-guards/openapi-codegen-drift.sh: skip-when-absent
(line 33-39 — informational scaffold).
- api/openapi.yaml info.version: '2.0.0', latest tag: v2.1.7
(a 7-version drift between spec and ship).
Net effect: every new route required three coordinated edits (Go
handler, openapi.yaml, frontend client.ts), payload-level breaking
changes shipped unnoticed, and downstream API client integration
cost was permanent.
Phase 1 fix (the audit's literal scope):
1. **Run Orval**, commit the generated tree. 316 files / ~1.8 MB
under web/src/api/generated/, tags-split layout (one directory
per OpenAPI tag), TanStack Query client mode. All output routes
through web/src/api/mutator.ts which delegates to the existing
fetchJSON in client.ts so auth/CSRF/401-event semantics stay
in one place.
2. **Fix two spec defects** the first orval run surfaced:
- YAML duplicate-key bug at L77-89 — SCEP's description was
misplaced under OIDC. Restored to its own tag entry.
- Missing #/components/schemas/Error referenced by three
operations. Aliased to the existing ErrorResponse schema.
3. **Flip the codegen-drift guard from skip-when-absent to
hard-gate.** A missing generated/ directory now fails the
build with an actionable restore command. The existing
regenerate-and-diff path stays as before.
4. **New openapi-version-tag-parity CI guard.** Asserts
openapi.yaml info.version equals the latest v* git tag. Falls
back to api.github.com when the local clone is shallow.
Bumped openapi.yaml info.version 2.0.0 → 2.1.7 in the same
commit so the new guard greens out.
5. **CI workflow** updated to fetch tags on the frontend job's
checkout so the parity guard reads them locally (the GH API
fallback still works but adds a network round-trip).
Verified locally:
- openapi-codegen-drift.sh: clean (re-generation produces
byte-identical tree to what's tracked).
- openapi-version-tag-parity.sh: clean (2.1.7 == v2.1.7).
- tsc --noEmit: exit 0 across the entire frontend (the
generated tree's responseType field threaded through the
mutator's CertctlFetchOptions cleanly).
- Existing Vitest suite: 141/141 pass on the three sampled
suites (AuthProvider + client + IssuerHierarchyPage).
Follow-on work (NOT in this commit):
- Per-consumer migration: pages flip from client.ts imports to
generated/ imports one at a time. Both styles share fetchJSON
semantics, so the migration is incremental.
- Server-side oapi-codegen handler stubs (Phase 2 from the
audit's fix language) — separate sprint.
Closes ARCH-001-A.
77 lines
3.0 KiB
Bash
Executable File
77 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# scripts/ci-guards/openapi-version-tag-parity.sh
|
|
#
|
|
# ARCH-001-A closure (Sprint 5, 2026-05-16). The hand-written
|
|
# api/openapi.yaml carries an info.version that historically drifted
|
|
# from the actual git-tag-shipping cadence (was "2.0.0" against a
|
|
# v2.1.7 latest tag). External consumers reading the spec for their
|
|
# generated clients have no signal which release shipped it.
|
|
#
|
|
# Fix: the guard reads info.version from openapi.yaml and the latest
|
|
# `v*` git tag from the repo. If they don't match, fail. Bump
|
|
# info.version in the same commit that runs `git tag -a v* ...`
|
|
# at release time.
|
|
#
|
|
# Edge cases handled:
|
|
# - Shallow CI clones: actions/checkout fetches no tags by default.
|
|
# The guard falls back to the GitHub API when local tags are
|
|
# unavailable, mirroring CLAUDE.md's ground-truth-against-the-API
|
|
# pattern. CI sets fetch-tags: true on the checkout step (per the
|
|
# workflow update that lands alongside this guard) so local-tag
|
|
# reads work reliably.
|
|
# - Pre-first-tag: skip with a notice if no v* tag exists yet.
|
|
|
|
set -e
|
|
|
|
YAML="api/openapi.yaml"
|
|
if [ ! -f "$YAML" ]; then
|
|
echo "::error::openapi-version-tag-parity: $YAML not found"
|
|
exit 1
|
|
fi
|
|
|
|
# Extract info.version from openapi.yaml. The version is at top level
|
|
# under `info:`. Use a minimal awk state machine instead of pulling
|
|
# yq into the CI dep graph.
|
|
spec_version=$(awk '
|
|
/^info:/ { in_info = 1; next }
|
|
/^[a-zA-Z]/ { in_info = 0 }
|
|
in_info && /^[[:space:]]+version:/ {
|
|
sub(/.*version:[[:space:]]*/, "")
|
|
sub(/[[:space:]]*#.*$/, "")
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "")
|
|
print
|
|
exit
|
|
}' "$YAML")
|
|
|
|
if [ -z "$spec_version" ]; then
|
|
echo "::error file=${YAML}::openapi-version-tag-parity: could not parse info.version. Expected a `version: x.y.z` line under `info:`."
|
|
exit 1
|
|
fi
|
|
|
|
# Resolve the latest tag locally. Fall back to the GitHub API if the
|
|
# checkout is shallow + tag-less (CLAUDE.md ground-truth pattern).
|
|
latest_tag=$(git tag --sort=-v:refname 2>/dev/null | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true)
|
|
if [ -z "$latest_tag" ]; then
|
|
echo "openapi-version-tag-parity: no local v* tag found; falling back to api.github.com/.../tags"
|
|
latest_tag=$(curl -sS https://api.github.com/repos/certctl-io/certctl/tags 2>/dev/null \
|
|
| grep -oE '"name": *"v[0-9]+\.[0-9]+\.[0-9]+"' \
|
|
| head -1 \
|
|
| sed -E 's/.*"v/v/; s/".*//')
|
|
fi
|
|
if [ -z "$latest_tag" ]; then
|
|
echo "openapi-version-tag-parity: no v* tag anywhere yet — skipping (pre-first-release)."
|
|
exit 0
|
|
fi
|
|
|
|
# Strip the leading 'v' from the tag for comparison.
|
|
tag_version="${latest_tag#v}"
|
|
|
|
if [ "$spec_version" != "$tag_version" ]; then
|
|
echo "::error file=${YAML}::openapi-version-tag-parity: info.version=${spec_version} does NOT match latest tag ${latest_tag}."
|
|
echo " Bump $YAML info.version to ${tag_version} in the same commit that ships the release,"
|
|
echo " OR if a release commit is in flight, tag it first then re-run CI."
|
|
exit 1
|
|
fi
|
|
|
|
echo "openapi-version-tag-parity: clean (info.version=${spec_version} matches latest tag ${latest_tag})."
|