mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 15:11:29 +00:00
M-029 Pass 1 closure: tighten ci.yml M-009 guard from soft budget to hard zero
Pass 1 finished — every src/ useMutation now goes through useTrackedMutation. Promote the M-009 guard to a hard-zero invariant: any bare useMutation() call outside web/src/hooks/useTrackedMutation.ts fails CI immediately. Pre-Bundle-8 the codebase had 56 bare useMutation sites. Bundle 8 shipped the wrapper. M-029 Pass 1 migrated all 56 sites to the wrapper across 6 batches (commits2057e76/e0a3d50/ee25f00/ec3772d/190a27e/213b464). With the soft-budget gate now obsolete, the hard-zero gate prevents drift back into the discretionary-invalidation pattern that motivated M-009 in the first place. Rationale: per-site enforcement (the wrapper's discriminated-union invalidates contract) is strictly stronger than the +5 budget guard. The guard's failure mode also improves: instead of a count delta the operator has to interpret, they get the exact file:line(s) of the offending bare useMutation call. Verification: python3 yaml.safe_load YAML OK manual guard simulation PASS: bare useMutation = 0 outside wrapper
This commit is contained in:
+32
-22
@@ -1003,32 +1003,42 @@ jobs:
|
||||
fi
|
||||
echo "L-019 dangerouslySetInnerHTML guardrail: clean."
|
||||
|
||||
- name: Bundle-8 / M-009 mutation invalidation contract guard
|
||||
# Audit M-009: every useMutation must either invalidate the
|
||||
# queries it changes OR document why no invalidation is needed.
|
||||
# SOFT guard — counts useMutation sites and asserts the budget
|
||||
# doesn't grow without a corresponding invalidateQueries / setQueryData /
|
||||
# useTrackedMutation reference. Stricter per-site enforcement is
|
||||
# tracked as M-029 (covers the long-tail useListParams + useTrackedMutation
|
||||
# migration of the existing 56 useMutation sites).
|
||||
- name: Bundle-8 / M-009 + M-029 Pass 1 mutation contract guard (hard zero)
|
||||
# Audit M-009 + M-029 Pass 1 closure:
|
||||
#
|
||||
# Pre-Bundle-8 the codebase had 56 bare useMutation sites with
|
||||
# discretionary invalidation. Bundle 8 shipped the useTrackedMutation
|
||||
# wrapper (web/src/hooks/useTrackedMutation.ts) that requires every
|
||||
# caller to declare `invalidates: QueryKey[] | 'noop'`. M-029 Pass 1
|
||||
# then migrated all 56 sites to the wrapper across 6 batches.
|
||||
#
|
||||
# This guard pins the contract going forward: every useMutation call
|
||||
# in src/ MUST be inside useTrackedMutation.ts (the wrapper itself
|
||||
# is the only legitimate caller of useMutation). Any bare useMutation
|
||||
# call elsewhere is a regression — adding a new mutation site means
|
||||
# going through the wrapper so the invalidates contract is enforced
|
||||
# per-site, not by a soft budget guard.
|
||||
#
|
||||
# If you genuinely need raw useMutation (extremely unlikely — the
|
||||
# wrapper supports invalidates: 'noop' for fire-and-forget mutations),
|
||||
# update this guard's exclusion list and document the carve-out.
|
||||
run: |
|
||||
set -e
|
||||
MUTATIONS=$(grep -rcE 'useMutation\(|useTrackedMutation\(' web/src/ 2>/dev/null \
|
||||
| awk -F: '{s+=$2} END{print s}')
|
||||
INVALIDATIONS=$(grep -rcE 'invalidateQueries|setQueryData|removeQueries|invalidates:' web/src/ 2>/dev/null \
|
||||
| awk -F: '{s+=$2} END{print s}')
|
||||
echo "M-009 budget — useMutation sites: $MUTATIONS / invalidation sites: $INVALIDATIONS"
|
||||
# At Bundle-8 close: 56 useMutation + 70 invalidation. We allow
|
||||
# +5 mutations growth before requiring invalidation parity. If
|
||||
# the gap widens, audit the new mutation sites for missing
|
||||
# invalidation pairs.
|
||||
BUDGET=$((INVALIDATIONS + 5))
|
||||
if [ "$MUTATIONS" -gt "$BUDGET" ]; then
|
||||
echo "M-009 regression: $MUTATIONS useMutation sites exceeds invalidation budget ($BUDGET)."
|
||||
echo "New mutations should pair with invalidateQueries/setQueryData OR migrate to"
|
||||
echo "useTrackedMutation (web/src/hooks/useTrackedMutation.ts) with explicit invalidates."
|
||||
BARE=$(grep -rnE '\buseMutation\(' web/src/ 2>/dev/null | grep -v 'web/src/hooks/useTrackedMutation\.ts' || true)
|
||||
if [ -n "$BARE" ]; then
|
||||
echo "M-009 hard-zero regression: bare useMutation() call(s) outside the wrapper:"
|
||||
echo "$BARE"
|
||||
echo
|
||||
echo "Every mutation must go through useTrackedMutation"
|
||||
echo "(web/src/hooks/useTrackedMutation.ts) with explicit"
|
||||
echo "invalidates: QueryKey[] | 'noop'. See file header for usage."
|
||||
exit 1
|
||||
fi
|
||||
# Sanity counts (informational, not a gate).
|
||||
TRACKED=$(grep -rcE '\buseTrackedMutation\(' web/src/ 2>/dev/null | awk -F: '{s+=$2} END{print s}')
|
||||
INVALIDATIONS=$(grep -rcE 'invalidateQueries|setQueryData|removeQueries|invalidates:' web/src/ 2>/dev/null | awk -F: '{s+=$2} END{print s}')
|
||||
echo "M-009 hard-zero: bare useMutation sites = 0 (wrapper-internal call excluded)."
|
||||
echo "M-009 informational: useTrackedMutation sites = $TRACKED; invalidation surface = $INVALIDATIONS."
|
||||
|
||||
- name: Forbidden env-var docs drift regression guard (G-3)
|
||||
# G-3 master closed cat-g-163dae19bc59 (docs-only env vars
|
||||
|
||||
Reference in New Issue
Block a user