Skip to content
Back to skills

Progressive Delivery

BSecurity

Canary gates that cannot fail and flags that cannot be flipped: dead, stale, unsafe default, build-time.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 20, 2026
ai-agentspythonrustgojavaphpshellbashreactvuenode

Works with

  • cli
  • api

Security analysis

B88/100
  • criticalSends environment variables or credentials to an external URL

Pro shows the line behind each finding and how to fix it

Scanned October 20, 2026

npx -y skills add adnanmokhtar/refract --skill progressive-delivery --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Progressive Delivery?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Progressive Delivery
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/adnanmokhtar-progressive-delivery/badge)](https://www.skillsdirectory.com/skills/adnanmokhtar-progressive-delivery)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: progressive-delivery
description: "Canary gates that cannot fail and flags that cannot be flipped: dead, stale, unsafe default, build-time."
kind: skill
pack: devops
allowed-tools: [Read, Grep, Glob, Bash]
---

# Skill: progressive-delivery

## Premise

Two mechanisms let a team deploy without releasing: an automated canary gate, and a flag that can be flipped at runtime. Both fail silently, and both look present in a file review:
- an AnalysisTemplate whose `successCondition` is `result[0] >= 0` passes every canary, and so does a `failureLimit` larger than its `count`, a metric listed under `dryRun`, a query on a series that does not exist, or a query on the whole service instead of the canary;
- a flag whose SDK default is `true` turns the risky path **on** for everyone when the provider is down; a flag read once at boot, or inlined by the bundler (`VITE_*`, `NEXT_PUBLIC_*`), cannot be flipped without a deploy; a key the code reads but the registry never declares evaluates to its default forever.

No tool in the ledger reads these. Argo Rollouts and Flagger accept every one of them; the flag SDKs log a warning at most.

This skill owns `ownership.json` row `progressive-delivery` (§1–§9). `@deployment-engineer` (this pack) judges its rows.

**Every finding cites the line `<file:line>` + the object or key (Rollout, AnalysisTemplate metric, Canary, flag key) + the environment it reaches (the render, or the registry's environment block) + the platform or SDK fact it rests on (with its date and tag) + the oracle and its tier + the fix in the project's own syntax.** A pre-flight row is a candidate. "Rollouts are gated" or "flags look healthy" is never printed; the output is the class-coverage block (§ Output). Verification follows [`refute-before-report.md`](../../../../snippets/refute-before-report.md) with [`precedents/devops.md`](../../../../snippets/precedents/devops.md) (deploy § 22–28; § 27 and § 28 are this skill's). The oracles are [`proof-oracles.md`](../../../../snippets/proof-oracles.md) § DevOps, V232 (§5) and V233 (§4, §8, §9); §1–§3, §6 and §7 are static facts proven by a two-ended trace (tier S: the declaration and the read, or the two environment values).

## The rule

A candidate is a finding when all four hold (the seven gates as `precedents/devops.md` § Reading the seven gates applies them):

1. **It ships** (gates 1, 7). The Rollout or Canary is in the render of an environment a CD target or deploy unit applies; the flag is read by code a service root runs. A dev overlay, a per-PR preview (precedents § 24) or a test fixture is Hardening or Refuted.
2. **The event happens** (gate 2). A canary that should abort runs (a bad image, a 20 % error rate); the flag provider is unreachable, slow or missing the key; someone needs to flip the flag during an incident.
3. **No control already handles it** (gate 4). A second metric in the same template that can fail; a declared synthetic-load analysis (precedents § 28); a permanent operational flag (precedents § 27); a provider bootstrap that serves a safe value offline; an inverted flag name whose `true` is the old path.
4. **The oracle fired, or both ends are cited** (gate 6). V232 / V233 at their tier; without a run, the two ends (the template line and the render row; the SDK call and the registry entry) and the row is `[report-flagged]`.

## Boundaries

- **Which strategy** (canary, blue-green, rolling) and its rollback command → `@deployment-engineer`. **Watching one rollout** and handing a breach to `/rollback-deploy` → `monitor-deploy`. **A CI deploy with no rollout wait** → `deploy-safety-scan` §4; a GitOps CI that only commits manifests → precedents § 23 and `gitops-audit`.
- **Selector grounding** is observability's technique: `alert-rule-lint` §4 and its `scripts/rule-inventory.py --check=selectors`, when the observability pack is installed (pre-flight 5g runs it on every Prometheus query of §5). Without it, every query row prints `[report-flagged] selector not grounded (owner pack observability not installed)` and §5 never claims a series exists. The PromQL itself (windows, `rate` on a gauge) is not judged here.
- **The Rollout's pod template** (replicas, PDB, probes, resources) → infrastructure `k8s-availability-scan` (it reads the Argo Rollout template), **the GitOps spec** → infrastructure `k8s-render-scan` §4, each when installed; without it, `N/A (owner pack infrastructure not installed)`.
- **A contract migration shipped under a canary** (old and new pods share one schema) → database `migration-safety` §4 when installed, and `deploy-safety-scan` §1d.
- **Config that is not a flag** (a URL, a key) declared per environment → `deploy-safety-scan` §6. **A server image built per environment** → `release-integrity-scan` §4. **A flag SDK key committed in a file** → security `secret-scan-method` when installed.

## Adapt to the codebase — the lanes

Discover every surface (pre-flight 0a, 1); never assume `k8s/`, `flags.yaml` or `src/`. Print the lanes you ran in the header.

| Canary lane | Object | Gate field this skill reads | Never-fails shape |
|---|---|---|---|
| **Argo Rollouts** (v1.10.0 [R]) | `Rollout` + `AnalysisTemplate` / `ClusterAnalysisTemplate` | `strategy.canary.analysis` or an `- analysis:` step; blue-green `prePromotionAnalysis`; per metric `successCondition`, `failureCondition`, `count`, `failureLimit`, `inconclusiveLimit`, `spec.dryRun` | `result[0] >= 0`; no condition at all (evaluates Successful [K]); `failureLimit ≥ count`; `dryRun`; blue-green with `autoPromotionEnabled` left at its default `true` and no `prePromotionAnalysis` [K] |
| **Flagger** (v1.45.0 [R]) | `Canary` + `MetricTemplate` | `analysis.metrics[]`, `thresholdRange`, `threshold`, `skipAnalysis` | `metrics: []` (health checks only [R]); `thresholdRange.min: 0` on a success rate; `skipAnalysis: true` promotes without analysis [K] |
| **Spinnaker / Kayenta** | pipeline JSON stage `kayentaCanary` | `scoreThresholds.marginal`, `.pass` | `marginal: 0` never fails a score [K?]; read-only lane |
| **None** | plain Deployment, rolling | — | §5 prints `N/A (no progressive-delivery object)` |

| Flag lane | Call the pre-flight reads | Default on provider outage or a missing key | Flip without a deploy |
|---|---|---|---|
| **OpenFeature** (spec v0.9.0 [R]) | `getBooleanValue('k', <default>)`, `get*Details` | the default argument [K] | yes (provider) |
| **LaunchDarkly** | `variation('k', ctx, <default>)`, `boolVariation`, Go `BoolVariation` | the default argument [K] | yes |
| **Unleash** | `isEnabled('k', ctx, fallback)`, Python `is_enabled(…, fallback_function=…)` | the fallback [K] | yes |
| **GrowthBook · ConfigCat · Flagsmith · Split** | `isOn`, `getFeatureValue`, `getValue(Async)`, `hasFeature`, `getTreatment` on a flag client | the default argument [K?] | yes |
| **Config file** (flagd, `flags.yaml`) | the registry keys × the SDK keys | the SDK's default | yes, when a provider serves the file at runtime; no, when the app reads it at boot |
| **Environment variable** (`FEATURE_*`, `*_ENABLED`) | `process.env.X`, `os.environ`, `os.Getenv` | whatever the parse gives an unset var | **no**: a change is a restart or a rollout |
| **Build-time public env** (`VITE_*`, `NEXT_PUBLIC_*`, `REACT_APP_*`, `EXPO_PUBLIC_*`) | `import.meta.env.X`, `process.env.X` in client code; `--build-arg` | inlined at build | **no**: a rebuild and a deploy |

## Tool lane and ledger

| Lane | Command | Trap that makes a clean result false |
|---|---|---|
| Render | infrastructure `render.sh` when installed; else `kubectl --kubeconfig=<scratch> kustomize <dir>` and `helm template` into `$SCAN` (offline; a remote base is skipped as `blocked (remote base)`) | A failed render makes that environment's §5 rows `blocked (render failed: <dir>)`; an overlay that patches `failureLimit` exists only in the render |
| Grounding | `python3 -B .claude/skills/alert-rule-lint/scripts/rule-inventory.py <queries> <recording-rule files> --check=selectors --inventory=<names> --format=text --all` (observability pack) | Run **without** `--inventory` it exits 0 and hides the `blocked (no --inventory)` note: always pass the inventory. An inventory of 0 names makes every selector UNGROUNDED: read the count first |
| Registry | the repo's flag files; a SaaS registry (LaunchDarkly, Unleash server) only through its API with a token the user names | No file and no API → §1–§3 and §6 registry side `UNREADABLE (registry not in repo)`, never "no dead flags" |
| Age | `git log --diff-filter=A -S<key> -- <registry>` | A shallow clone dates every key to the clone: say so |
| Live (L) | `kubectl argo rollouts get rollout <r> --kubeconfig=<scratch>` on a throwaway cluster | Never the default context: no scratch cluster → `NO-DATA(rollout)` with the command written out |

## Pre-flight

Run as **one** script from the repo root (shell state does not survive between tool calls; bash 3.2 and zsh print the same rows). Grep **N** feeds **§N**; 0a/0b are the inventory and the render. Every `rg` names its path; an rg exit 2 is a failed grep, never zero hits. The include globs come **before** the exclusion array: in ripgrep the later glob wins, so `-g '*.ts'` after `-g '!**/*.spec.*'` would read the specs again (measured below). Nothing is written in the repo: renders, sets and the scratch rule file go to `$SCAN`.

```bash
X=(--hidden -g '!.git/**' -g '!**/node_modules/**' -g '!**/{dist,build,out,target,vendor,.venv,venv,__pycache__,coverage,.next,.nuxt,.output,.turbo,generated}/**'
   -g '!**/{test,tests,__tests__,spec,e2e,fixtures,__fixtures__,mocks,__mocks__,testdata,examples}/**' -g '!**/*.{spec,test,e2e-spec,stories}.*' -g '!**/*_test.go' -g '!**/test_*.py'
   -g '!**/{docs,ai,.claude}/**' -g '!*.{md,mdx,snap,lock,svg,map}' -g '!**/{locales,i18n,migrations}/**')
CODE=(-g '*.{ts,tsx,js,jsx,mjs,cjs,vue,svelte,astro,py,go,rb,erb,java,kt,cs,php,ex,exs,rs}' -g '!*.d.ts')
NC=(rg -v --pcre2 '^[^:]+:\d+:\s*(#|//|\*|/\*)')
SCAN="${SCAN:-$(mktemp -d "${TMPDIR:-/tmp}/delivery.XXXXXX")}"; echo "SCAN=$SCAN"
RENDERS() { find "$SCAN" -maxdepth 1 -name 'render-*.yaml' | sort; }
# Flag SDK calls whose first argument is the key; generic method names count only on a flag-ish receiver.
CALL="(?:\bget(?:Boolean|String|Number|Object)(?:Value|Details)|\b(?:bool|string|int|float|json)Variation(?:Detail)?|\b(?:Bool|String|Int|Float|JSON)Variation(?:Detail)?|\bget_(?:boolean|string|integer|float|object)_value|\bis_enabled|\bisOn|\bgetFeatureValue|\bevalFeature|\bhasFeature|\b\w*(?i:flag|feature|toggle|unleash|launchdarkly|ldclient|ld|openfeature|growthbook|configcat|split)\w*\s*\.\s*(?:variation(?:Detail)?|isEnabled|IsEnabled|getValue(?:Async)?|getTreatment))\s*\(\s*['\"\`]([\w.:/-]+)['\"\`]"
ENVF='\b((?:FEATURE|FF|FLAG|TOGGLE|ENABLE|DISABLE|KILL)_[A-Z0-9_]+|[A-Z][A-Z0-9_]*_(?:ENABLED|DISABLED|FLAG|FEATURE|KILLSWITCH))\b'
PUB='\b((?:NEXT_PUBLIC|VITE|REACT_APP|EXPO_PUBLIC|NUXT_PUBLIC|GATSBY|PUBLIC)_\w*(?:FLAG|FEATURE|ENABLE|DISABLE|TOGGLE|KILL)\w*)\b'
echo '# 0a delivery objects (Rollout, AnalysisTemplate, Flagger Canary/MetricTemplate) and Spinnaker/Kayenta stages — discovered, never a guessed dir'
rg -l --pcre2 -g '*.{yml,yaml,tpl}' "${X[@]}" '^\s*kind:\s*(Rollout|AnalysisTemplate|ClusterAnalysisTemplate|Canary|MetricTemplate)\s*$' . | sort > "$SCAN/delivery.txt"; cat "$SCAN/delivery.txt"
rg -l --pcre2 -g '*.{json,yml,yaml}' "${X[@]}" '\bkayentaCanary\b' . | while IFS= read -r f; do rg -n -H --pcre2 '\bkayentaCanary\b|"?(marginal|pass)"?\s*:\s*"?\d+' "$f"; done
echo '# 0b render (offline, isolated kubeconfig): reuse a render already in $SCAN or infrastructure render.sh; else kubectl kustomize / helm template'
if [ -z "$(RENDERS)" ] && [ -s "$SCAN/delivery.txt" ]; then RS=.claude/skills/k8s-render-scan/scripts/render.sh; : > "$SCAN/none.kubeconfig"
  if [ -f "$RS" ]; then bash "$RS" . --out="$SCAN" --quiet; echo "render.sh exit=$?"; else
    rg --files -g '**/kustomization.{yml,yaml}' "${X[@]}" . | sed 's|/[^/]*$||' | while IFS= read -r d; do
      rg -q --pcre2 '^\s*-\s*(https?://|github\.com/|git@)|\?ref=' "$d" -g 'kustomization.{yml,yaml}' && { echo "blocked (remote base): $d"; continue; }
      KUBECONFIG="$SCAN/none.kubeconfig" kubectl --kubeconfig="$SCAN/none.kubeconfig" kustomize "$d" > "$SCAN/render-$(echo "$d" | tr '/.' '__').yaml" 2>>"$SCAN/render.err" || echo "render FAILED: $d"; done
    rg --files -g '**/Chart.yaml' "${X[@]}" . | sed 's|/[^/]*$||' | while IFS= read -r d; do
      KUBECONFIG="$SCAN/none.kubeconfig" helm template x "$d" > "$SCAN/render-$(echo "$d" | tr '/.' '__').yaml" 2>>"$SCAN/render.err" || echo "render FAILED: $d"; done; fi; fi
RENDERS | sed 's|.*/||'
echo '# 5 canary gate rows: source (cited) and render (applied); render-only rows are overlay or values changes'
printf 'groups:\n- name: canary-analysis\n  rules:\n' > "$SCAN/analysis-rules.yaml"
GATE="$SCAN/gate.awk"; cat > "$GATE" <<'AWK'
function ind(t) { match(t, /^ */); return RLENGTH }
function val(t) { sub(/^[^:]*:[ \t]*/, "", t); sub(/[ \t]+#.*$/, "", t); gsub(/^["']|["']$/, "", t); return t }
function rule(nm, ln, e) { if (R == "" || pv != "prometheus" || e == "") return; gsub(/\{\{[^}]*\}\}/, "1m", e); gsub(/\n/, "\n      ", e)
  printf "  - alert: \"%s:%d:%s\"\n    expr: |\n      %s\n", FILENAME, ln, nm, e >> R }
function emit_m(  c, sco) { if (mn == "") return; c = ""
  if (sc ~ /(>=|=>)[ \t]*0(\.0*)?[ \t]*$/ || fc ~ /<[ \t]*0(\.0*)?[ \t]*$/) c = c " NEVER-FAILS"
  if (sc == "" && fc == "" && pv != "job") c = c " NO-CONDITION"
  if (cnt ~ /^[0-9]+$/ && fl ~ /^[0-9]+$/ && fl + 0 >= cnt + 0) c = c " LIMIT>=COUNT"
  if (sc != "" && fc != "" && il == "") c = c " NO-INCONCLUSIVE-LIMIT"
  if ((mn in dry) || dryall) c = c " DRYRUN"
  if (q ~ /or[ \t]+vector\([ \t]*0[ \t]*\)|scalar\(/) c = c " ZERO-FALLBACK"
  sco = (q ~ /rollouts[-_]pod[-_]template[-_]hash/) ? "hash" : (q ~ /\{\{ *args\./) ? "args" : (q == "" ? "-" : "no")
  if (sco == "no") c = c " UNSCOPED"
  printf "%s:%d: %s %s metric=%s provider=%s count=%s failureLimit=%s inconclusiveLimit=%s success=[%s] failure=[%s] scope=%s ->%s\n", FILENAME, ml, k, n, mn, (pv == "" ? "?" : pv), (cnt == "" ? "-" : cnt), (fl == "" ? "0" : fl), (il == "" ? "-" : il), sc, fc, sco, (c == "" ? " ok" : c)
  rule(mn, ml, q); mn = ""; sc = ""; fc = ""; cnt = ""; fl = ""; il = ""; pv = ""; q = "" }
function flush(  c) { emit_m()
  if (k == "Rollout") { c = ""
    if (strat == "canary" && sw > 0 && !an) c = " NO-ANALYSIS"
    if (strat == "blueGreen" && ap != "false" && !pre) c = " BG-AUTO-PROMOTE"
    printf "%s:%d: Rollout %s strategy=%s setWeight=%d analysis=%s autoPromotion=%s prePromotion=%s templates=%s ->%s\n", FILENAME, dl, n, (strat == "" ? "?" : strat), sw, (an ? "yes" : "no"), (ap == "" ? "default" : ap), (pre ? "yes" : "no"), (refs == "" ? "-" : substr(refs, 2)), (c == "" ? " ok" : c) }
  if (k == "Canary" && api ~ /flagger/) { c = ""; if (skip) c = c " SKIP-ANALYSIS"; if (nm <= 0) c = c " NO-METRICS"
    printf "%s:%d: Canary %s metrics=%s threshold=%s interval=%s skipAnalysis=%s ->%s%s\n", FILENAME, dl, n, (nm < 0 ? "absent" : nm), (th == "" ? "-" : th), (iv == "" ? "-" : iv), (skip ? "true" : "false"), (c == "" && fm == "" ? " ok" : c), fm }
  if (k == "MetricTemplate" && api ~ /flagger/) { c = (q ~ /\{\{ *target *\}\}/) ? "target" : "no"
    printf "%s:%d: MetricTemplate %s provider=%s scope=%s ->%s\n", FILENAME, dl, n, pv, c, (c == "no" ? " UNSCOPED" : " ok"); rule(n, dl, q) }
  k = ""; n = ""; api = ""; strat = ""; sw = 0; an = 0; ap = ""; pre = 0; refs = ""; skip = 0; nm = -1; th = ""; iv = ""; fm = ""
  split("", dry); dryall = 0; indry = 0; inm = 0; inq = 0; q = ""; pv = "" }
FNR == 1 { if (NR > 1) flush(); k = ""; nm = -1 }
/^---/ { flush(); next }
/^[ \t]*#/ { next }
{ i = ind($0); s = $0; sub(/^ */, "", s) }
inq { if (s == "" || i > qi) { if (s != "") q = q (q == "" ? "" : "\n") s; next } inq = 0 }
/^apiVersion:/ { api = val($0) }
/^kind:/ { k = val($0); dl = FNR }
/^  name:/ && n == "" { n = val($0) }
k ~ /AnalysisTemplate$/ {
  if (s ~ /^dryRun:/) { indry = 1; di = i; next }
  if (indry && i <= di && s !~ /^- /) indry = 0
  if (indry && s ~ /metricName:/) { v = val(s); if (v ~ /[.*]/) dryall = 1; dry[v] = 1; next }
  if (s ~ /^metrics:/) { emit_m(); inm = 1; mi = i; next }
  if (inm && i <= mi && s !~ /^- /) { emit_m(); inm = 0 }
  if (inm && s ~ /^- / && i <= mi + 2) { emit_m(); mn = "?"; ml = FNR; fi = i + 2; s = substr(s, 3); i = fi }
  if (mn != "" && i == fi) { if (s ~ /^name:/) mn = val(s); else if (s ~ /^successCondition:/) sc = val(s); else if (s ~ /^failureCondition:/) fc = val(s)
    else if (s ~ /^count:/) cnt = val(s); else if (s ~ /^failureLimit:/) fl = val(s); else if (s ~ /^inconclusiveLimit:/) il = val(s) }
  if (mn != "" && i > fi && pv == "" && s ~ /^(prometheus|datadog|newRelic|wavefront|influxdb|cloudWatch|graphite|kayenta|web|job|skywalking|plugin):/) { pv = s; sub(/:.*/, "", pv) }
}
(k ~ /AnalysisTemplate$/ || k == "MetricTemplate") && s ~ /^query:/ { v = val(s); if (v ~ /^[|>][-+]?$/) { inq = 1; qi = i; q = "" } else q = v; next }
k == "MetricTemplate" && s ~ /^type:/ { pv = val(s) }
k == "Rollout" { if (s ~ /^canary:/) strat = "canary"; if (s ~ /^blueGreen:/) strat = "blueGreen"
  if (s ~ /^- setWeight:/) sw++; if (s ~ /^(- )?analysis:/) an = 1; if (s ~ /^prePromotionAnalysis:/) pre = 1
  if (s ~ /^autoPromotionEnabled:/) ap = val(s); if (s ~ /^(- )?templateName:/) { t = s; sub(/^- /, "", t); refs = refs "," val(t) } }
k == "Canary" && api ~ /flagger/ { if (s ~ /^skipAnalysis:[ \t]*true/) skip = 1; if (s ~ /^threshold:/) th = val(s); if (s ~ /^interval:/ && iv == "") iv = val(s)
  if (s ~ /^metrics:/) { nm = 0; fmi = i; next }
  if (nm >= 0 && s ~ /^- / && i <= fmi + 2) { nm++; cm = "?"; ci = i + 2; s = substr(s, 3); i = ci }
  if (nm > 0 && i == ci && s ~ /^name:/) cm = val(s)
  if (nm > 0 && s ~ /^min:/ && val(s) ~ /^0(\.0*)?$/) fm = fm " NEVER-FAILS(" cm ": thresholdRange.min 0)" }
END { flush() }
AWK
while IFS= read -r f; do [ -n "$f" ] && awk -v R="$SCAN/analysis-rules.yaml" -f "$GATE" "$f"; done < "$SCAN/delivery.txt" | tee "$SCAN/gate-src.txt"
RENDERS | while IFS= read -r f; do awk -v R= -f "$GATE" "$f"; done > "$SCAN/gate-render.txt"
awk 'NR == FNR { k = $0; sub(/^[^ ]+ /, "", k); s[k] = 1; next } { k = $0; sub(/^[^ ]+ /, "", k); if (!(k in s)) print "render-only: " $0 }' "$SCAN/gate-src.txt" "$SCAN/gate-render.txt"
echo '# 5g selector grounding: observability rule-inventory.py --check=selectors (when installed) on every Prometheus query above'
RI=.claude/skills/alert-rule-lint/scripts/rule-inventory.py
if ! rg -q 'alert:' "$SCAN/analysis-rules.yaml"; then echo 'grounding: N/A (no Prometheus query)'
elif [ ! -f "$RI" ]; then echo 'grounding: NOT-RUN (owner pack observability not installed) — every query row stays [report-flagged] selector not grounded'
else if [ -z "${INV:-}" ]; then INV="$SCAN/inventory.txt"; NM='([A-Za-z_:][\w.:]*)'   # alert-rule-lint pre-flight step 2, verbatim; that file wins on a difference
  { rg -U --pcre2 -o --no-filename -r '$1' "(?:new\s+(?:\w+\.)?(?:Counter|Gauge|Histogram|Summary)|make(?:Counter|Gauge|Histogram|Summary)Provider)\s*(?:<[^>]*>)?\(\s*\{[^}]*?\bname\s*:\s*['\"]${NM}['\"]" "${CODE[@]}" "${X[@]}" .
    rg -U --pcre2 -o --no-filename -r '$1' "(?:\bprometheus_client\.|(?<![.\w]))(?:Counter|Gauge|Histogram|Summary|Info|Enum)\(\s*['\"]${NM}['\"]" "${CODE[@]}" "${X[@]}" .
    rg -U --pcre2 -o --no-filename -r '$1' "(?:Counter|Gauge|Histogram|Summary)Opts\s*\{[^}]*?\bName:\s*\"${NM}\"" "${CODE[@]}" "${X[@]}" .
    rg -U --pcre2 -o --no-filename -r '$1' "(?:\b(?:Counter|Timer|Gauge|DistributionSummary|LongTaskTimer)\.builder|\bregistry\.(?:counter|timer|gauge|summary)|\b(?:create|Create)(?:Counter|Histogram|UpDownCounter|Gauge|Observable(?:Gauge|Counter|UpDownCounter))(?:<\w+>)?|\bcreate_(?:counter|histogram|up_down_counter|gauge|observable_\w+)|\b(?:Int64|Float64)(?:Counter|Histogram|UpDownCounter|Gauge|Observable(?:Counter|Gauge|UpDownCounter))|\bMetrics\.Create(?:Counter|Gauge|Histogram|Summary))\(\s*['\"]${NM}['\"]" "${CODE[@]}" "${X[@]}" .
  } | sort -u > "$INV"; fi
  RF=(); while IFS= read -r f; do RF+=("$f"); done < <(rg -l --pcre2 -g '*.{yml,yaml}' "${X[@]}" '^\s*-?\s*record:\s*\S' .)
  echo "inventory: $(wc -l < "$INV" | tr -d ' ') names; recording-rule files: ${#RF[@]}"
  python3 -B "$RI" "$SCAN/analysis-rules.yaml" ${RF[@]+"${RF[@]}"} --check=selectors --inventory="$INV" --format=text --all > "$SCAN/grounding.txt"; echo "rule-inventory exit=$?"
  rg -F "$SCAN/analysis-rules.yaml" "$SCAN/grounding.txt"; fi
echo '# 1 flag registries (keys) and SDK call sites (keys read in code)'
rg --files -g '**/*{flag,flags,feature,features,toggle,toggles}*.{yml,yaml,json}' -g '**/*.flagd.json' "${X[@]}" -g '!**/package*.json' . | sort > "$SCAN/registries.txt"; cat "$SCAN/registries.txt"
while IFS= read -r f; do case "$f" in
  *.json) python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); f=d.get("flags") or d.get("features") or {}; [print(k) for k in (f if isinstance(f,dict) else [x.get("name") for x in f if isinstance(x,dict)])]' "$f" ;;
  *) awk '/^(flags|features|toggles):[ \t]*$/ { on = 1; next } on && /^[^ #]/ { on = 0 } on && /^  [A-Za-z0-9_.:\/-]+:/ { k = $1; sub(/:$/, "", k); gsub(/["\x27]/, "", k); print k }' "$f" ;;
esac | sed "s|^|$f	|"; done < "$SCAN/registries.txt" | sort -u > "$SCAN/keys.tsv"; echo "registry keys: $(wc -l < "$SCAN/keys.tsv" | tr -d ' ')"
rg -n -o --pcre2 "${CODE[@]}" "${X[@]}" -r '$1' "$CALL" . | "${NC[@]}" | sort > "$SCAN/calls.txt"; echo "SDK call sites: $(wc -l < "$SCAN/calls.txt" | tr -d ' ')"
cut -d: -f3- "$SCAN/calls.txt" | sort -u > "$SCAN/called.txt"; cut -f2 "$SCAN/keys.tsv" | sort -u > "$SCAN/declared.txt"
echo "called, never declared (evaluates to its default forever): $(comm -23 "$SCAN/called.txt" "$SCAN/declared.txt" | tr '\n' ' ')"
echo '# 1-2 reference count per declared key (quoted literal, and its SCREAMING_SNAKE form), age from git; harness check on keys with a call site'
while IFS="$(printf '\t')" read -r f key; do
  lit=$(rg -c -F "${CODE[@]}" "${X[@]}" -e "'$key'" -e "\"$key\"" -e "\`$key\`" . | awk -F: '{ s += $NF } END { print s + 0 }')
  up=$(printf '%s' "$key" | tr 'a-z.:/-' 'A-Z____'); snake=$(rg -c -F "${CODE[@]}" "${X[@]}" -e "$up" . | awk -F: '{ s += $NF } END { print s + 0 }')
  age=$(git log --diff-filter=A --format=%as -S"$key" -- "$f" 2>/dev/null | tail -1)
  called=$(rg -q -x -F -e "$key" "$SCAN/called.txt" && echo yes || echo no)
  printf '%s\t%s\tliteral=%s\tsnake=%s\tcall-site=%s\tadded=%s\n' "$f" "$key" "$lit" "$snake" "$called" "${age:-unknown}"
done < "$SCAN/keys.tsv" | tee "$SCAN/refs.tsv" | awk -F'\t' '$3 == "literal=0" || $4 != "snake=0" || $5 == "call-site=no"'
awk -F'\t' '$5 == "call-site=yes" && $3 == "literal=0" { bad++ } $5 == "call-site=yes" { n++ } END { print (bad ? "HARNESS BROKEN: " bad " keys with a call site count 0 — fix the globs before any DEAD row" : "harness ok (" n + 0 " keys with a call site all counted)") }' "$SCAN/refs.tsv"
echo '# 2 flags hard-coded in code; 3 registry keys missing an owner, or an expiry on a release flag'
rg -n --pcre2 "${CODE[@]}" "${X[@]}" '^(?:export\s+)?(?:const|let|var)\s+\w*(?i:flag|feature|enable|toggle)\w*\s*(?::\s*\w+\s*)?=\s*(?:true|false)\s*;?\s*(?://.*)?$|^[A-Z][A-Z0-9_]*(?:FLAG|FEATURE|ENABLE|TOGGLE)[A-Z0-9_]*\s*=\s*(?:True|False)\s*(?:#.*)?$|^\s*(?:(?:public|private|protected)\s+)?static\s+final\s+boolean\s+\w*(?i:flag|feature|enable|toggle)\w*\s*=\s*(?:true|false)\s*;' . | "${NC[@]}"
while IFS= read -r f; do case "$f" in *.json) ;; *) awk -v F="$f" 'function out() { if (k != "" && (!o || !(e || p))) print F ":" kl ": " k " missing=" (o ? "" : "owner ") (e || p ? "" : "expiry") }
  /^(flags|features|toggles):/ { on = 1; next } on && /^[^ #]/ { out(); k = ""; on = 0 } on && /^  [A-Za-z0-9_.:\/-]+:/ { out(); k = $1; sub(/:$/, "", k); kl = FNR; o = 0; e = 0; p = 0; next }
  on && /^    owner:/ { o = 1 } on && /^    (expires|expiry|expiration|remove_by|removal|sunset|until|ticket):/ { e = 1 } on && /^    (type|kind):[ \t]*(permanent|ops|operational|kill-?switch|ramp|experiment)/ { p = 1 } END { out() }' "$f" ;; esac; done < "$SCAN/registries.txt"
echo '# 4 kill-switch candidates: env-var flags read at module scope (boot) vs per call; risky keys'
rg -n --pcre2 "${CODE[@]}" "${X[@]}" "(?:process\.env\.|process\.env\[['\"]|os\.(?:environ\[|environ\.get\(|getenv\()['\"]|os\.Getenv\(\"|ENV\[['\"]|System\.getenv\(\")$ENVF" . | "${NC[@]}" | awk -F: '{ l = $0; sub(/^[^:]+:[0-9]+:/, "", l); print ((l ~ /^(export |const |let |var |[A-Z_][A-Z0-9_]* *=|static |final )/) ? "boot-read" : "per-call") "\t" $0 }'
rg -n --pcre2 "${CODE[@]}" "${X[@]}" -i "$CALL" . | rg -i --pcre2 '\b(pay|refund|charge|checkout|billing|invoice|payout|transfer|delete|purge|wipe|migrat|webhook|email|sms|notify)' | "${NC[@]}"
echo '# 6 per-environment values: env-var flags across .env.*, compose, rendered env; registry environments blocks'
rg -n --hidden --pcre2 -g '**/.env*' -g '*compose*.{yml,yaml}' -g '!**/node_modules/**' "^\s*-?\s*$ENVF\s*[=:]\s*\S" . | sort
RENDERS | while IFS= read -r f; do rg -n -A1 --pcre2 "^\s*-\s*name:\s*$ENVF" "$f"; done
while IFS= read -r f; do case "$f" in *.json) ;; *) awk -v F="$f" '
  function chk(  n, i, v, d, nu) { n = split(ev, a, ","); d = 0; nu = 1; for (i = 1; i <= n; i++) { v = a[i]; sub(/^[^:]*:[ \t]*/, "", v); gsub(/[ \t{}"\x27]/, "", v); if (v !~ /^[0-9.]+$/) nu = 0; if (i > 1 && v != f1) d = 1; if (i == 1) f1 = v }
    if (d) print F ":" el ": " k " " (nu ? "RAMP (precedents § 27)" : (ev ~ /prod[a-z]*:[ \t]*(true|on|1)([ \t,]|$)/ && ev ~ /:[ \t]*(false|off|0)([ \t,]|$)/) ? "PROD-AHEAD" : (ev ~ /prod[a-z]*:[ \t]*(false|off|0)([ \t,]|$)/) ? "STAGED" : "DRIFT") " {" ev "}"; ev = "" }
  /^  [A-Za-z0-9_.:\/-]+:/ { if (ev != "") chk(); k = $1; sub(/:$/, "", k); ine = 0; next }
  /^    (environments|envs):/ { el = FNR; v = $0; sub(/^[^:]*:[ \t]*/, "", v); if (v ~ /^\{/) { gsub(/[{}]/, "", v); ev = v; chk() } else { ine = 1; ev = "" } next }
  ine && /^      [A-Za-z0-9_-]+:/ { t = $0; sub(/^ +/, "", t); ev = ev (ev == "" ? "" : ",") t; next }
  ine { ine = 0; if (ev != "") chk() }
  END { if (ev != "") chk() }' "$f" ;; esac; done < "$SCAN/registries.txt"
echo '# 7 a flag that selects config, credentials or routing (call site with a config read within 2 lines)'
rg -n -U --pcre2 "${CODE[@]}" "${X[@]}" "${CALL}[^\n]*\n?(?:[^\n]*\n){0,2}?[^\n]*(?:process\.env|os\.environ|os\.Getenv|config\.get|ConfigService)[^\n]*(?i:host|url|dsn|endpoint|region|bucket|queue|secret|database|db_|key)" . | rg --pcre2 "$CALL" | "${NC[@]}"
echo '# 8 SDK calls whose default (the value on provider outage, a missing key, an uninitialised client) is true'
rg -n --pcre2 "${CODE[@]}" "${X[@]}" "${CALL}[^\n]*,\s*(?:\(\s*\)\s*=>\s*|lambda[^:\n]*:\s*|default(?:_value)?\s*=\s*|fallback(?:_function)?\s*=\s*(?:lambda[^:\n]*:\s*)?)?(?:true|True)\b(?:\s*\)|\s*,)" . | "${NC[@]}"
echo '# 9 build-time flags: public env inlined by the bundler; flag build args'
rg -n --pcre2 "${CODE[@]}" "${X[@]}" "(?:process\.env|import\.meta\.env)\.$PUB" . | "${NC[@]}"
rg -n --hidden --pcre2 -g 'Dockerfile*' -g '*.dockerfile' -g 'Containerfile*' -g '.github/workflows/*.{yml,yaml}' -g '**/.gitlab-ci*.yml' -g '!**/node_modules/**' "^\s*ARG\s+$ENVF|--build-arg[ =]+$ENVF|^\s*ARG\s+$PUB|--build-arg[ =]+$PUB" .
```

How to read it:
- **§5 rows** are one per Rollout, Canary, MetricTemplate and AnalysisTemplate metric, from the source (the citation) and from each render (what applies). `render-only:` marks a row an overlay or values file changed. Codes: `NEVER-FAILS`, `NO-CONDITION`, `LIMIT>=COUNT`, `NO-INCONCLUSIVE-LIMIT`, `DRYRUN`, `UNSCOPED`, `ZERO-FALLBACK` (an empty series reads as 0 and passes), `NO-ANALYSIS`, `BG-AUTO-PROMOTE`, `NO-METRICS`, `SKIP-ANALYSIS`. `scope=hash` means the query selects the canary's pods (`rollouts_pod_template_hash`); `args` means it is templated, so read what the Rollout passes (`podTemplateHashValue: Latest`, the canary Service).
- **5g** prints only the AL-10 rows of the scratch rule file; each `alert` name is `<file>:<line>:<metric>` of the source. Grounding counts code registrations, recording-rule outputs and exporter defaults (observability precedents § 37, § 43); `exporter-default (unverified)` asks whether that exporter runs.
- **1** `called, never declared` is the orphan list. The reference table prints only keys worth reading: no literal, a SCREAMING_SNAKE twin (an env var or a constant: read it), or no SDK call site. `HARNESS BROKEN` voids every DEAD row until the globs are fixed (a `.vue` or `.erb` codebase needs its extension in `CODE`).
- **4** `boot-read` is an env-var flag assigned at module scope: it is not a kill switch. The second list is SDK calls on risky keys; the read decides whether the flag guards the side effect.
- **6** labels a registry environment block `PROD-AHEAD` (prod on, a pre-prod environment off: prod runs a path nothing tested), `STAGED` (pre-prod ahead: the release order working), `DRIFT` (non-boolean values differ: read) or `RAMP` (precedents § 27).

Blind spots, worked around rather than trusted: a key passed through a constant or wrapper (`flags.get(FLAGS.NEW_CHECKOUT)`) has no literal at the call: the SCREAMING_SNAKE column and the wrapper's own call site catch most of it, the read settles the rest. A SaaS registry is invisible offline. ApplicationSet-generated Rollouts are not expanded. A Helm chart rendered with default values only shows the default environment: pass each values file to `helm template` when one exists per environment.

### Measured (2026-10-10)

| Tree | Result |
|---|---|
| Seeded sample (Argo Rollouts ×3 + 2 AnalysisTemplates, Flagger Canary ×3 + MetricTemplate, a Kayenta stage, a kustomize prod overlay, a PrometheusRule, flagd `flags.yaml` with 13 keys, OpenFeature/LaunchDarkly TS, Unleash Python, LaunchDarkly Go, Vite; 27 positives, 12 decoys) | 1.6 s, 0 rg errors; bash 3.2 and zsh identical. **27/27 positives surface**: NEVER-FAILS ×2 (Argo `>= 0`, Flagger `min: 0`), LIMIT>=COUNT ×2 (one **render-only**, from an overlay patch), UNSCOPED ×2, NO-INCONCLUSIVE-LIMIT, DRYRUN, NO-ANALYSIS, BG-AUTO-PROMOTE, NO-METRICS, SKIP-ANALYSIS, Kayenta `marginal: 0`, 1 ungrounded selector via `rule-inventory.py` (8 queries, 2-name inventory, 1 recording rule); a dead key, an orphan key, a hard-coded flag, 6 keys missing an expiry or owner, a boot-read refund flag, PROD-AHEAD ×2, DRIFT, a flag that picks the database host, default-`true` ×2 (OpenFeature, Unleash `fallback_function`), a Vite flag and a flag `--build-arg`. Decoys: a spec file and a fixture never appear; the synthetic-load template (§ 28), the ramp (§ 27), STAGED ×3, the permanent `maintenance-mode`, the inverted `disable-legacy-pricing` default, the recording-rule query (grounded) and the scoped success-rate metric surface and clear on read. Without the observability pack, 5g printed `NOT-RUN (owner pack observability not installed)` |
| `benchmarks/fixtures/*` (16) | 0 delivery objects, 0 registries, 0 SDK calls in all 16; 0 rg errors. One row in quality-mix: `FEATURE_NEW_PRORATION=true` in `.env.example` with no reader (info). The slow-marketplace `isEnabled(` lives in a test file and stays excluded |
| Real NestJS + React monorepo (read-only, `git status` clean before and after) | 1.7 s. No Rollout, Canary or flag SDK; a KEDA ScaledObject and two kustomize `Kustomization` build files are not delivery objects (0 rows). **One FP class fixed before shipping:** the first hard-coded-flag regex printed 20 ORM entity defaults (`isEnabled = true;` class fields); it now needs a module-scope `const`/`let`/`var`, a SCREAMING_SNAKE constant or a `static final boolean`, and prints 0. **One sibling defect measured:** an include glob placed after the exclusion array re-reads `*.spec.ts` (ripgrep's last glob wins), so every code grep here puts `CODE` first. §6: one env-var flag declared in one environment (no drift). Every § prints `N/A` honestly |

## The 3,000-hit path

1. **Intersect** with what ships: §5 rows in a render a CD target applies; flag rows in a service root a rendered workload runs. Dismiss the rest with one read each (a dev overlay, a fixture, a preview).
2. **Group** by unit (precedents § 5): one AnalysisTemplate or ClusterAnalysisTemplate referenced by N Rollouts, one MetricTemplate, one registry file, one SDK wrapper, one key.
3. **Rank:** a prod Rollout or Canary that can never fail; then a default-`true` or boot-read flag on a risky key; then the rest.
4. **Read the top 30 groups.**
5. **Run the bulk oracle once:** one `rule-inventory.py` call grounds every query; one unit test per SDK wrapper with a failing provider settles §8 for every call through it (V233).
6. **Ledger the rest** as `Live, unreviewed (k groups / n rows — <continuing command>)`. Never mark it `checked`.

## Scans for

| § | Classes | Default severity |
|---|---|---|
| 1 | Dead key (declared, never read) · orphan key (read, never declared: the default forever) | L (M when the orphan guards a risky path) |
| 2 | Stale flag: hard-coded in code, or on in every environment past its expiry | L |
| 3 | No owner, or no removal date on a release flag | L |
| 4 | No kill switch on a risky path (payments, refunds, deletes, migrations, outbound side effects): env-var or boot-read flag, or none | M (H on a prod money or data-loss path) |
| 5 | DP-7 canary without a real gate | H |
| 6 | Flag drift across environments (PROD-AHEAD, DRIFT) | M |
| 7 | A flag that rewires config, credentials or routing | M |
| 8 | DP-8 unsafe default on provider outage or a missing key | M (H on a risky path) |
| 9 | DP-8 build-time flag that is treated as runtime | M |

### 1. Dead and orphan keys

```
BAD:   flags.yaml  dead-banner: {…}          (no literal, no SDK call, no env twin anywhere in code)
       getBooleanValue('orders-v3-pagination', false)   (no registry entry: always false, in every environment)
GOOD:  delete the registry entry and the branch together · declare the key, or delete the call
```
**Read the code:** Is the key read by another repo or a mobile client (a shared flagd file)? Through a constant or wrapper? A `literal=0 snake≥1` row is usually an env var or a constant: follow it to §2, §4 or §9. **Finding when** the key has no reader in any repo the registry serves (dead), or the SDK reads a key no registry declares (orphan). The harness line must say `harness ok`. **Proof:** S — both ends cited (the registry line and the empty reference count, or the call and the empty registry).

### 2. Stale flags

```
BAD:   const ENABLE_NEW_SEARCH = true;   // rollout finished      (the flag is gone in all but name)
       enable-new-search: { staging: true, production: true }  added 2025-11, no expiry
GOOD:  remove the flag and the dead branch in one change
```
**Read the code:** Is it a permanent operational flag (kill switch, maintenance mode: precedents § 27)? Is the `added=` date real (a shallow clone)? **Finding when** a release flag is constant in every environment, or replaced by a literal. **Proof:** S.

### 3. No owner or removal date

**Read the code:** `type: permanent` / ops / ramp keys are exempt from the expiry. Does the team track owners elsewhere (a CODEOWNERS block on the registry, the SaaS registry's maintainer)? **Finding when** a release flag has neither. One finding per registry file with N keys (precedents § 5). **Proof:** S.

### 4. No kill switch on a risky path

```
BAD:   const ASYNC_REFUND = process.env.FEATURE_ASYNC_REFUND === 'true';   (module scope: a restart to flip)
GOOD:  await flags.getBooleanValue('async-refund', false, ctx)   evaluated per request, served by the provider
```
**Read the code:** Does the flag guard the side effect itself (the charge, the delete, the outbound call), or only a log line? Is the env var re-read per request (a config service that reloads)? Is there another runtime switch for the same path (a circuit breaker, a queue pause)? **Finding when** a risky path shipped in this change, or reachable in prod, can only be turned off by a deploy or a restart. **Proof (V233):** flip the flag in a scratch provider with the service running: the next request takes the old path, with no deploy.

### 5. Canary without a real gate (DP-7)

```
BAD:   successCondition: result[0] >= 0                        (an error ratio is never negative)
       count: 3, failureLimit: 5                               (3 failures never exceed 5)
       dryRun: [{metricName: experimental-p99}]                (measured, never counted)
       query: …{route="/checkout"}…                            (the whole service: stable traffic dilutes the canary)
       Flagger metrics: []  ·  thresholdRange: {min: 0}  ·  skipAnalysis: true
       strategy.blueGreen with no prePromotionAnalysis and autoPromotionEnabled left at its default
GOOD:  successCondition: result[0] <= 0.01 · failureLimit: 1 of count: 5 · rollouts_pod_template_hash="{{args.canary-hash}}"
       · every selector grounded · blueGreen autoPromotionEnabled: false or a prePromotionAnalysis
```
**Read the code:**
- Can the condition be false for any value the metric can take (a ratio in 0–1, a latency in seconds vs milliseconds, a percentage vs a fraction)?
- Is another metric in the same template able to fail (the gate is the template, not one metric)?
- Does the query select the canary (`scope=hash`; for `args`, what the Rollout passes)? With a traffic-routed canary, does it select the canary Service?
- Does the selector ground (5g)? An ungrounded selector with `or vector(0)` or `scalar(` reads as a perfect canary; without them, Argo records errors until `consecutiveErrorLimit` [K].
- Is the analysis synthetic by design (a `job` provider driving load: precedents § 28)? Is it a preview environment (§ 24)?

**Finding when** no metric of the gate can fail for the values the series can take, the gate never runs (NO-ANALYSIS, BG-AUTO-PROMOTE, SKIP-ANALYSIS, NO-METRICS), or its selector names a series that exists nowhere. **Proof (V232):** the static read shows a value that fails the condition; every selector grounds (observability `rule-inventory.py --check=selectors` when installed); a canary returning 20 % errors on a throwaway cluster aborts within the analysis window (L, isolated kubeconfig), else `NO-DATA(rollout)`.

### 6. Flag drift across environments

**Read the code:** Is it a ramp or a permanent ops flag (precedents § 27)? Is `STAGED` the release order (pre-prod first) — that is not a finding. Is the difference documented next to the key? **Finding when** prod enables a value no pre-prod environment runs (PROD-AHEAD), or a non-boolean value differs with no note. **Proof:** S — the two environment values with their lines.

### 7. A flag that rewires config

**Read the code:** Does flipping it change only a code path, or also a host, a credential, a region or a queue? Is the second target provisioned in every environment the flag can turn on? **Finding when** a flag's branch selects a different backend or secret, so a flip has a blast radius the flag's name does not say. **Proof:** S — the call and the config read it selects.

### 8. Unsafe default on provider outage (DP-8)

```
BAD:   await client.getBooleanValue('new-checkout', true, ctx)        (provider down → the new checkout for everyone)
       client.is_enabled("bulk-refund", ctx, fallback_function=lambda feature_name, context: True)
GOOD:  the default is the old, safe path: getBooleanValue('new-checkout', false, ctx)
```
**Read the code:** Is the name inverted, so `true` is the safe path (`disable-legacy-pricing`)? Does the provider serve a bootstrap file offline? Is the path risky (money, data, outbound side effects)? **Finding when** the default enables the new or risky path. **Proof (V233):** a unit test with a provider that throws (OpenFeature `NOOP`/error provider, an LD client in offline mode, an Unleash client with no toggles) gets the safe value.

### 9. Build-time flags

**Read the code:** Does the registry, a runbook or an incident plan describe this flag as flippable at runtime (`flags.yaml` "flip at runtime" beside a `VITE_FLAG_*`)? Is it a pure build switch the team deploys to change, by design? **Finding when** a flag used as a runtime switch is inlined at build or passed as a `--build-arg`. **Proof (V233):** flipping the registry value leaves the served bundle unchanged (`rg` the value in `dist/`), while a runtime-evaluated flag changes on the next request.

## Proof

The oracles are in [`proof-oracles.md`](../../../../snippets/proof-oracles.md) § DevOps. **Scope: scratch providers, unit tests, `${TMPDIR}` and throwaway kind clusters through an isolated `--kubeconfig=<scratch>` only — never the user's flag service, cluster or default kube context.**

| § | Oracle | Tier | V |
|---|---|---|---|
| 1, 2, 3, 6, 7 | Two-ended static trace (the declaration and the read; the two environment values) | S | — |
| 4, 8, 9 | Provider-error default: a failing provider returns the safe value; the kill switch flips without a deploy; a build-time value does not | R (unit) | V233 |
| 5 | Canary checks: the condition can fail (static), every selector grounds (observability), a 20 %-error canary aborts (L) | S + L | V232 |

## Output

```
progressive-delivery — lanes: argo-rollouts (3 Rollouts, 2 templates; render: base + prod), flagger (3 Canaries), kayenta (read),
  flags: flagd file (13 keys), openfeature, launchdarkly, unleash, env, vite · observability: installed (5g ran) · infrastructure: not installed

Findings: 3 · Unconfirmed: 2

1. deploy/k8s/base/analysis-api.yaml:11                                  [report-with-fix]   high
   §5 DP-7 — AnalysisTemplate api-canary metric error-rate: `successCondition: result[0] >= 0` on an error ratio
   (0–1) cannot fail; Rollout api (prod render) uses it as its only error gate. Proof: static (V232). Fix:
   `successCondition: result[0] <= 0.01`, `failureLimit: 1`.

2. deploy/k8s/overlays/prod/kustomization.yaml:11                       [report-with-fix]   high
   §5 DP-7 — render-only: the prod patch sets success-rate `failureLimit: 9` over `count: 5` (base is 1).
   Proof: render row (V232, static). Fix: drop the patch, or keep `failureLimit` below `count`.

3. apps/api/src/flags.ts:7                                               [report-flagged]    medium
   §8 DP-8 — `getBooleanValue('new-checkout', true, ctx)`: provider down → the new checkout for all users.
   Unproven link: no unit test with a failing provider. Settling test: V233. Fix: default `false`.

Verification: 41 candidates → 3 findings · 2 unconfirmed · 6 hardening · 0 pre-existing · 30 refuted (self-verified)

Class coverage:
  §1 dead / orphan keys        checked (13 keys, 11 calls, harness ok → 2 rows)
  §5 canary gate                checked (14 source rows, 1 render-only; 8 queries → 1 ungrounded)
  §8 unsafe default             checked (11 calls → 3 rows; 1 inverted name refuted)
  …one line for each of §2–§4, §6, §7, §9
```

`[report-with-fix]`: the object, its environment, the platform or SDK fact and the oracle are in hand, and the fix is local. `[report-flagged]`: one link is unproven — the grounding (observability absent), a SaaS registry, a `[K]` semantic, no unit test, a failed render. The row names the link and the settling test.

The coverage block lists **all nine** §s on every run: `checked (k → m)` only when the grep or the awk ran on a lane that is present; `N/A (no progressive-delivery object)` / `N/A (no flag registry or SDK)`; `blocked (<reason>)` for a failed render or `HARNESS BROKEN`; `Unconfirmed (registry not in repo)`; `Live, unreviewed (k groups / n rows)` for a partial review.

## False positives / precedents

The verifier reads [`precedents/devops.md`](../../../../snippets/precedents/devops.md) and [`refute-before-report.md`](../../../../snippets/refute-before-report.md) § 4. The ones this skill meets most:
- **§ 27** permanent operational flags are not stale, and a percentage ramp that differs across environments is the ramp working. **§ 28** a declared synthetic-load analysis is a real gate when its check can fail. **§ 24** preview environments. **§ 23** a GitOps CI that only commits manifests.
- **§ 5** one finding per template, MetricTemplate, registry file or SDK wrapper. **§ 4** pre-existing lines on a diff run never block. **§ 41** a tool that errored or parsed nothing is NOT-RUN.
- Observability precedents § 37 (a metric name in no inventory is fabricated: a proposed query that names one is Refuted) and § 16 (a mocked promtool test does not prove a series exists).

Read rules measured on the sample (2026-10-10): a `STAGED` difference is the release order, not drift; an inverted flag name whose `true` is the old path is not an unsafe default; a `job`-provider metric has no condition by design.

Rationalizations to reject:
- "The Rollout has an analysis": one that cannot fail is a slower full deploy.
- "We can always roll back": a rollback is a deploy; a kill switch is a flip.
- "The flag defaults to true because the feature is on in prod": the default is what the outage serves.
- "It is a flag, so we can turn it off": not when it is inlined at build or read at boot.

## When to run

- On any diff touching a Rollout, AnalysisTemplate, Canary, MetricTemplate, a Spinnaker pipeline, a flag registry, a flag SDK call, a `FEATURE_*` / `*_ENABLED` env var or a public build-time env var.
- Before a risky change ships behind a flag; quarterly as a flag-hygiene sweep (§1–§3).
- On every whole-repo `/devops-audit`, reporting all nine §s.

## Halt conditions

- **Do not report a critical or high row as confirmed until a separate verifier has refuted it.** That verifier is not the run that found it; it applies [`refute-before-report.md`](../../../../snippets/refute-before-report.md) with `precedents/devops.md`. Under `/devops-audit` that is its verification phase; standalone, run the protocol as a second pass and tag those rows `self-verified`. List the rest as Unconfirmed, Hardening, Pre-existing or Refuted; never drop them.
- **Halt on a finding missing its anchors:** the line, the object or key, the environment, the platform or SDK fact with its tag, and the fix.
- **No DEAD row without `harness ok`.** A broken harness reports every flag dead; "delete these 23 flags" cannot be taken back. A key a SaaS registry holds is `UNREADABLE` offline.
- **No "selector does not exist" without the grounding run**, and no grounding claim without observability's script: without it the row stays `[report-flagged]`.
- **Never run V232 or V233 against the user's flag service, cluster or default kube context.** Every `kubectl` takes an isolated `--kubeconfig=<scratch>`.
- **No proposed fix may weaken a gate to clear a row:** no raised `failureLimit`, no metric moved into `dryRun`, no `skipAnalysis`, no default flipped to `true`.
- **Do not print "rollouts are gated" or "flags are clean" without the coverage block.** **Do not re-derive a class another skill owns** (§ Boundaries); cross-link it.

## Related

- `/devops-audit` dispatches this skill on a flag config, Rollout or AnalysisTemplate and folds its coverage block into the delivery rows; `/devops-fix` closes them with V232 and V233. `/rollback-deploy` asks whether a kill switch exists before it moves an artifact.
- `@deployment-engineer` judges these rows and picks the strategy; `monitor-deploy` watches one rollout; `deploy-safety-scan`, `release-integrity-scan` and `gitops-audit` (this pack) own the neighbouring classes.
- Observability `alert-rule-lint` §4 (the grounding script), infrastructure `k8s-availability-scan` and `k8s-render-scan` §4, database `migration-safety` §4, security `secret-scan-method` — each when its pack is installed.

## Sources

Classes DP-7 and DP-8, the Argo Rollouts v1.10.0 / Flagger v1.45.0 / OpenFeature spec v0.9.0 versions and the `[K]` semantics (Inconclusive pauses, provider defaults) from the devops research catalog (release and deploy reliability notes, 2026-10-10) and the batch-4 devops eval (kept-skill scope). The grounding technique is observability's (`alert-rule-lint` §4, measured there). Rows tagged `[K]`/`[K?]` are settled on the `ci-defects` fixture or a sandbox before a fix relies on them. Paraphrased; no text reproduced beyond identifiers.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.