Skip to content

Commit 9cf9da1

Browse files
committed
feat(sdk-review): batch 3/6 — safety-first checks
Adds the 5 highest-value check scripts: ## Checks (.claude/scripts/) - check-secrets.sh — SEC-01..10 (AWS, JWT, GitHub PAT, Google, Slack, private keys, .env files). All BLOCK_LOCKED (non-suppressible). - check-license-spdx.sh — LIC-01/02 (SPDX + copyright headers on new files). Respects REUSE.toml aggregate at repo root — auto-exempted here. - check-disclosure.sh — DIS-01..08 (SAP-internal URLs, Jira leaks, internal artifactory, unfilled PR template placeholders). Public/internal profile switch. DIS-06 BLOCK_LOCKED. - check-hardcode.sh — HC-01..06 (URLs, credentials, magic timeouts, direct os.environ access). HC-03 (SAP-internal in public repo) BLOCK_LOCKED. - check-telemetry.sh — PY-TEL-01..07 (@record_metrics on public *Client methods; test asserts emission). Uses ast_python_checks.py from batch 1. All checks are self-review protected via lib/skill-self-skip.sh — the skill won't fire on its own regex/fixture strings. **Standalone testable.** Each check reads a diff on stdin and emits JSON. Not yet wired into an orchestrator; that comes in batch 6. Part 3 of 6. Ref: AFSDK-3937
1 parent 0c8b36f commit 9cf9da1

5 files changed

Lines changed: 394 additions & 0 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
# check-disclosure.sh — open-source disclosure hygiene.
3+
# Detects SAP-internal URLs, ORD IDs, internal Jira, unfilled PR body templates.
4+
set -euo pipefail
5+
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
# shellcheck source=lib/json-emit.sh
8+
source "$SCRIPT_DIR/lib/json-emit.sh"
9+
# shellcheck source=lib/skill-self-skip.sh
10+
source "$SCRIPT_DIR/lib/skill-self-skip.sh"
11+
12+
LANGUAGE="${LANGUAGE:-python}"
13+
PROFILE="${DISCLOSURE_PROFILE:-public}" # "public" or "internal"
14+
DIFF_FILE="${DIFF_FILE:-/dev/stdin}"
15+
PR_BODY_FILE="${PR_BODY_FILE:-}"
16+
17+
STARTED=$(now_iso)
18+
findings=$(mktemp)
19+
trap 'rm -f "$findings"' EXIT
20+
21+
diff_content=$(cat "$DIFF_FILE" 2>/dev/null || echo "")
22+
23+
# Determine severity based on profile
24+
sev_public() { if [ "$PROFILE" = "public" ]; then echo "BLOCK"; else echo "FLAG"; fi; }
25+
sev_internal_ok() { if [ "$PROFILE" = "public" ]; then echo "BLOCK"; else echo "PASS"; fi; }
26+
27+
# Scan added lines only
28+
echo "$diff_content" | awk '
29+
BEGIN { file=""; line=0 }
30+
/^diff --git a\// { file=$4; sub(/^b\//, "", file); line=0; next }
31+
/^@@/ { if (match($0, /\+[0-9]+/)) line=substr($0, RSTART+1, RLENGTH-1)+0; next }
32+
/^\+/ && !/^\+\+\+/ { print file "\t" line "\t" substr($0, 2); line++; next }
33+
/^ / { line++; next }
34+
' | while IFS=$'\t' read -r file line_num content; do
35+
[ -z "$file" ] && continue
36+
# Self-review protection: skip skill files
37+
if [ "$(is_skill_file "$file")" = "true" ]; then continue; fi
38+
39+
# DIS-01: SAP-internal hostnames
40+
if echo "$content" | grep -qEi '(int\.repositories\.cloud\.sap|\.tools\.sap|\.wdf\.sap\.corp|\.mo\.sap\.corp|jira\.tools\.sap)'; then
41+
emit_finding "DIS-01" "$(sev_public)" "$file" "$line_num" "SAP-internal hostname detected in code" "" >> "$findings"
42+
fi
43+
# DIS-02: Internal Jira URL
44+
if echo "$content" | grep -qEi 'jira\.tools\.sap|jira\.wdf\.sap\.corp'; then
45+
emit_finding "DIS-02" "$(sev_public)" "$file" "$line_num" "Internal Jira URL — remove or move to internal-only docs" "" >> "$findings"
46+
fi
47+
# DIS-06: Internal artifactory index-url
48+
if echo "$content" | grep -qEi '\-\-index-url\s+https?://int\.repositories\.cloud\.sap'; then
49+
emit_finding "DIS-06" "BLOCK" "$file" "$line_num" "Internal artifactory --index-url — must not appear in public/shared configs" "" >> "$findings"
50+
fi
51+
done
52+
53+
# DIS-07/08: PR body
54+
if [ -n "$PR_BODY_FILE" ] && [ -f "$PR_BODY_FILE" ]; then
55+
body=$(cat "$PR_BODY_FILE")
56+
# DIS-07: unfilled Closes #<issue_number> placeholder
57+
if echo "$body" | grep -qE 'Closes #<issue_number>'; then
58+
emit_finding "DIS-07" "BLOCK" "PR_BODY" 1 "PR body contains unfilled 'Closes #<issue_number>' placeholder" "" >> "$findings"
59+
fi
60+
# DIS-08 (SHADOW): internal URLs / Jira in PR body
61+
if echo "$body" | grep -qEi '\.tools\.sap|\.wdf\.sap\.corp|jira\.tools\.sap'; then
62+
emit_finding "DIS-08" "FLAG" "PR_BODY" 1 "PR body references SAP-internal URLs/Jira — remove from public-visible artifacts" "" >> "$findings"
63+
fi
64+
fi
65+
66+
status=$(status_from_findings < "$findings")
67+
emit_report "disclosure" "$LANGUAGE" "$status" "$STARTED" < "$findings"

.claude/scripts/check-hardcode.sh

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
# check-hardcode.sh — no hardcoded URLs, credentials, or magic values in impl code.
3+
set -euo pipefail
4+
5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
# shellcheck source=lib/json-emit.sh
7+
source "$SCRIPT_DIR/lib/json-emit.sh"
8+
# shellcheck source=lib/skill-self-skip.sh
9+
source "$SCRIPT_DIR/lib/skill-self-skip.sh"
10+
11+
LANGUAGE="${LANGUAGE:-python}"
12+
DIFF_FILE="${DIFF_FILE:-/dev/stdin}"
13+
14+
STARTED=$(now_iso)
15+
findings=$(mktemp)
16+
trap 'rm -f "$findings"' EXIT
17+
18+
diff_content=$(cat "$DIFF_FILE" 2>/dev/null || echo "")
19+
20+
# Determine ignore patterns per language
21+
if [ "$LANGUAGE" = "python" ]; then
22+
ignore_files='^(tests?/|mocks?/|docs?/|.*/spec/|.*/constants\.py|.*/user-guide\.md|README\.md)'
23+
else
24+
ignore_files='^(src/test/|mocks?/|docs?/|.*Constants\.java|.*/constants/|.*/user-guide\.md|README\.md)'
25+
fi
26+
27+
echo "$diff_content" | awk '
28+
BEGIN { file=""; line=0 }
29+
/^diff --git a\// { file=$4; sub(/^b\//, "", file); line=0; next }
30+
/^@@/ { if (match($0, /\+[0-9]+/)) line=substr($0, RSTART+1, RLENGTH-1)+0; next }
31+
/^\+/ && !/^\+\+\+/ { print file "\t" line "\t" substr($0, 2); line++; next }
32+
/^ / { line++; next }
33+
' | while IFS=$'\t' read -r file line_num content; do
34+
[ -z "$file" ] && continue
35+
if [ "$(is_skill_file "$file")" = "true" ]; then continue; fi
36+
# Filter out test/mock/docs/constants files
37+
if echo "$file" | grep -qE "$ignore_files"; then continue; fi
38+
39+
# HC-01: hardcoded URL
40+
if echo "$content" | grep -qE 'https?://[a-zA-Z0-9]'; then
41+
# exempt localhost / example.com / example.org (test-safe)
42+
if ! echo "$content" | grep -qE 'https?://(localhost|127\.0\.0\.1|example\.(com|org|net)|reserved\.)'; then
43+
emit_finding "HC-01" "BLOCK" "$file" "$line_num" "Hardcoded URL in implementation — externalize to config" "" >> "$findings"
44+
fi
45+
fi
46+
# HC-02: Authorization Bearer
47+
if echo "$content" | grep -qE 'Authorization[[:space:]]*:[[:space:]]*Bearer[[:space:]]+[A-Za-z0-9]'; then
48+
emit_finding "HC-02" "BLOCK" "$file" "$line_num" "Hardcoded Authorization header value" "" >> "$findings"
49+
fi
50+
# HC-04: direct os.environ / System.getenv
51+
if [ "$LANGUAGE" = "python" ]; then
52+
if echo "$content" | grep -qE 'os\.environ\["[A-Z]'; then
53+
emit_finding "HC-04" "FLAG" "$file" "$line_num" "Direct os.environ access — prefer secret_resolver / config layer" "" >> "$findings"
54+
fi
55+
else
56+
if echo "$content" | grep -qE 'System\.getenv\('; then
57+
emit_finding "HC-04" "FLAG" "$file" "$line_num" "Direct System.getenv() — prefer SecretResolver" "" >> "$findings"
58+
fi
59+
fi
60+
# HC-06: hardcoded timeout numeric literal
61+
if echo "$content" | grep -qiE '(timeout|retries|max_retries)[[:space:]]*=[[:space:]]*[0-9]+'; then
62+
emit_finding "HC-06" "FLAG" "$file" "$line_num" "Hardcoded timeout/retry number — externalize to config" "" >> "$findings"
63+
fi
64+
done
65+
66+
status=$(status_from_findings < "$findings")
67+
emit_report "hardcode" "$LANGUAGE" "$status" "$STARTED" < "$findings"
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env bash
2+
# check-license-spdx.sh — verify new source files have SPDX headers.
3+
# REUSE.toml aggregate → rule PASSES (baseline exemption).
4+
set -euo pipefail
5+
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
# shellcheck source=lib/json-emit.sh
8+
source "$SCRIPT_DIR/lib/json-emit.sh"
9+
# shellcheck source=lib/predicates.sh
10+
source "$SCRIPT_DIR/lib/predicates.sh"
11+
12+
LANGUAGE="${LANGUAGE:-python}"
13+
REPO_ROOT="${REPO_ROOT:-.}"
14+
DIFF_FILE="${DIFF_FILE:-/dev/stdin}"
15+
16+
STARTED=$(now_iso)
17+
findings=$(mktemp)
18+
trap 'rm -f "$findings"' EXIT
19+
20+
# Predicate: REUSE.toml aggregate present?
21+
reuse_present=$(reuse_toml_aggregate_present "$REPO_ROOT")
22+
23+
diff_content=$(cat "$DIFF_FILE" 2>/dev/null || echo "")
24+
25+
if [ "$reuse_present" = "true" ]; then
26+
# Baseline exemption: rule OFF for this repo
27+
jq -n --arg check "license-spdx" --arg lang "$LANGUAGE" --arg started "$STARTED" \
28+
'{
29+
check: $check, version: "1.0.0", language: $lang, status: "PASS",
30+
started_at: $started, duration_ms: 0, modules_analysed: [],
31+
findings: [], shadow_findings: [],
32+
summary: {block_count: 0, flag_count: 0, suppressed_by_baseline: 0,
33+
note: "REUSE.toml aggregate present — LIC-01/02 exempted"}
34+
}'
35+
exit 0
36+
fi
37+
38+
# Find newly added files (starts with "diff --git a/... b/..." followed by "new file mode")
39+
# We match on "+++ b/<path>" lines that appear right after "new file mode"
40+
added_files=$(echo "$diff_content" | awk '
41+
/^diff --git/ { in_block=1; is_new=0; path=""; next }
42+
in_block && /^new file mode/ { is_new=1; next }
43+
in_block && /^\+\+\+ b\// { if (is_new) print substr($0, 7); in_block=0 }
44+
')
45+
46+
# REUSE-IgnoreStart
47+
if [ "$LANGUAGE" = "python" ]; then
48+
ext_match='\.py$'
49+
spdx_line='# SPDX-License-Identifier: Apache-2.0'
50+
cprt_line='# SPDX-FileCopyrightText:'
51+
else
52+
ext_match='\.java$'
53+
spdx_line='// SPDX-License-Identifier: Apache-2.0'
54+
cprt_line='// SPDX-FileCopyrightText:'
55+
fi
56+
# REUSE-IgnoreEnd
57+
58+
while IFS= read -r f; do
59+
[ -z "$f" ] && continue
60+
if ! echo "$f" | grep -qE "$ext_match"; then continue; fi
61+
# Read first 10 lines from the diff for that file to check headers
62+
header=$(echo "$diff_content" | awk -v file="$f" '
63+
$0 == "+++ b/" file { flag=1; count=0; next }
64+
flag && /^\+/ && !/^\+\+\+/ { print substr($0, 2); count++; if (count>=10) exit }
65+
flag && /^diff --git/ { exit }
66+
')
67+
if ! echo "$header" | grep -qF "$spdx_line"; then
68+
emit_finding "LIC-01" "BLOCK" "$f" 1 "New source file missing SPDX-License-Identifier header" "" >> "$findings"
69+
fi
70+
if ! echo "$header" | grep -qF "$cprt_line"; then
71+
emit_finding "LIC-02" "BLOCK" "$f" 1 "New source file missing SPDX-FileCopyrightText header" "" >> "$findings"
72+
fi
73+
done <<< "$added_files"
74+
75+
status=$(status_from_findings < "$findings")
76+
emit_report "license-spdx" "$LANGUAGE" "$status" "$STARTED" < "$findings"

.claude/scripts/check-secrets.sh

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env bash
2+
# check-secrets.sh — detect secrets (AWS keys, JWTs, GitHub tokens, private keys, etc.) in added lines.
3+
# All SEC-* rules are BLOCK_LOCKED (cannot be suppressed).
4+
set -euo pipefail
5+
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
# shellcheck source=lib/json-emit.sh
8+
source "$SCRIPT_DIR/lib/json-emit.sh"
9+
# shellcheck source=lib/skill-self-skip.sh
10+
source "$SCRIPT_DIR/lib/skill-self-skip.sh"
11+
12+
LANGUAGE="${LANGUAGE:-python}"
13+
DIFF_FILE="${DIFF_FILE:-/dev/stdin}"
14+
15+
STARTED=$(now_iso)
16+
17+
# Read diff (either from env-provided file or stdin) — extract added lines with file+line info
18+
diff_content=$(cat "$DIFF_FILE" 2>/dev/null || echo "")
19+
20+
if [ -z "$diff_content" ]; then
21+
emit_report "secrets" "$LANGUAGE" "PASS" "$STARTED" <<< ""
22+
exit 0
23+
fi
24+
25+
findings=$(mktemp)
26+
trap 'rm -f "$findings"' EXIT
27+
28+
# Parse diff and scan each added line
29+
echo "$diff_content" | awk '
30+
BEGIN { file=""; line=0 }
31+
/^diff --git a\// {
32+
file=$4
33+
sub(/^b\//, "", file)
34+
line=0
35+
next
36+
}
37+
/^@@/ {
38+
if (match($0, /\+[0-9]+/)) line=substr($0, RSTART+1, RLENGTH-1)+0
39+
next
40+
}
41+
/^\+/ && !/^\+\+\+/ {
42+
print file "\t" line "\t" substr($0, 2)
43+
line++
44+
next
45+
}
46+
/^ / { line++; next }
47+
' | while IFS=$'\t' read -r file line_num content; do
48+
[ -z "$file" ] && continue
49+
# Self-review protection: skip skill files
50+
if [ "$(is_skill_file "$file")" = "true" ]; then continue; fi
51+
52+
# SEC-01: AWS Access Key
53+
if echo "$content" | grep -qE 'AKIA[0-9A-Z]{16}'; then
54+
emit_finding "SEC-01" "BLOCK" "$file" "$line_num" "AWS Access Key detected — remove immediately and rotate the key" "" >> "$findings"
55+
fi
56+
# SEC-02: Google API Key
57+
if echo "$content" | grep -qE 'AIza[0-9A-Za-z_-]{35}'; then
58+
emit_finding "SEC-02" "BLOCK" "$file" "$line_num" "Google API Key detected — remove and rotate" "" >> "$findings"
59+
fi
60+
# SEC-03: GitHub PAT
61+
if echo "$content" | grep -qE 'gh[pousr]_[A-Za-z0-9_]{36,}'; then
62+
emit_finding "SEC-03" "BLOCK" "$file" "$line_num" "GitHub PAT detected — remove and rotate" "" >> "$findings"
63+
fi
64+
# SEC-04: OpenAI key
65+
if echo "$content" | grep -qE 'sk-[A-Za-z0-9]{20,}'; then
66+
emit_finding "SEC-04" "BLOCK" "$file" "$line_num" "OpenAI-like API key detected — remove and rotate" "" >> "$findings"
67+
fi
68+
# SEC-05: Slack bot token
69+
if echo "$content" | grep -qE 'xox[baprs]-[A-Za-z0-9-]+'; then
70+
emit_finding "SEC-05" "BLOCK" "$file" "$line_num" "Slack token detected — remove and rotate" "" >> "$findings"
71+
fi
72+
# SEC-06: JWT
73+
if echo "$content" | grep -qE 'eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}'; then
74+
emit_finding "SEC-06" "BLOCK" "$file" "$line_num" "JWT token detected — remove and rotate" "" >> "$findings"
75+
fi
76+
# SEC-07: Private key header
77+
if echo "$content" | grep -qE 'BEGIN (RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY'; then
78+
emit_finding "SEC-07" "BLOCK" "$file" "$line_num" "Private key header detected — remove and rotate" "" >> "$findings"
79+
fi
80+
# SEC-08: BTP client_secret literal (heuristic: assignment with looks-like-secret value)
81+
# Match client_secret= or clientsecret= with quoted values that look like real secrets
82+
if echo "$content" | grep -qE '"clientsecret"\s*:\s*"[A-Za-z0-9+/=]{20,}"'; then
83+
emit_finding "SEC-08" "BLOCK" "$file" "$line_num" "BTP client_secret literal detected — use secret resolver" "" >> "$findings"
84+
fi
85+
done
86+
87+
# SEC-10: .env files in diff (not .env.example, .env.test, .env.sample)
88+
env_lines=$(echo "$diff_content" | grep -E '^\+\+\+ b/\.env(\..+)?$' || true)
89+
while IFS= read -r line; do
90+
[ -z "$line" ] && continue
91+
# extract path from "+++ b/<path>"
92+
path="${line#+++ b/}"
93+
# skip allowed suffixes
94+
case "$path" in
95+
.env.example|.env.test|.env.sample|.env.template) continue ;;
96+
.env|.env.*) emit_finding "SEC-10" "BLOCK" "$path" 1 ".env file committed — never commit .env; use .env.example" "" >> "$findings" ;;
97+
esac
98+
done <<< "$env_lines"
99+
100+
status=$(status_from_findings < "$findings")
101+
emit_report "secrets" "$LANGUAGE" "$status" "$STARTED" < "$findings"

.claude/scripts/check-telemetry.sh

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env bash
2+
# check-telemetry.sh — verify telemetry instrumentation.
3+
# Python: @record_metrics on public *Client methods + emission tests.
4+
# Java: Telemetry.executeWithTelemetry(...) wrapping + tests.
5+
set -euo pipefail
6+
7+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
# shellcheck source=lib/json-emit.sh
9+
source "$SCRIPT_DIR/lib/json-emit.sh"
10+
11+
LANGUAGE="${LANGUAGE:-python}"
12+
REPO_ROOT="${REPO_ROOT:-.}"
13+
DIFF_FILE="${DIFF_FILE:-/dev/stdin}"
14+
15+
STARTED=$(now_iso)
16+
findings=$(mktemp)
17+
trap 'rm -f "$findings"' EXIT
18+
19+
diff_content=$(cat "$DIFF_FILE" 2>/dev/null || echo "")
20+
21+
# Get list of client files newly added or modified
22+
if [ "$LANGUAGE" = "python" ]; then
23+
client_files=$(echo "$diff_content" | grep -oE '^\+\+\+ b/src/sap_cloud_sdk/[a-z_]+/.*Client\.py|^\+\+\+ b/src/sap_cloud_sdk/[a-z_]+/client\.py' | sed 's|^+++ b/||' | sort -u)
24+
else
25+
client_files=$(echo "$diff_content" | grep -oE '^\+\+\+ b/src/main/java/com/sap/cloud/sdk/[a-z_]+/.*Client\.java' | sed 's|^+++ b/||' | sort -u)
26+
fi
27+
28+
# Detect new decorator additions (added lines with @record_metrics for Python, executeWithTelemetry for Java)
29+
if [ "$LANGUAGE" = "python" ]; then
30+
new_decorators=$(echo "$diff_content" | grep -E '^\+[[:space:]]*@record_metrics' | wc -l | tr -d ' ')
31+
else
32+
new_decorators=$(echo "$diff_content" | grep -E '^\+.*Telemetry\.executeWithTelemetry' | wc -l | tr -d ' ')
33+
fi
34+
35+
# PY-TEL-02: For each changed client file, run AST check
36+
if [ "$LANGUAGE" = "python" ] && [ -n "$client_files" ]; then
37+
# shellcheck disable=SC2086
38+
python3 "$SCRIPT_DIR/lib/ast_python_checks.py" tel-02 $client_files 2>/dev/null >> "$findings" || true
39+
fi
40+
41+
# JV-TEL-02: For Java, grep-based check (executeWithTelemetry wrap around methods)
42+
if [ "$LANGUAGE" = "java" ] && [ -n "$client_files" ]; then
43+
while IFS= read -r f; do
44+
[ -z "$f" ] && continue
45+
full_path="$REPO_ROOT/$f"
46+
[ -f "$full_path" ] || continue
47+
# find public methods in the file
48+
while IFS= read -r match; do
49+
line_num="${match%%:*}"
50+
# check the following 20 lines for executeWithTelemetry
51+
end_line=$((line_num + 20))
52+
body=$(sed -n "${line_num},${end_line}p" "$full_path")
53+
if ! echo "$body" | grep -q "executeWithTelemetry"; then
54+
emit_finding "JV-TEL-02" "BLOCK" "$f" "$line_num" \
55+
"Public method lacks Telemetry.executeWithTelemetry wrap" "" >> "$findings"
56+
fi
57+
done < <(grep -nE '^[[:space:]]*public [A-Za-z<>]+ [a-z][a-zA-Z0-9]+\(' "$full_path" 2>/dev/null | grep -v 'public class\|public interface\|public enum' || true)
58+
done <<< "$client_files"
59+
fi
60+
61+
# PY-TEL-06 / JV-TEL-05: emission tests required when new decorator/wrapper added
62+
if [ "$new_decorators" -gt 0 ]; then
63+
if [ "$LANGUAGE" = "python" ]; then
64+
# Find test files added/modified in same PR
65+
test_files=$(echo "$diff_content" | grep -oE '^\+\+\+ b/tests/[a-z_]+/.*/test_.*\.py' | sed 's|^+++ b/||' | sort -u)
66+
has_metric_assert=false
67+
while IFS= read -r tf; do
68+
[ -z "$tf" ] && continue
69+
[ -f "$REPO_ROOT/$tf" ] || continue
70+
if grep -q 'record_request_metric\|record_error_metric' "$REPO_ROOT/$tf"; then
71+
has_metric_assert=true; break
72+
fi
73+
done <<< "$test_files"
74+
if [ "$has_metric_assert" = "false" ]; then
75+
emit_finding "PY-TEL-06" "BLOCK" "tests/" 1 \
76+
"New @record_metrics added ($new_decorators occurrences) but no test asserts record_request_metric was called" \
77+
"Add a test that mocks record_request_metric and asserts it was called with the expected Module/Operation" >> "$findings"
78+
fi
79+
fi
80+
fi
81+
82+
status=$(status_from_findings < "$findings")
83+
emit_report "telemetry" "$LANGUAGE" "$status" "$STARTED" < "$findings"

0 commit comments

Comments
 (0)