@intentsolutions/audit-harness 0.1.0 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +364 -0
- package/LICENSE +202 -21
- package/NOTICE +15 -0
- package/README.md +36 -4
- package/bin/audit-harness.js +18 -8
- package/package.json +11 -9
- package/scripts/arch-check.sh +25 -1
- package/scripts/bias-count.sh +50 -4
- package/scripts/crap-score.py +65 -5
- package/scripts/emit-evidence.sh +256 -0
- package/scripts/escape-scan.sh +58 -4
- package/scripts/gherkin-lint.sh +53 -9
- package/scripts/harness-hash.sh +78 -5
package/scripts/escape-scan.sh
CHANGED
|
@@ -19,14 +19,32 @@
|
|
|
19
19
|
# bash escape-scan.sh path/to/change.patch
|
|
20
20
|
# bash escape-scan.sh --staged # git diff --cached
|
|
21
21
|
# bash escape-scan.sh --range HEAD~1..HEAD
|
|
22
|
+
# bash escape-scan.sh --staged --json # machine-readable JSON to stdout
|
|
23
|
+
#
|
|
24
|
+
# JSON mode:
|
|
25
|
+
# stdout = single JSON object suitable for piping to `audit-harness emit-evidence`
|
|
26
|
+
# stderr = unchanged human-readable [SEVERITY] notes (preserves backward-compat)
|
|
27
|
+
# exit codes unchanged
|
|
22
28
|
|
|
23
29
|
set -euo pipefail
|
|
24
30
|
|
|
25
31
|
DIFF_SRC=""
|
|
26
32
|
VERIFY_HASH=1
|
|
33
|
+
JSON_OUT=0
|
|
27
34
|
ROOT="${ROOT:-$(pwd)}"
|
|
28
35
|
HASH_SCRIPT="$(dirname "$0")/harness-hash.sh"
|
|
29
36
|
|
|
37
|
+
# First-pass arg parse: peel --json off the tail (any position) so primary
|
|
38
|
+
# arg parsing below is unchanged.
|
|
39
|
+
_filtered_args=()
|
|
40
|
+
for arg in "$@"; do
|
|
41
|
+
case "$arg" in
|
|
42
|
+
--json) JSON_OUT=1 ;;
|
|
43
|
+
*) _filtered_args+=("$arg") ;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
set -- "${_filtered_args[@]+"${_filtered_args[@]}"}"
|
|
47
|
+
|
|
30
48
|
if [[ "$#" -eq 0 ]]; then
|
|
31
49
|
echo "escape-scan: pass a diff source (- for stdin, --staged, --range, or a patch file)" >&2
|
|
32
50
|
exit 2
|
|
@@ -34,11 +52,20 @@ fi
|
|
|
34
52
|
|
|
35
53
|
case "$1" in
|
|
36
54
|
-) DIFF_SRC="/dev/stdin" ;;
|
|
37
|
-
--staged)
|
|
38
|
-
|
|
55
|
+
--staged)
|
|
56
|
+
DIFF_SRC=$(mktemp)
|
|
57
|
+
trap 'rm -f "$DIFF_SRC"' EXIT
|
|
58
|
+
git diff --cached > "$DIFF_SRC"
|
|
59
|
+
;;
|
|
60
|
+
--range)
|
|
61
|
+
DIFF_SRC=$(mktemp)
|
|
62
|
+
trap 'rm -f "$DIFF_SRC"' EXIT
|
|
63
|
+
git diff "$2" > "$DIFF_SRC"
|
|
64
|
+
shift
|
|
65
|
+
;;
|
|
39
66
|
--no-hash) VERIFY_HASH=0; shift; DIFF_SRC="$1" ;;
|
|
40
67
|
--help|-h)
|
|
41
|
-
sed -n '2,
|
|
68
|
+
sed -n '2,26p' "$0"; exit 0 ;;
|
|
42
69
|
*) DIFF_SRC="$1" ;;
|
|
43
70
|
esac
|
|
44
71
|
|
|
@@ -159,7 +186,34 @@ if echo "$added_lines" | grep -Eq 'toBeDefined\(\)|\.is not None'; then
|
|
|
159
186
|
fi
|
|
160
187
|
|
|
161
188
|
# --- Summary & exit ---
|
|
162
|
-
|
|
189
|
+
if [[ "$JSON_OUT" -eq 1 ]]; then
|
|
190
|
+
# Result mapping (per intent-eval-lab evidence-bundle SPEC § 5 R6):
|
|
191
|
+
# any REFUSE → FAIL
|
|
192
|
+
# any CHALLENGE (no REFUSE) → FAIL (exit 1 = blocking, requires human)
|
|
193
|
+
# only FLAG → ADVISORY (exit 0 — informational)
|
|
194
|
+
# none → PASS
|
|
195
|
+
result="PASS"
|
|
196
|
+
if [[ "$REFUSE" -gt 0 || "$CHALLENGE" -gt 0 ]]; then
|
|
197
|
+
result="FAIL"
|
|
198
|
+
elif [[ "$FLAG" -gt 0 ]]; then
|
|
199
|
+
result="ADVISORY"
|
|
200
|
+
fi
|
|
201
|
+
input_hash=$(sha256sum "$DIFF_SRC" | awk '{print "sha256:"$1}')
|
|
202
|
+
policy_hash="sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
203
|
+
if [[ -f "$TESTING_MD" ]]; then
|
|
204
|
+
policy_hash=$(sha256sum "$TESTING_MD" | awk '{print "sha256:"$1}')
|
|
205
|
+
fi
|
|
206
|
+
printf '{"gate_id":"audit-harness:%s:escape-scan","result":"%s","input_hash":"%s","policy_hash":"%s","metadata":{"refuse":%d,"challenge":%d,"flag":%d,"coverage_line_floor":%d,"coverage_branch_floor":%d,"mutation_floor":%d}' \
|
|
207
|
+
"${AUDIT_HARNESS_SIDE:-ci}" "$result" "$input_hash" "$policy_hash" "$REFUSE" "$CHALLENGE" "$FLAG" \
|
|
208
|
+
"$COVERAGE_LINE_FLOOR" "$COVERAGE_BRANCH_FLOOR" "$MUTATION_FLOOR"
|
|
209
|
+
if [[ "$result" == "ADVISORY" ]]; then
|
|
210
|
+
printf ',"advisory_severity":"info"'
|
|
211
|
+
fi
|
|
212
|
+
printf '}\n'
|
|
213
|
+
echo "escape-scan: REFUSE=$REFUSE CHALLENGE=$CHALLENGE FLAG=$FLAG" >&2
|
|
214
|
+
else
|
|
215
|
+
echo "escape-scan: REFUSE=$REFUSE CHALLENGE=$CHALLENGE FLAG=$FLAG"
|
|
216
|
+
fi
|
|
163
217
|
if [[ "$REFUSE" -gt 0 ]]; then
|
|
164
218
|
echo "escape-scan: pipeline halted (REFUSE)" >&2
|
|
165
219
|
exit 2
|
package/scripts/gherkin-lint.sh
CHANGED
|
@@ -15,11 +15,13 @@ set -euo pipefail
|
|
|
15
15
|
|
|
16
16
|
PATH_ARG="features/"
|
|
17
17
|
STRICT=0
|
|
18
|
+
JSON_OUT=0
|
|
18
19
|
|
|
19
20
|
while [[ $# -gt 0 ]]; do
|
|
20
21
|
case "$1" in
|
|
21
22
|
--path) PATH_ARG="$2"; shift 2 ;;
|
|
22
23
|
--strict) STRICT=1; shift ;;
|
|
24
|
+
--json) JSON_OUT=1; shift ;;
|
|
23
25
|
--help|-h)
|
|
24
26
|
sed -n '2,15p' "$0"; exit 0 ;;
|
|
25
27
|
*) echo "gherkin-lint: unknown flag $1" >&2; exit 2 ;;
|
|
@@ -27,15 +29,40 @@ while [[ $# -gt 0 ]]; do
|
|
|
27
29
|
done
|
|
28
30
|
|
|
29
31
|
if [[ ! -d "$PATH_ARG" ]]; then
|
|
32
|
+
if [[ "$JSON_OUT" -eq 1 ]]; then
|
|
33
|
+
printf '{"gate_id":"audit-harness:%s:gherkin-lint","result":"NOT_APPLICABLE","input_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","policy_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","metadata":{"reason":"path not found","path":"%s"}}\n' \
|
|
34
|
+
"${AUDIT_HARNESS_SIDE:-ci}" "$PATH_ARG"
|
|
35
|
+
fi
|
|
30
36
|
echo "gherkin-lint: path not found: $PATH_ARG" >&2
|
|
31
37
|
exit 2
|
|
32
38
|
fi
|
|
33
39
|
|
|
40
|
+
INPUT_HASH=$(find "$PATH_ARG" -name "*.feature" -type f -exec sha256sum {} \; 2>/dev/null | sort | sha256sum | awk '{print "sha256:"$1}')
|
|
41
|
+
|
|
42
|
+
if [[ "$JSON_OUT" -eq 1 ]]; then
|
|
43
|
+
exec 3>&1
|
|
44
|
+
exec 1>&2
|
|
45
|
+
fi
|
|
46
|
+
|
|
34
47
|
WARN_COUNT=0
|
|
35
48
|
ERROR_COUNT=0
|
|
36
49
|
|
|
37
50
|
warn() { echo "WARN $1:$2 $3"; WARN_COUNT=$((WARN_COUNT + 1)); }
|
|
38
|
-
|
|
51
|
+
|
|
52
|
+
# process_awk_output — funnel awk-printed WARN/ERROR lines through the bash
|
|
53
|
+
# counters so the summary + exit code reflect awk-fallback findings (the
|
|
54
|
+
# subprocesses below can't otherwise touch the parent-shell counters).
|
|
55
|
+
# Single-pass awk counts both at once; no-match handled cleanly under
|
|
56
|
+
# set -euo pipefail via the `+0` numeric coercions.
|
|
57
|
+
process_awk_output() {
|
|
58
|
+
local out="$1"
|
|
59
|
+
[ -z "$out" ] && return 0
|
|
60
|
+
local w=0 e=0
|
|
61
|
+
read -r w e < <(awk '/^WARN /{w++} /^ERROR /{e++} END {print w+0, e+0}' <<< "$out")
|
|
62
|
+
WARN_COUNT=$((WARN_COUNT + w))
|
|
63
|
+
ERROR_COUNT=$((ERROR_COUNT + e))
|
|
64
|
+
printf '%s\n' "$out"
|
|
65
|
+
}
|
|
39
66
|
|
|
40
67
|
# 1. Prefer official gherkin-lint if available
|
|
41
68
|
if command -v gherkin-lint >/dev/null 2>&1; then
|
|
@@ -48,7 +75,7 @@ else
|
|
|
48
75
|
|
|
49
76
|
while IFS= read -r -d '' feature; do
|
|
50
77
|
# Imperative verbs / CSS selectors in steps (declarative warning)
|
|
51
|
-
awk -v file="$feature" '
|
|
78
|
+
process_awk_output "$(awk -v file="$feature" '
|
|
52
79
|
/^[[:space:]]*(Given|When|Then|And|But)/ {
|
|
53
80
|
line = $0
|
|
54
81
|
if (line ~ /click|type|fill[ _]in|press|select.*from[ _]dropdown/) {
|
|
@@ -58,10 +85,10 @@ else
|
|
|
58
85
|
printf "WARN %s:%d CSS selector / xpath in step (prefer business language)\n", file, NR
|
|
59
86
|
}
|
|
60
87
|
}
|
|
61
|
-
' "$feature"
|
|
88
|
+
' "$feature")"
|
|
62
89
|
|
|
63
90
|
# Scenario length (> 10 steps)
|
|
64
|
-
awk -v file="$feature" '
|
|
91
|
+
process_awk_output "$(awk -v file="$feature" '
|
|
65
92
|
/^[[:space:]]*Scenario/ { sc = NR; steps = 0; sn = $0; next }
|
|
66
93
|
/^[[:space:]]*(Given|When|Then|And|But)/ { if (sc) steps++ }
|
|
67
94
|
/^[[:space:]]*Scenario|^[[:space:]]*Feature|^$/ {
|
|
@@ -75,7 +102,7 @@ else
|
|
|
75
102
|
printf "WARN %s:%d scenario has %d steps (>10 is too long)\n", file, sc, steps
|
|
76
103
|
}
|
|
77
104
|
}
|
|
78
|
-
' "$feature"
|
|
105
|
+
' "$feature")"
|
|
79
106
|
|
|
80
107
|
# Repeated Givens without Background (3+ identical Given lines)
|
|
81
108
|
dupe=$(awk '/^[[:space:]]*Given/ { print }' "$feature" | sort | uniq -c | awk '$1 >= 3 { print }')
|
|
@@ -84,9 +111,7 @@ else
|
|
|
84
111
|
fi
|
|
85
112
|
|
|
86
113
|
# "And" at scenario start (grammar error)
|
|
87
|
-
awk -v file="$feature" '
|
|
88
|
-
prev_blank = 1
|
|
89
|
-
/^[[:space:]]*$/ { prev_blank = 1; next }
|
|
114
|
+
process_awk_output "$(awk -v file="$feature" '
|
|
90
115
|
/^[[:space:]]*Scenario/ { in_scenario = 1; step_count = 0; next }
|
|
91
116
|
/^[[:space:]]*(Given|When|Then|And|But)/ {
|
|
92
117
|
if (in_scenario && step_count == 0 && /^[[:space:]]*And/) {
|
|
@@ -94,7 +119,7 @@ else
|
|
|
94
119
|
}
|
|
95
120
|
step_count++
|
|
96
121
|
}
|
|
97
|
-
' "$feature"
|
|
122
|
+
' "$feature")"
|
|
98
123
|
|
|
99
124
|
done < <(find "$PATH_ARG" -name "*.feature" -print0)
|
|
100
125
|
fi
|
|
@@ -102,6 +127,25 @@ fi
|
|
|
102
127
|
echo ""
|
|
103
128
|
echo "gherkin-lint summary: $WARN_COUNT warning(s), $ERROR_COUNT error(s)"
|
|
104
129
|
|
|
130
|
+
if [[ "$JSON_OUT" -eq 1 ]]; then
|
|
131
|
+
exec 1>&3 3>&-
|
|
132
|
+
result="PASS"
|
|
133
|
+
sev_block=""
|
|
134
|
+
if [[ "$ERROR_COUNT" -gt 0 ]]; then
|
|
135
|
+
result="FAIL"
|
|
136
|
+
elif [[ "$WARN_COUNT" -gt 0 ]]; then
|
|
137
|
+
if [[ "$STRICT" -eq 1 ]]; then
|
|
138
|
+
result="FAIL"
|
|
139
|
+
else
|
|
140
|
+
result="ADVISORY"
|
|
141
|
+
sev_block=',"advisory_severity":"warn"'
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
printf '{"gate_id":"audit-harness:%s:gherkin-lint","result":"%s"%s,"input_hash":"%s","policy_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","metadata":{"warnings":%d,"errors":%d,"strict":%s,"path":"%s"}}\n' \
|
|
145
|
+
"${AUDIT_HARNESS_SIDE:-ci}" "$result" "$sev_block" "$INPUT_HASH" "$WARN_COUNT" "$ERROR_COUNT" \
|
|
146
|
+
"$([[ "$STRICT" -eq 1 ]] && echo true || echo false)" "$PATH_ARG"
|
|
147
|
+
fi
|
|
148
|
+
|
|
105
149
|
if [[ "$ERROR_COUNT" -gt 0 ]]; then
|
|
106
150
|
exit 1
|
|
107
151
|
fi
|
package/scripts/harness-hash.sh
CHANGED
|
@@ -6,19 +6,48 @@
|
|
|
6
6
|
# causes escape-scan.sh to REFUSE the AI diff.
|
|
7
7
|
#
|
|
8
8
|
# Usage:
|
|
9
|
-
# bash harness-hash.sh --init
|
|
10
|
-
# bash harness-hash.sh --verify
|
|
11
|
-
# bash harness-hash.sh --
|
|
9
|
+
# bash harness-hash.sh --init # write manifest (engineer-initiated)
|
|
10
|
+
# bash harness-hash.sh --verify # compare current hashes to manifest
|
|
11
|
+
# bash harness-hash.sh --verify --json # machine-readable JSON to stdout (verify only)
|
|
12
|
+
# bash harness-hash.sh --list # show which files are pinned
|
|
12
13
|
#
|
|
13
14
|
# Exit codes:
|
|
14
15
|
# 0 — OK (pin matches, or init succeeded)
|
|
15
16
|
# 2 — HARNESS_TAMPERED (hash mismatch)
|
|
16
17
|
# 3 — no manifest found (--verify without --init)
|
|
18
|
+
#
|
|
19
|
+
# JSON mode:
|
|
20
|
+
# stdout = single JSON object suitable for piping to `audit-harness emit-evidence`
|
|
21
|
+
# stderr = unchanged human-readable summary (preserves backward-compat)
|
|
22
|
+
# exit codes unchanged
|
|
17
23
|
|
|
18
24
|
set -euo pipefail
|
|
19
25
|
|
|
26
|
+
# Cross-platform SHA-256: `sha256sum` ships with GNU coreutils (Linux);
|
|
27
|
+
# macOS only has `shasum -a 256`. Both produce identical `<hash> <file>`
|
|
28
|
+
# output, so downstream awk parsing is unchanged.
|
|
29
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
30
|
+
SHA256_CMD=(sha256sum)
|
|
31
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
32
|
+
SHA256_CMD=(shasum -a 256)
|
|
33
|
+
else
|
|
34
|
+
echo "harness-hash: neither sha256sum nor shasum found in PATH" >&2
|
|
35
|
+
exit 2
|
|
36
|
+
fi
|
|
37
|
+
|
|
20
38
|
ROOT="${ROOT:-$(pwd)}"
|
|
21
39
|
MANIFEST="${ROOT}/.harness-hash"
|
|
40
|
+
JSON_OUT=0
|
|
41
|
+
|
|
42
|
+
# Peel --json from anywhere in args (additive, doesn't disturb existing arg shape)
|
|
43
|
+
_filtered_args=()
|
|
44
|
+
for arg in "$@"; do
|
|
45
|
+
case "$arg" in
|
|
46
|
+
--json) JSON_OUT=1 ;;
|
|
47
|
+
*) _filtered_args+=("$arg") ;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
set -- "${_filtered_args[@]+"${_filtered_args[@]}"}"
|
|
22
51
|
|
|
23
52
|
PATTERNS=(
|
|
24
53
|
# Wall 1: acceptance
|
|
@@ -42,6 +71,27 @@ PATTERNS=(
|
|
|
42
71
|
"stryker.config.js"
|
|
43
72
|
)
|
|
44
73
|
|
|
74
|
+
# Optional per-repo extra patterns appended from .harness-hash-extra-patterns
|
|
75
|
+
# at the repo root. Used by repos whose policy files don't match the default
|
|
76
|
+
# canonical patterns above — e.g., the audit-harness repo itself pins its own
|
|
77
|
+
# scripts (scripts/*.sh + scripts/*.py + bin/audit-harness.js), which are the
|
|
78
|
+
# policy enforcement surface but aren't covered by the consumer-facing
|
|
79
|
+
# defaults. Lines beginning with `#` are comments; blank lines are ignored.
|
|
80
|
+
# This mechanism is additive — repos without the file get exactly the
|
|
81
|
+
# default behavior, so consumer repos are not affected.
|
|
82
|
+
EXTRA_PATTERNS_FILE="${ROOT}/.harness-hash-extra-patterns"
|
|
83
|
+
if [[ -f "${EXTRA_PATTERNS_FILE}" ]]; then
|
|
84
|
+
while IFS= read -r line || [[ -n "${line}" ]]; do
|
|
85
|
+
# strip inline comments
|
|
86
|
+
line="${line%%#*}"
|
|
87
|
+
# trim leading + trailing whitespace
|
|
88
|
+
line="${line#"${line%%[![:space:]]*}"}"
|
|
89
|
+
line="${line%"${line##*[![:space:]]}"}"
|
|
90
|
+
[[ -z "${line}" ]] && continue
|
|
91
|
+
PATTERNS+=("${line}")
|
|
92
|
+
done < "${EXTRA_PATTERNS_FILE}"
|
|
93
|
+
fi
|
|
94
|
+
|
|
45
95
|
collect_files() {
|
|
46
96
|
local out=()
|
|
47
97
|
shopt -s nullglob globstar
|
|
@@ -61,7 +111,7 @@ hash_files() {
|
|
|
61
111
|
return 0
|
|
62
112
|
fi
|
|
63
113
|
while IFS= read -r f; do
|
|
64
|
-
printf '%s %s\n' "$(
|
|
114
|
+
printf '%s %s\n' "$("${SHA256_CMD[@]}" "$f" | awk '{print $1}')" "$f"
|
|
65
115
|
done <<< "$files"
|
|
66
116
|
}
|
|
67
117
|
|
|
@@ -76,6 +126,10 @@ cmd_init() {
|
|
|
76
126
|
cmd_verify() {
|
|
77
127
|
cd "$ROOT"
|
|
78
128
|
if [[ ! -f "$MANIFEST" ]]; then
|
|
129
|
+
if [[ "$JSON_OUT" -eq 1 ]]; then
|
|
130
|
+
printf '{"gate_id":"audit-harness:%s:harness-hash","result":"NOT_APPLICABLE","input_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","policy_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","metadata":{"reason":"no manifest at %s (run --init)"}}\n' \
|
|
131
|
+
"${AUDIT_HARNESS_SIDE:-ci}" "$MANIFEST"
|
|
132
|
+
fi
|
|
79
133
|
echo "harness-hash: no manifest at $MANIFEST (run --init)" >&2
|
|
80
134
|
exit 3
|
|
81
135
|
fi
|
|
@@ -84,13 +138,32 @@ cmd_verify() {
|
|
|
84
138
|
local expected
|
|
85
139
|
expected=$(cat "$MANIFEST")
|
|
86
140
|
|
|
141
|
+
local manifest_hash
|
|
142
|
+
manifest_hash=$("${SHA256_CMD[@]}" "$MANIFEST" | awk '{print "sha256:"$1}')
|
|
143
|
+
|
|
144
|
+
local pinned_count
|
|
145
|
+
pinned_count=$(echo "$expected" | grep -c '^' || true)
|
|
146
|
+
|
|
87
147
|
# Compare sorted manifests so order doesn't matter
|
|
88
148
|
local diff_out
|
|
89
149
|
diff_out=$(diff <(echo "$expected" | sort) <(echo "$current" | sort) || true)
|
|
90
150
|
if [[ -z "$diff_out" ]]; then
|
|
91
|
-
|
|
151
|
+
if [[ "$JSON_OUT" -eq 1 ]]; then
|
|
152
|
+
printf '{"gate_id":"audit-harness:%s:harness-hash","result":"PASS","input_hash":"%s","policy_hash":"%s","metadata":{"pinned_count":%d}}\n' \
|
|
153
|
+
"${AUDIT_HARNESS_SIDE:-ci}" "$manifest_hash" "$manifest_hash" "$pinned_count"
|
|
154
|
+
echo "harness-hash: OK" >&2
|
|
155
|
+
else
|
|
156
|
+
echo "harness-hash: OK"
|
|
157
|
+
fi
|
|
92
158
|
exit 0
|
|
93
159
|
fi
|
|
160
|
+
if [[ "$JSON_OUT" -eq 1 ]]; then
|
|
161
|
+
# diff output may contain quotes/newlines; encode as a single-line escaped string
|
|
162
|
+
local diff_escaped
|
|
163
|
+
diff_escaped=$(printf '%s' "$diff_out" | python3 -c 'import sys, json; print(json.dumps(sys.stdin.read()))')
|
|
164
|
+
printf '{"gate_id":"audit-harness:%s:harness-hash","result":"FAIL","failure_mode":"HARNESS_TAMPERED","input_hash":"%s","policy_hash":"%s","metadata":{"pinned_count":%d,"diff":%s}}\n' \
|
|
165
|
+
"${AUDIT_HARNESS_SIDE:-ci}" "$manifest_hash" "$manifest_hash" "$pinned_count" "$diff_escaped"
|
|
166
|
+
fi
|
|
94
167
|
echo "HARNESS_TAMPERED: pinned artifact changed" >&2
|
|
95
168
|
echo "$diff_out" >&2
|
|
96
169
|
exit 2
|