@windyroad/risk-scorer 0.3.4 → 0.3.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.
|
@@ -33,25 +33,41 @@ for arg in "$@"; do
|
|
|
33
33
|
done
|
|
34
34
|
|
|
35
35
|
# --- Fast hash inputs mode: output only metadata for drift detection hash ---
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
36
|
+
# Tree-based hash — stable across BOTH commit and push operations (P054).
|
|
37
|
+
# Captures the conceptual "HEAD + index + working tree" state by asking git
|
|
38
|
+
# to build a tree object that reflects what a commit of the current state
|
|
39
|
+
# would contain. This makes the hash invariant over:
|
|
40
|
+
# - git commit (content moves from index to HEAD; tree SHA is identical)
|
|
41
|
+
# - git push (HEAD is unchanged locally; tree SHA is identical)
|
|
42
|
+
# The previous approach hashed `git diff origin/main --stat`, which shrinks
|
|
43
|
+
# to empty after a push advances origin/main, producing spurious drift
|
|
44
|
+
# denials on npm run release:watch even though the commits being released
|
|
45
|
+
# were the same ones already scored.
|
|
39
46
|
if [ "$SHOW_HASH_INPUTS" = true ]; then
|
|
40
|
-
#
|
|
41
|
-
# This is stable across commit and push operations — content moves between
|
|
42
|
-
# pipeline stages (staged → committed → pushed) without changing the hash.
|
|
43
|
-
# Previous approach used git diff HEAD + git diff origin/main..HEAD which
|
|
44
|
-
# broke on every commit because content shifted between the two diffs.
|
|
45
|
-
# Exclude docs/governance paths from hash — they cannot affect the running application
|
|
47
|
+
# Exclude docs/governance paths — they cannot affect the running application.
|
|
46
48
|
EXCL=$(_doc_exclusions)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
# git stash create writes a commit object (tree + parents) representing
|
|
50
|
+
# index + working tree, without touching HEAD, the index, or any refs.
|
|
51
|
+
# Returns empty when there is nothing to stash (clean tree).
|
|
52
|
+
STASH_COMMIT=$(git stash create 2>/dev/null || true)
|
|
53
|
+
if [ -n "$STASH_COMMIT" ]; then
|
|
54
|
+
CONCEPTUAL_TREE=$(git rev-parse "${STASH_COMMIT}^{tree}" 2>/dev/null || echo "")
|
|
51
55
|
else
|
|
52
|
-
|
|
56
|
+
CONCEPTUAL_TREE=$(git rev-parse HEAD^{tree} 2>/dev/null || echo "")
|
|
53
57
|
fi
|
|
54
|
-
|
|
58
|
+
if [ -n "$CONCEPTUAL_TREE" ]; then
|
|
59
|
+
# Diff against the empty tree to enumerate every file in the
|
|
60
|
+
# conceptual tree with its blob SHA (shown as "added"). git diff
|
|
61
|
+
# supports `:!` pathspec exclusions where `git ls-tree` does not,
|
|
62
|
+
# so this is the path that honours the doc exclusions above.
|
|
63
|
+
# Same-content trees produce identical output regardless of which
|
|
64
|
+
# ref points at them, which is what gives push stability.
|
|
65
|
+
# 4b825dc642cb6eb9a060e54bf8d69288fbee4904 is git's well-known empty-tree SHA.
|
|
66
|
+
eval "git diff --raw 4b825dc642cb6eb9a060e54bf8d69288fbee4904 $CONCEPTUAL_TREE -- $EXCL" 2>/dev/null || true
|
|
67
|
+
fi
|
|
68
|
+
# Changeset count (affects release/changeset risk — tracked separately
|
|
69
|
+
# because .changeset/ is in the doc-exclusions list and therefore not
|
|
70
|
+
# reflected in the tree listing above).
|
|
55
71
|
if [ -d ".changeset" ]; then
|
|
56
72
|
find .changeset -name '*.md' -not -name 'README.md' 2>/dev/null | wc -l | tr -d ' '
|
|
57
73
|
fi
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Tests for pipeline-state.sh --hash-inputs stability
|
|
3
|
+
# Covers P054: the hash must be stable across both commit AND push so the
|
|
4
|
+
# release gate does not fire spurious "state drift" denials after a
|
|
5
|
+
# policy-authorised push advances origin/main.
|
|
6
|
+
|
|
7
|
+
setup() {
|
|
8
|
+
HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
9
|
+
SCRIPT="$HOOKS_DIR/lib/pipeline-state.sh"
|
|
10
|
+
|
|
11
|
+
# Set up a temp git repo with an origin remote so origin/main is meaningful.
|
|
12
|
+
TEST_DIR="$(mktemp -d)"
|
|
13
|
+
REMOTE_DIR="$(mktemp -d)"
|
|
14
|
+
|
|
15
|
+
# Bare remote
|
|
16
|
+
(cd "$REMOTE_DIR" && git init --bare --initial-branch=main >/dev/null 2>&1)
|
|
17
|
+
|
|
18
|
+
# Working repo
|
|
19
|
+
cd "$TEST_DIR"
|
|
20
|
+
git init --initial-branch=main >/dev/null 2>&1
|
|
21
|
+
git config user.email "test@example.com"
|
|
22
|
+
git config user.name "Test"
|
|
23
|
+
git remote add origin "$REMOTE_DIR"
|
|
24
|
+
|
|
25
|
+
# Seed initial commit
|
|
26
|
+
echo "initial" > README.md
|
|
27
|
+
git add README.md
|
|
28
|
+
git commit -m "initial" >/dev/null 2>&1
|
|
29
|
+
git push -u origin main >/dev/null 2>&1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
teardown() {
|
|
33
|
+
rm -rf "$TEST_DIR" "$REMOTE_DIR"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Helper: compute the hash of --hash-inputs output in the current directory.
|
|
37
|
+
compute_hash() {
|
|
38
|
+
bash "$SCRIPT" --hash-inputs 2>/dev/null | shasum -a 256 | cut -d' ' -f1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@test "hash is stable across git commit of staged changes" {
|
|
42
|
+
cd "$TEST_DIR"
|
|
43
|
+
|
|
44
|
+
echo "line 1" > feature.ts
|
|
45
|
+
git add feature.ts
|
|
46
|
+
|
|
47
|
+
HASH_BEFORE=$(compute_hash)
|
|
48
|
+
|
|
49
|
+
git commit -m "add feature" >/dev/null 2>&1
|
|
50
|
+
|
|
51
|
+
HASH_AFTER=$(compute_hash)
|
|
52
|
+
|
|
53
|
+
[ "$HASH_BEFORE" = "$HASH_AFTER" ] || {
|
|
54
|
+
echo "Hash changed on commit."
|
|
55
|
+
echo "before: $HASH_BEFORE"
|
|
56
|
+
echo "after: $HASH_AFTER"
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@test "hash is stable across git push (P054 regression guard)" {
|
|
62
|
+
cd "$TEST_DIR"
|
|
63
|
+
|
|
64
|
+
echo "line 1" > feature.ts
|
|
65
|
+
git add feature.ts
|
|
66
|
+
git commit -m "add feature" >/dev/null 2>&1
|
|
67
|
+
|
|
68
|
+
HASH_BEFORE=$(compute_hash)
|
|
69
|
+
|
|
70
|
+
git push origin main >/dev/null 2>&1
|
|
71
|
+
|
|
72
|
+
HASH_AFTER=$(compute_hash)
|
|
73
|
+
|
|
74
|
+
[ "$HASH_BEFORE" = "$HASH_AFTER" ] || {
|
|
75
|
+
echo "Hash changed on push — this is the P054 regression."
|
|
76
|
+
echo "before: $HASH_BEFORE"
|
|
77
|
+
echo "after: $HASH_AFTER"
|
|
78
|
+
return 1
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@test "hash is stable across full commit-then-push sequence" {
|
|
83
|
+
cd "$TEST_DIR"
|
|
84
|
+
|
|
85
|
+
echo "line 1" > feature.ts
|
|
86
|
+
git add feature.ts
|
|
87
|
+
|
|
88
|
+
HASH_STAGED=$(compute_hash)
|
|
89
|
+
|
|
90
|
+
git commit -m "add feature" >/dev/null 2>&1
|
|
91
|
+
HASH_COMMITTED=$(compute_hash)
|
|
92
|
+
|
|
93
|
+
git push origin main >/dev/null 2>&1
|
|
94
|
+
HASH_PUSHED=$(compute_hash)
|
|
95
|
+
|
|
96
|
+
[ "$HASH_STAGED" = "$HASH_COMMITTED" ] || {
|
|
97
|
+
echo "Hash changed on commit step."
|
|
98
|
+
return 1
|
|
99
|
+
}
|
|
100
|
+
[ "$HASH_COMMITTED" = "$HASH_PUSHED" ] || {
|
|
101
|
+
echo "Hash changed on push step."
|
|
102
|
+
return 1
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@test "hash changes when a new tracked file is edited" {
|
|
107
|
+
cd "$TEST_DIR"
|
|
108
|
+
|
|
109
|
+
echo "v1" > feature.ts
|
|
110
|
+
git add feature.ts
|
|
111
|
+
git commit -m "v1" >/dev/null 2>&1
|
|
112
|
+
git push origin main >/dev/null 2>&1
|
|
113
|
+
|
|
114
|
+
HASH_BEFORE=$(compute_hash)
|
|
115
|
+
|
|
116
|
+
echo "v2" > feature.ts
|
|
117
|
+
|
|
118
|
+
HASH_AFTER=$(compute_hash)
|
|
119
|
+
|
|
120
|
+
[ "$HASH_BEFORE" != "$HASH_AFTER" ] || {
|
|
121
|
+
echo "Hash did not change on content edit."
|
|
122
|
+
return 1
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@test "hash changes when a new changeset is added" {
|
|
127
|
+
cd "$TEST_DIR"
|
|
128
|
+
|
|
129
|
+
mkdir -p .changeset
|
|
130
|
+
HASH_BEFORE=$(compute_hash)
|
|
131
|
+
|
|
132
|
+
cat > .changeset/abc.md <<'CS'
|
|
133
|
+
---
|
|
134
|
+
"@windyroad/itil": patch
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
Fix something.
|
|
138
|
+
CS
|
|
139
|
+
|
|
140
|
+
HASH_AFTER=$(compute_hash)
|
|
141
|
+
|
|
142
|
+
[ "$HASH_BEFORE" != "$HASH_AFTER" ] || {
|
|
143
|
+
echo "Hash did not change on new changeset."
|
|
144
|
+
return 1
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@test "hash is stable with no upstream remote (works on a fresh repo)" {
|
|
149
|
+
# Fresh repo with no remote tracking branch.
|
|
150
|
+
NO_REMOTE_DIR="$(mktemp -d)"
|
|
151
|
+
cd "$NO_REMOTE_DIR"
|
|
152
|
+
git init --initial-branch=main >/dev/null 2>&1
|
|
153
|
+
git config user.email "test@example.com"
|
|
154
|
+
git config user.name "Test"
|
|
155
|
+
echo "init" > README.md
|
|
156
|
+
git add README.md
|
|
157
|
+
git commit -m "init" >/dev/null 2>&1
|
|
158
|
+
|
|
159
|
+
# Should not error out, should emit a hash input
|
|
160
|
+
run bash "$SCRIPT" --hash-inputs
|
|
161
|
+
[ "$status" -eq 0 ]
|
|
162
|
+
[ -n "$output" ]
|
|
163
|
+
|
|
164
|
+
# Two consecutive calls should produce the same hash (deterministic)
|
|
165
|
+
HASH_A=$(compute_hash)
|
|
166
|
+
HASH_B=$(compute_hash)
|
|
167
|
+
[ "$HASH_A" = "$HASH_B" ]
|
|
168
|
+
|
|
169
|
+
rm -rf "$NO_REMOTE_DIR"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@test "hash is stable with clean working tree (no stash-create content)" {
|
|
173
|
+
cd "$TEST_DIR"
|
|
174
|
+
# Working tree is clean after setup's push. Two consecutive calls are stable.
|
|
175
|
+
HASH_A=$(compute_hash)
|
|
176
|
+
HASH_B=$(compute_hash)
|
|
177
|
+
[ "$HASH_A" = "$HASH_B" ] || {
|
|
178
|
+
echo "Hash is non-deterministic on clean tree."
|
|
179
|
+
return 1
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@test "hash is stable with dirty working tree (uncommitted edits)" {
|
|
184
|
+
cd "$TEST_DIR"
|
|
185
|
+
echo "dirty" > work.txt
|
|
186
|
+
git add work.txt
|
|
187
|
+
echo "more dirty" > unstaged.txt
|
|
188
|
+
|
|
189
|
+
HASH_A=$(compute_hash)
|
|
190
|
+
HASH_B=$(compute_hash)
|
|
191
|
+
[ "$HASH_A" = "$HASH_B" ] || {
|
|
192
|
+
echo "Hash is non-deterministic on dirty tree."
|
|
193
|
+
return 1
|
|
194
|
+
}
|
|
195
|
+
}
|