@metasession.co/devaudit-cli 0.3.12 → 0.3.14
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/dist/index.js +53 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/upload-evidence.sh +55 -1
- package/sdlc/files/_common/0-project-setup.md +10 -4
- package/sdlc/files/_common/Periodic_Security_Review_Schedule.md +5 -5
- package/sdlc/files/_common/README_TEMPLATE.md +8 -8
- package/sdlc/files/_common/Test_Architecture.md +15 -15
- package/sdlc/files/_common/Test_Plan_TEMPLATE.md +13 -13
- package/sdlc/files/_common/Test_Policy.md +2 -2
- package/sdlc/files/_common/Test_Strategy.md +10 -10
- package/sdlc/files/_common/governance/ai-disclosure.md.template +3 -3
- package/sdlc/files/_common/governance/dpia.md.template +3 -3
- package/sdlc/files/_common/governance/incident-report.md.template +3 -3
- package/sdlc/files/_common/governance/nil-incident-report.md.template +1 -1
- package/sdlc/files/_common/governance/periodic-review.md.template +3 -3
- package/sdlc/files/_common/governance/risk-register.md.template +1 -1
- package/sdlc/files/_common/governance/ropa.md.template +2 -2
- package/sdlc/files/_common/joining-an-existing-project.md +4 -4
- package/sdlc/files/_common/scripts/check-host-deployment.sh +107 -0
- package/sdlc/files/_common/scripts/check-host-deployment.test.sh +119 -0
- package/sdlc/files/_common/scripts/check-release-pr-scope.sh +95 -0
- package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +137 -0
- package/sdlc/files/_common/scripts/close-out-release.sh +175 -54
- package/sdlc/files/_common/scripts/close-out-release.test.sh +90 -0
- package/sdlc/files/_common/scripts/extract-release-metadata.sh +27 -0
- package/sdlc/files/_common/scripts/extract-release-metadata.test.sh +13 -1
- package/sdlc/files/_common/scripts/generate-bundled-changes.sh +311 -64
- package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +133 -82
- package/sdlc/files/_common/scripts/generate-housekeeping-release-ticket.sh +18 -9
- package/sdlc/files/_common/scripts/generate-housekeeping-release-ticket.test.sh +66 -0
- package/sdlc/files/_common/scripts/generate-security-summary.sh +14 -5
- package/sdlc/files/_common/scripts/generate-security-summary.test.sh +57 -0
- package/sdlc/files/_common/scripts/prepare-release-pr.sh +44 -4
- package/sdlc/files/_common/scripts/render-test-cycles.sh +227 -0
- package/sdlc/files/_common/scripts/render-test-cycles.test.sh +107 -0
- package/sdlc/files/_common/scripts/report-release-check.sh +86 -0
- package/sdlc/files/_common/scripts/report-release-check.test.sh +97 -0
- package/sdlc/files/_common/scripts/report-test-cycle.sh +344 -0
- package/sdlc/files/_common/scripts/submit-bundle-manifest.sh +100 -0
- package/sdlc/files/_common/scripts/submit-bundle-manifest.test.sh +138 -0
- package/sdlc/files/_common/scripts/upload-evidence.sh +24 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +16 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.test.sh +14 -0
- package/sdlc/files/_common/scripts/validate-test-summary.test.sh +4 -4
- package/sdlc/files/_common/skills/adr-author/SKILL.md +2 -2
- package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +2 -2
- package/sdlc/files/_common/skills/e2e-test-engineer/references/bootstrap.md +5 -5
- package/sdlc/files/_common/skills/governance-doc-author/references/incident-classification.md +1 -1
- package/sdlc/files/_common/skills/requirements-aligner/SKILL.md +4 -4
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +21 -2
- package/sdlc/files/_common/skills/sdlc-implementer/references/change-request-loop.md +1 -1
- package/sdlc/files/_common/skills/sdlc-implementer/references/compliance-constraints.md +7 -14
- package/sdlc/files/ci/ci.yml.template +119 -28
- package/sdlc/files/ci/close-out-release.yml.template +7 -4
- package/sdlc/files/ci/compliance-evidence.yml.template +230 -20
- package/sdlc/files/ci/feature-e2e.yml.template +84 -1
- package/sdlc/files/ci/post-deploy-prod.yml.template +189 -6
- package/sdlc/files/ci/python/ci.yml.template +60 -12
- package/sdlc/files/ci/quality-gates-provenance.yml.template +123 -28
- package/sdlc/package.json +1 -1
- package/sdlc/src/bin/devaudit-sdlc.js +44 -2
- package/sdlc/src/blueprints/1-plan-requirement.raw.md +6 -1
- package/sdlc/src/blueprints/2-implement-and-test.raw.md +1 -1
- package/sdlc/src/blueprints/3-compile-evidence.raw.md +34 -20
- package/sdlc/src/blueprints/4-submit-for-review.raw.md +2 -2
- package/sdlc/src/blueprints/5-deploy-main.raw.md +2 -2
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# generate-bundled-changes.test.sh — Tests for generate-bundled-changes.sh.
|
|
3
|
-
#
|
|
4
|
-
# Builds a throwaway git repo per case, crafts commits with mixed types,
|
|
5
|
-
# runs the helper against it, asserts on the stdout content. Hermetic:
|
|
6
|
-
# runs inside mktemp'd directories that are torn down at the end.
|
|
7
|
-
#
|
|
8
|
-
# Usage:
|
|
9
|
-
# ./scripts/generate-bundled-changes.test.sh
|
|
10
3
|
|
|
11
4
|
set -euo pipefail
|
|
12
5
|
|
|
@@ -20,7 +13,6 @@ FAIL=0
|
|
|
20
13
|
TMPDIR_BASE="$(mktemp -d)"
|
|
21
14
|
trap 'rm -rf "$TMPDIR_BASE"' EXIT
|
|
22
15
|
|
|
23
|
-
# Build a fresh git fixture with an initial commit.
|
|
24
16
|
make_fixture() {
|
|
25
17
|
local dir="$1"
|
|
26
18
|
rm -rf "$dir"
|
|
@@ -34,7 +26,6 @@ make_fixture() {
|
|
|
34
26
|
git commit -q -m "feat: initial commit [REQ-001]"
|
|
35
27
|
}
|
|
36
28
|
|
|
37
|
-
# Add a commit with a given message.
|
|
38
29
|
add_commit() {
|
|
39
30
|
local msg="$1"
|
|
40
31
|
echo "$(date +%s%N)" >> f.txt
|
|
@@ -42,6 +33,29 @@ add_commit() {
|
|
|
42
33
|
git commit -q -m "$msg"
|
|
43
34
|
}
|
|
44
35
|
|
|
36
|
+
write_release_ticket() {
|
|
37
|
+
local version="$1"
|
|
38
|
+
local requirement_line="$2"
|
|
39
|
+
local summary="$3"
|
|
40
|
+
local predecessors="$4"
|
|
41
|
+
mkdir -p compliance/pending-releases
|
|
42
|
+
cat > "compliance/pending-releases/RELEASE-TICKET-${version}.md" <<EOF
|
|
43
|
+
# Release Ticket - ${version}
|
|
44
|
+
|
|
45
|
+
**Requirement:** ${requirement_line}
|
|
46
|
+
**PR:** #123 https://github.com/example/repo/pull/123
|
|
47
|
+
|
|
48
|
+
## Summary
|
|
49
|
+
|
|
50
|
+
${summary}
|
|
51
|
+
|
|
52
|
+
## Bundled Changes
|
|
53
|
+
|
|
54
|
+
- **Core tracked release:** \`${version}\`
|
|
55
|
+
- **Absorbed predecessor releases:** ${predecessors}
|
|
56
|
+
EOF
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
assert_contains() {
|
|
46
60
|
local desc="$1" want="$2" got="$3"
|
|
47
61
|
if echo "$got" | grep -qF "$want"; then
|
|
@@ -70,117 +84,154 @@ assert_not_contains() {
|
|
|
70
84
|
fi
|
|
71
85
|
}
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
assert_eq() {
|
|
88
|
+
local desc="$1" want="$2" got="$3"
|
|
89
|
+
if [ "$want" = "$got" ]; then
|
|
90
|
+
echo " PASS: $desc"
|
|
91
|
+
PASS=$((PASS + 1))
|
|
92
|
+
else
|
|
93
|
+
echo " FAIL: $desc"
|
|
94
|
+
echo " want: $want"
|
|
95
|
+
echo " got: $got"
|
|
96
|
+
FAIL=$((FAIL + 1))
|
|
97
|
+
fi
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
assert_file_exists() {
|
|
101
|
+
local desc="$1" path="$2"
|
|
102
|
+
if [ -f "$path" ]; then
|
|
103
|
+
echo " PASS: $desc"
|
|
104
|
+
PASS=$((PASS + 1))
|
|
105
|
+
else
|
|
106
|
+
echo " FAIL: $desc"
|
|
107
|
+
echo " missing file: $path"
|
|
108
|
+
FAIL=$((FAIL + 1))
|
|
109
|
+
fi
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Test 1: housekeeping commits appear in markdown + JSON.
|
|
113
|
+
echo "Test 1: housekeeping commits render as non-release work"
|
|
75
114
|
DIR1="$TMPDIR_BASE/test1"
|
|
76
115
|
make_fixture "$DIR1"
|
|
77
116
|
add_commit "chore: sync DevAudit templates from v0.1.69 to v0.1.70 [skip ci]"
|
|
78
117
|
add_commit "docs: update API reference for /bookings endpoint"
|
|
79
118
|
add_commit "feat: add booking widget [REQ-042]"
|
|
80
|
-
add_commit "chore: bump eslint 9.0.5 to 9.0.6"
|
|
119
|
+
add_commit "chore(deps): bump eslint 9.0.5 to 9.0.6"
|
|
81
120
|
add_commit "fix: resolve null pointer in booking service [REQ-042]"
|
|
82
|
-
# Tag the initial commit as the "since" ref
|
|
83
121
|
SINCE=$(git rev-list --max-parents=0 HEAD)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
assert_contains "
|
|
87
|
-
assert_contains "
|
|
88
|
-
|
|
89
|
-
assert_not_contains "
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
assert_contains "reference field present" "**Reference:**" "$OUTPUT"
|
|
122
|
+
JSON_OUT="$DIR1/bundle.json"
|
|
123
|
+
OUTPUT=$(bash "$HELPER" "$SINCE" "REQ-042" --json-out "$JSON_OUT" 2>&1)
|
|
124
|
+
assert_contains "markdown includes chore commit" "chore: sync DevAudit templates" "$OUTPUT"
|
|
125
|
+
assert_contains "markdown includes docs commit" "docs: update API reference" "$OUTPUT"
|
|
126
|
+
assert_contains "markdown includes scoped chore commit" "chore(deps): bump eslint" "$OUTPUT"
|
|
127
|
+
assert_not_contains "markdown excludes feat commit" "feat: add booking widget" "$OUTPUT"
|
|
128
|
+
assert_not_contains "markdown excludes fix commit" "fix: resolve null pointer" "$OUTPUT"
|
|
129
|
+
assert_file_exists "json manifest emitted" "$JSON_OUT"
|
|
130
|
+
assert_eq "non-release work count" "3" "$(jq -r '.nonReleaseWorkItems | length' "$JSON_OUT")"
|
|
131
|
+
assert_eq "member count with no tickets" "0" "$(jq -r '.members | length' "$JSON_OUT")"
|
|
132
|
+
assert_eq "first housekeeping kind" "housekeeping_commit" "$(jq -r '.nonReleaseWorkItems[0].kind' "$JSON_OUT")"
|
|
96
133
|
echo
|
|
97
134
|
|
|
98
|
-
#
|
|
99
|
-
echo "Test 2:
|
|
135
|
+
# Test 2: explicit predecessor tickets become manifest members.
|
|
136
|
+
echo "Test 2: explicit predecessor membership emitted"
|
|
100
137
|
DIR2="$TMPDIR_BASE/test2"
|
|
101
138
|
make_fixture "$DIR2"
|
|
102
|
-
add_commit "feat:
|
|
103
|
-
add_commit "fix:
|
|
139
|
+
add_commit "feat: predecessor scope [REQ-041]"
|
|
140
|
+
add_commit "fix: predecessor bugfix [REQ-041]"
|
|
141
|
+
add_commit "feat: current release scope [REQ-042]"
|
|
142
|
+
write_release_ticket "REQ-041" "REQ-041 - Prior tracked release" "Prior release summary." "None"
|
|
143
|
+
write_release_ticket "REQ-042" "REQ-042 - Current tracked release" "Current release summary." "\`REQ-041\`"
|
|
104
144
|
SINCE=$(git rev-list --max-parents=0 HEAD)
|
|
105
|
-
|
|
106
|
-
|
|
145
|
+
JSON_OUT="$DIR2/bundle.json"
|
|
146
|
+
OUTPUT=$(bash "$HELPER" "$SINCE" "REQ-042" --json-out "$JSON_OUT" 2>&1)
|
|
147
|
+
assert_contains "markdown shows predecessor section" "REQ-041" "$OUTPUT"
|
|
148
|
+
assert_eq "manifest contains one explicit member" "1" "$(jq -r '.members | length' "$JSON_OUT")"
|
|
149
|
+
assert_eq "member version recorded" "REQ-041" "$(jq -r '.members[0].version' "$JSON_OUT")"
|
|
150
|
+
assert_eq "member role recorded" "predecessor" "$(jq -r '.members[0].role' "$JSON_OUT")"
|
|
151
|
+
assert_eq "member relationship recorded" "superseded" "$(jq -r '.members[0].relationship' "$JSON_OUT")"
|
|
152
|
+
assert_eq "manifest hash present" "true" "$(jq -r 'has("manifestHash")' "$JSON_OUT")"
|
|
153
|
+
assert_eq "manifest schema v2" "2" "$(jq -r '.schemaVersion' "$JSON_OUT")"
|
|
154
|
+
assert_eq "original title recorded" "Prior tracked release" "$(jq -r '.members[0].originalTitle' "$JSON_OUT")"
|
|
155
|
+
assert_eq "inheritance policy explicit" "all_eligible" "$(jq -r '.members[0].evidenceInheritancePolicy.mode' "$JSON_OUT")"
|
|
156
|
+
EXPECTED_HASH="sha256:$(jq -cS 'del(.manifestHash, .generator.generatedAt)' "$JSON_OUT" | tr -d '\n' | sha256sum | awk '{print $1}')"
|
|
157
|
+
assert_eq "canonical manifest hash verifies" "$EXPECTED_HASH" "$(jq -r '.manifestHash' "$JSON_OUT")"
|
|
107
158
|
echo
|
|
108
159
|
|
|
109
|
-
#
|
|
110
|
-
echo "Test 3:
|
|
160
|
+
# Test 3: ambiguous predecessor ownership fails.
|
|
161
|
+
echo "Test 3: ambiguous predecessor ownership is rejected"
|
|
111
162
|
DIR3="$TMPDIR_BASE/test3"
|
|
112
163
|
make_fixture "$DIR3"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
assert_contains "
|
|
127
|
-
assert_contains "perf: type captured" "perf: optimize query" "$OUTPUT"
|
|
128
|
-
assert_contains "refactor: type captured" "refactor: extract validation" "$OUTPUT"
|
|
164
|
+
write_release_ticket "REQ-041" "REQ-041 - Prior tracked release" "Prior release summary." "None"
|
|
165
|
+
write_release_ticket "REQ-042" "REQ-042 - Current tracked release" "Current release summary." "None"
|
|
166
|
+
set +e
|
|
167
|
+
OUTPUT=$(bash "$HELPER" "$(git rev-list --max-parents=0 HEAD)" "REQ-042" 2>&1)
|
|
168
|
+
RC=$?
|
|
169
|
+
set -e
|
|
170
|
+
if [ "$RC" -ne 0 ]; then
|
|
171
|
+
echo " PASS: non-zero exit for ambiguous predecessor set"
|
|
172
|
+
PASS=$((PASS + 1))
|
|
173
|
+
else
|
|
174
|
+
echo " FAIL: expected non-zero exit for ambiguous predecessor set"
|
|
175
|
+
FAIL=$((FAIL + 1))
|
|
176
|
+
fi
|
|
177
|
+
assert_contains "ambiguous error text" "ambiguous predecessor ownership" "$OUTPUT"
|
|
129
178
|
echo
|
|
130
179
|
|
|
131
|
-
#
|
|
132
|
-
echo "Test 4:
|
|
180
|
+
# Test 4: self-supersession is rejected.
|
|
181
|
+
echo "Test 4: self-supersession is rejected"
|
|
133
182
|
DIR4="$TMPDIR_BASE/test4"
|
|
134
183
|
make_fixture "$DIR4"
|
|
135
|
-
|
|
136
|
-
OUTPUT=$(bash "$HELPER" "$SINCE" "REQ-001" 2>&1 || true)
|
|
137
|
-
assert_contains "no commits message" "No housekeeping commits found" "$OUTPUT"
|
|
138
|
-
echo
|
|
139
|
-
|
|
140
|
-
# ─── Test 5: Invalid ref ────────────────────────────────────────────
|
|
141
|
-
echo "Test 5: invalid ref — error exit"
|
|
142
|
-
DIR5="$TMPDIR_BASE/test5"
|
|
143
|
-
make_fixture "$DIR5"
|
|
184
|
+
write_release_ticket "REQ-042" "REQ-042 - Current tracked release" "Current release summary." "\`REQ-042\`"
|
|
144
185
|
set +e
|
|
145
|
-
OUTPUT=$(bash "$HELPER" "
|
|
186
|
+
OUTPUT=$(bash "$HELPER" "$(git rev-list --max-parents=0 HEAD)" "REQ-042" 2>&1)
|
|
146
187
|
RC=$?
|
|
147
188
|
set -e
|
|
148
189
|
if [ "$RC" -ne 0 ]; then
|
|
149
|
-
echo " PASS: non-zero exit
|
|
190
|
+
echo " PASS: non-zero exit for self-supersession"
|
|
150
191
|
PASS=$((PASS + 1))
|
|
151
192
|
else
|
|
152
|
-
echo " FAIL: expected non-zero exit
|
|
193
|
+
echo " FAIL: expected non-zero exit for self-supersession"
|
|
153
194
|
FAIL=$((FAIL + 1))
|
|
154
195
|
fi
|
|
196
|
+
assert_contains "self-supersession error text" "cannot self-supersede" "$OUTPUT"
|
|
155
197
|
echo
|
|
156
198
|
|
|
157
|
-
#
|
|
158
|
-
echo "Test
|
|
159
|
-
|
|
160
|
-
make_fixture "$
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
SINCE=$(git rev-list --max-parents=0 HEAD)
|
|
164
|
-
OUTPUT=$(bash "$HELPER" "$SINCE" "REQ-050" 2>&1 || true)
|
|
165
|
-
assert_contains "scoped chore(deps) captured" "chore(deps): bump eslint" "$OUTPUT"
|
|
166
|
-
assert_contains "scoped ci(workflow) captured" "ci(workflow): update timeout" "$OUTPUT"
|
|
167
|
-
echo
|
|
168
|
-
|
|
169
|
-
# ─── Test 7: Missing since-ref argument ────────────────────────────
|
|
170
|
-
echo "Test 7: missing since-ref argument — error exit"
|
|
171
|
-
DIR7="$TMPDIR_BASE/test7"
|
|
172
|
-
make_fixture "$DIR7"
|
|
199
|
+
# Test 5: duplicate explicit predecessors are rejected.
|
|
200
|
+
echo "Test 5: duplicate explicit predecessors are rejected"
|
|
201
|
+
DIR5="$TMPDIR_BASE/test5"
|
|
202
|
+
make_fixture "$DIR5"
|
|
203
|
+
write_release_ticket "REQ-041" "REQ-041 - Prior tracked release" "Prior release summary." "None"
|
|
204
|
+
write_release_ticket "REQ-042" "REQ-042 - Current tracked release" "Current release summary." "\`REQ-041\`, \`REQ-041\`"
|
|
173
205
|
set +e
|
|
174
|
-
OUTPUT=$(bash "$HELPER" 2>&1)
|
|
206
|
+
OUTPUT=$(bash "$HELPER" "$(git rev-list --max-parents=0 HEAD)" "REQ-042" 2>&1)
|
|
175
207
|
RC=$?
|
|
176
208
|
set -e
|
|
177
209
|
if [ "$RC" -ne 0 ]; then
|
|
178
|
-
echo " PASS: non-zero exit
|
|
210
|
+
echo " PASS: non-zero exit for duplicate predecessors"
|
|
179
211
|
PASS=$((PASS + 1))
|
|
180
212
|
else
|
|
181
|
-
echo " FAIL: expected non-zero exit
|
|
213
|
+
echo " FAIL: expected non-zero exit for duplicate predecessors"
|
|
182
214
|
FAIL=$((FAIL + 1))
|
|
183
215
|
fi
|
|
216
|
+
assert_contains "duplicate predecessor error text" "duplicate explicit predecessor" "$OUTPUT"
|
|
217
|
+
echo
|
|
218
|
+
|
|
219
|
+
# Test 6: regeneration is deterministic apart from generatedAt.
|
|
220
|
+
echo "Test 6: manifest regeneration is deterministic"
|
|
221
|
+
DIR6="$TMPDIR_BASE/test6"
|
|
222
|
+
make_fixture "$DIR6"
|
|
223
|
+
add_commit "docs: update release notes"
|
|
224
|
+
write_release_ticket "REQ-042" "REQ-042 - Current tracked release" "Current release summary." "None"
|
|
225
|
+
SINCE=$(git rev-list --max-parents=0 HEAD)
|
|
226
|
+
JSON_ONE="$DIR6/bundle-one.json"
|
|
227
|
+
JSON_TWO="$DIR6/bundle-two.json"
|
|
228
|
+
bash "$HELPER" "$SINCE" "REQ-042" --json-out "$JSON_ONE" >/dev/null
|
|
229
|
+
sleep 1
|
|
230
|
+
bash "$HELPER" "$SINCE" "REQ-042" --json-out "$JSON_TWO" >/dev/null
|
|
231
|
+
NORMALIZED_ONE="$(jq -S 'del(.generator.generatedAt)' "$JSON_ONE")"
|
|
232
|
+
NORMALIZED_TWO="$(jq -S 'del(.generator.generatedAt)' "$JSON_TWO")"
|
|
233
|
+
assert_eq "normalized manifests match" "$NORMALIZED_ONE" "$NORMALIZED_TWO"
|
|
234
|
+
assert_eq "manifest hashes match" "$(jq -r '.manifestHash' "$JSON_ONE")" "$(jq -r '.manifestHash' "$JSON_TWO")"
|
|
184
235
|
echo
|
|
185
236
|
|
|
186
237
|
echo "=== Summary: ${PASS} pass / ${FAIL} fail ==="
|
|
@@ -43,6 +43,12 @@ fi
|
|
|
43
43
|
|
|
44
44
|
TODAY=$(date -u +%Y-%m-%d)
|
|
45
45
|
|
|
46
|
+
# Escape freeform table-cell content so markdownlint does not misparse
|
|
47
|
+
# literal pipes as extra columns in generated release evidence.
|
|
48
|
+
markdown_table_cell() {
|
|
49
|
+
printf '%s' "${1:-}" | perl -0pe 's/(?<!\\)\|/\\|/g'
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
# Last 20 commits with subject lines. Skip the version-deriver fallback
|
|
47
53
|
# from itself (avoid recursion if this script is re-run on a branch
|
|
48
54
|
# that already includes its own auto-commit).
|
|
@@ -104,13 +110,16 @@ REPLACE — confirm risk class for this release (typically LOW for housekeeping)
|
|
|
104
110
|
|
|
105
111
|
## Test Evidence
|
|
106
112
|
|
|
113
|
+
> When editing any markdown table below, escape literal pipe characters in cell
|
|
114
|
+
> content as \`\\|\` so RTM/release-evidence tables stay lint-safe.
|
|
115
|
+
|
|
107
116
|
| Test Type | Status | Source |
|
|
108
|
-
|
|
109
|
-
| TypeScript | $(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A") | CI \`typecheck\` gate |
|
|
110
|
-
| SAST | $(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A") | CI \`SAST\` gate |
|
|
111
|
-
| Dependency audit | $(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A") | CI \`dependency_audit\` gate |
|
|
112
|
-
| E2E | $(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A") | CI \`e2e\` gate |
|
|
113
|
-
| Test reports | $(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A") | CI \`test_report\` gate |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| TypeScript | $(markdown_table_cell "$(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A")") | CI \`typecheck\` gate |
|
|
119
|
+
| SAST | $(markdown_table_cell "$(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A")") | CI \`SAST\` gate |
|
|
120
|
+
| Dependency audit | $(markdown_table_cell "$(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A")") | CI \`dependency_audit\` gate |
|
|
121
|
+
| E2E | $(markdown_table_cell "$(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A")") | CI \`e2e\` gate |
|
|
122
|
+
| Test reports | $(markdown_table_cell "$(test -f gate-outcomes.json && echo "see gate-outcomes.json" || echo "REPLACE — green / N/A")") | CI \`test_report\` gate |
|
|
114
123
|
|
|
115
124
|
> Companion artefact: \`compliance/security-summary-$VERSION.md\` (auto-generated by \`generate-security-summary.sh\` in the same workflow run; contains the SAST + dep-audit findings detail).
|
|
116
125
|
|
|
@@ -123,7 +132,7 @@ REPLACE — confirm risk class for this release (typically LOW for housekeeping)
|
|
|
123
132
|
## Post-Deploy Actions
|
|
124
133
|
|
|
125
134
|
| Type | Script / Command | Target | Required | Notes |
|
|
126
|
-
|
|
135
|
+
| ------ | ----------------- | -------- | ---------- | ------- |
|
|
127
136
|
| — | None | — | — | Housekeeping releases typically have no post-deploy actions |
|
|
128
137
|
|
|
129
138
|
<!-- Replace the "None" row above if this release requires post-deploy work. -->
|
|
@@ -133,14 +142,14 @@ REPLACE — confirm risk class for this release (typically LOW for housekeeping)
|
|
|
133
142
|
## Sign-off
|
|
134
143
|
|
|
135
144
|
| Role | Name | Date | Notes |
|
|
136
|
-
|
|
145
|
+
| --- | --- | --- | --- |
|
|
137
146
|
| Submitter | REPLACE | $TODAY | Auto-generated by CI; reviewed + edited |
|
|
138
147
|
| Reviewer (independent if project risk_tier ≠ low) | REPLACE | REPLACE | REPLACE |
|
|
139
148
|
|
|
140
149
|
## Audit Trail
|
|
141
150
|
|
|
142
151
|
| Date | Action | Actor | Notes |
|
|
143
|
-
|
|
152
|
+
| ------ | -------- | ------- | ------- |
|
|
144
153
|
| $TODAY | Stub auto-generated | devaudit-bot | Bare-date version $VERSION |
|
|
145
154
|
| REPLACE | Operator sign-off | REPLACE | REPLACE |
|
|
146
155
|
| REPLACE | Submitted for UAT review | REPLACE | Via portal |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# generate-housekeeping-release-ticket.test.sh — Tests for the housekeeping
|
|
3
|
+
# release-ticket generator's markdown table output contract.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
HELPER="$SCRIPT_DIR/generate-housekeeping-release-ticket.sh"
|
|
9
|
+
[ -x "$HELPER" ] || chmod +x "$HELPER"
|
|
10
|
+
|
|
11
|
+
PASS=0
|
|
12
|
+
FAIL=0
|
|
13
|
+
|
|
14
|
+
assert_contains() {
|
|
15
|
+
local desc="$1" want="$2" got="$3"
|
|
16
|
+
if printf '%s' "$got" | grep -qF "$want"; then
|
|
17
|
+
echo " PASS: $desc"
|
|
18
|
+
PASS=$((PASS + 1))
|
|
19
|
+
else
|
|
20
|
+
echo " FAIL: $desc"
|
|
21
|
+
echo " want: $want"
|
|
22
|
+
FAIL=$((FAIL + 1))
|
|
23
|
+
fi
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
assert_not_contains_regex() {
|
|
27
|
+
local desc="$1" pattern="$2" got="$3"
|
|
28
|
+
if printf '%s' "$got" | grep -qE "$pattern"; then
|
|
29
|
+
echo " FAIL: $desc"
|
|
30
|
+
echo " unexpected pattern: $pattern"
|
|
31
|
+
FAIL=$((FAIL + 1))
|
|
32
|
+
else
|
|
33
|
+
echo " PASS: $desc"
|
|
34
|
+
PASS=$((PASS + 1))
|
|
35
|
+
fi
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
TMPDIR_BASE="$(mktemp -d)"
|
|
39
|
+
trap 'rm -rf "$TMPDIR_BASE"' EXIT
|
|
40
|
+
|
|
41
|
+
make_fixture() {
|
|
42
|
+
local dir="$1"
|
|
43
|
+
mkdir -p "$dir"
|
|
44
|
+
cd "$dir"
|
|
45
|
+
git init -q --initial-branch=main
|
|
46
|
+
git config user.email "test@example.com"
|
|
47
|
+
git config user.name "test"
|
|
48
|
+
echo "x" > f.txt
|
|
49
|
+
git add f.txt
|
|
50
|
+
git commit -q -m "docs: initial commit"
|
|
51
|
+
printf '{ "typecheck": "PASS", "sast": "PASS", "dependency_audit": "PASS", "e2e": "PASS", "test_report": "PASS" }\n' > gate-outcomes.json
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
echo "Test 1: generated markdown uses spaced separators and carries pipe-escape guidance"
|
|
55
|
+
DIR1="$TMPDIR_BASE/test1"
|
|
56
|
+
make_fixture "$DIR1"
|
|
57
|
+
OUTPUT=$(bash "$HELPER" "v2026.07.14" 2>&1)
|
|
58
|
+
assert_contains "pipe-escape guidance emitted" 'escape literal pipe characters in cell' "$OUTPUT"
|
|
59
|
+
assert_contains "spaced separator in test evidence table" '| --- | --- | --- |' "$OUTPUT"
|
|
60
|
+
assert_contains "spaced separator in sign-off table" '| --- | --- | --- | --- |' "$OUTPUT"
|
|
61
|
+
assert_contains "spaced separator in audit trail table" '| ------ | -------- | ------- | ------- |' "$OUTPUT"
|
|
62
|
+
assert_not_contains_regex "no compact separators remain" '^\|[-:|]+\|$' "$OUTPUT"
|
|
63
|
+
echo
|
|
64
|
+
|
|
65
|
+
echo "Result: $PASS passed, $FAIL failed"
|
|
66
|
+
[ "$FAIL" = "0" ]
|
|
@@ -38,6 +38,12 @@ fi
|
|
|
38
38
|
|
|
39
39
|
TODAY=$(date -u +%Y-%m-%d)
|
|
40
40
|
|
|
41
|
+
# Escape freeform table-cell content so markdownlint does not misparse
|
|
42
|
+
# literal pipes as extra columns in generated release evidence.
|
|
43
|
+
markdown_table_cell() {
|
|
44
|
+
printf '%s' "${1:-}" | perl -0pe 's/(?<!\\)\|/\\|/g'
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
# Detect release shape from the version.
|
|
42
48
|
SHAPE="unknown"
|
|
43
49
|
if [[ "$VERSION" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then
|
|
@@ -147,11 +153,14 @@ $GATES
|
|
|
147
153
|
|
|
148
154
|
## Access control + audit log
|
|
149
155
|
|
|
156
|
+
> When editing any markdown table below, escape literal pipe characters in cell
|
|
157
|
+
> content as \`\\|\` so RTM/release-evidence tables stay lint-safe.
|
|
158
|
+
|
|
150
159
|
| Check | Result | Notes |
|
|
151
|
-
|
|
152
|
-
| Access control unchanged | REPLACE — yes/no | If yes, no further work. If no, document the auth/RBAC delta and confirm it landed an audit event. |
|
|
153
|
-
| Audit log append-only invariant preserved | REPLACE — yes/no | If yes, no further work. If no, document why and confirm the change has independent review. |
|
|
154
|
-
| Sensitive data exposure | REPLACE — yes/no | If yes, escalate to the GDPR triage in \`compliance/governance/dpia.md\` before merging. |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| Access control unchanged | $(markdown_table_cell "REPLACE — yes/no") | If yes, no further work. If no, document the auth/RBAC delta and confirm it landed an audit event. |
|
|
162
|
+
| Audit log append-only invariant preserved | $(markdown_table_cell "REPLACE — yes/no") | If yes, no further work. If no, document why and confirm the change has independent review. |
|
|
163
|
+
| Sensitive data exposure | $(markdown_table_cell "REPLACE — yes/no") | If yes, escalate to the GDPR triage in \`compliance/governance/dpia.md\` before merging. |
|
|
155
164
|
|
|
156
165
|
## Risk Assessment
|
|
157
166
|
|
|
@@ -162,7 +171,7 @@ REPLACE — one paragraph summarising the security posture of this release. For
|
|
|
162
171
|
## Sign-off
|
|
163
172
|
|
|
164
173
|
| Role | Name | Date | Notes |
|
|
165
|
-
|
|
174
|
+
| --- | --- | --- | --- |
|
|
166
175
|
| Author | devaudit-bot (auto-generated) | $TODAY | Stub generated from CI gate JSON |
|
|
167
176
|
| Reviewer | REPLACE | REPLACE | REPLACE |
|
|
168
177
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# generate-security-summary.test.sh — Tests for the security-summary
|
|
3
|
+
# generator's markdown table output contract.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
HELPER="$SCRIPT_DIR/generate-security-summary.sh"
|
|
9
|
+
[ -x "$HELPER" ] || chmod +x "$HELPER"
|
|
10
|
+
|
|
11
|
+
PASS=0
|
|
12
|
+
FAIL=0
|
|
13
|
+
|
|
14
|
+
assert_contains() {
|
|
15
|
+
local desc="$1" want="$2" got="$3"
|
|
16
|
+
if printf '%s' "$got" | grep -qF "$want"; then
|
|
17
|
+
echo " PASS: $desc"
|
|
18
|
+
PASS=$((PASS + 1))
|
|
19
|
+
else
|
|
20
|
+
echo " FAIL: $desc"
|
|
21
|
+
echo " want: $want"
|
|
22
|
+
FAIL=$((FAIL + 1))
|
|
23
|
+
fi
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
assert_not_contains_regex() {
|
|
27
|
+
local desc="$1" pattern="$2" got="$3"
|
|
28
|
+
if printf '%s' "$got" | grep -qE "$pattern"; then
|
|
29
|
+
echo " FAIL: $desc"
|
|
30
|
+
echo " unexpected pattern: $pattern"
|
|
31
|
+
FAIL=$((FAIL + 1))
|
|
32
|
+
else
|
|
33
|
+
echo " PASS: $desc"
|
|
34
|
+
PASS=$((PASS + 1))
|
|
35
|
+
fi
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
TMPDIR_BASE="$(mktemp -d)"
|
|
39
|
+
trap 'rm -rf "$TMPDIR_BASE"' EXIT
|
|
40
|
+
|
|
41
|
+
DIR1="$TMPDIR_BASE/test1"
|
|
42
|
+
mkdir -p "$DIR1"
|
|
43
|
+
cd "$DIR1"
|
|
44
|
+
printf '{ "results": [] }\n' > sast-results.json
|
|
45
|
+
printf '{ "vulnerabilities": {} }\n' > dependency-audit.json
|
|
46
|
+
printf '{ "sast": "PASS", "dependency_audit": "PASS" }\n' > gate-outcomes.json
|
|
47
|
+
|
|
48
|
+
echo "Test 1: generated markdown uses spaced separators and carries pipe-escape guidance"
|
|
49
|
+
OUTPUT=$(bash "$HELPER" "REQ-321" 2>&1)
|
|
50
|
+
assert_contains "pipe-escape guidance emitted" 'escape literal pipe characters in cell' "$OUTPUT"
|
|
51
|
+
assert_contains "spaced separator in access-control table" '| --- | --- | --- |' "$OUTPUT"
|
|
52
|
+
assert_contains "spaced separator in sign-off table" '| --- | --- | --- | --- |' "$OUTPUT"
|
|
53
|
+
assert_not_contains_regex "no compact separators remain" '^\|[-:|]+\|$' "$OUTPUT"
|
|
54
|
+
echo
|
|
55
|
+
|
|
56
|
+
echo "Result: $PASS passed, $FAIL failed"
|
|
57
|
+
[ "$FAIL" = "0" ]
|
|
@@ -36,20 +36,47 @@ if [ -z "$CURRENT_RELEASE" ]; then
|
|
|
36
36
|
exit 1
|
|
37
37
|
fi
|
|
38
38
|
|
|
39
|
+
RELEASE_TITLE=""
|
|
40
|
+
RELEASE_SUMMARY=""
|
|
41
|
+
if [[ "$CURRENT_RELEASE" =~ ^REQ-[0-9]+$ ]] && [ -f scripts/extract-release-metadata.sh ]; then
|
|
42
|
+
# shellcheck disable=SC1091
|
|
43
|
+
source scripts/extract-release-metadata.sh
|
|
44
|
+
extract_release_metadata "$CURRENT_RELEASE"
|
|
45
|
+
fi
|
|
46
|
+
|
|
39
47
|
TITLE="Release: ${CURRENT_RELEASE}"
|
|
48
|
+
if [ -n "$RELEASE_TITLE" ]; then
|
|
49
|
+
TITLE="${TITLE} — ${RELEASE_TITLE}"
|
|
50
|
+
fi
|
|
51
|
+
|
|
40
52
|
BODY=$(cat <<EOF
|
|
41
53
|
## Release
|
|
42
54
|
|
|
43
55
|
- Release: ${CURRENT_RELEASE}
|
|
44
56
|
- Base: ${RELEASE_BRANCH}
|
|
45
57
|
- Head: ${INTEGRATION_BRANCH}
|
|
58
|
+
EOF
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if [ -n "$RELEASE_TITLE" ]; then
|
|
62
|
+
BODY="${BODY}
|
|
63
|
+
- Title: ${RELEASE_TITLE}"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
if [ -n "$RELEASE_SUMMARY" ]; then
|
|
67
|
+
BODY="${BODY}
|
|
68
|
+
|
|
69
|
+
## Summary
|
|
70
|
+
|
|
71
|
+
${RELEASE_SUMMARY}"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
BODY="${BODY}
|
|
46
75
|
|
|
47
76
|
## DevAudit
|
|
48
77
|
|
|
49
78
|
- Release context is derived from the current ${INTEGRATION_BRANCH} head.
|
|
50
|
-
- If this PR replaced an older release PR, the older PR was superseded because it no longer matched the governing release context.
|
|
51
|
-
EOF
|
|
52
|
-
)
|
|
79
|
+
- If this PR replaced an older release PR, the older PR was superseded because it no longer matched the governing release context."
|
|
53
80
|
|
|
54
81
|
OPEN_PRS=$(gh pr list --base "$RELEASE_BRANCH" --head "$INTEGRATION_BRANCH" --state open --json number,title,body,url --limit 10)
|
|
55
82
|
COUNT=$(echo "$OPEN_PRS" | jq 'length')
|
|
@@ -75,7 +102,20 @@ PR_TITLE=$(echo "$OPEN_PRS" | jq -r '.[0].title // ""')
|
|
|
75
102
|
PR_BODY=$(echo "$OPEN_PRS" | jq -r '.[0].body // ""')
|
|
76
103
|
PR_URL=$(echo "$OPEN_PRS" | jq -r '.[0].url')
|
|
77
104
|
|
|
78
|
-
|
|
105
|
+
PR_IS_CURRENT=true
|
|
106
|
+
if ! printf '%s\n%s\n' "$PR_TITLE" "$PR_BODY" | grep -q "$CURRENT_RELEASE"; then
|
|
107
|
+
PR_IS_CURRENT=false
|
|
108
|
+
fi
|
|
109
|
+
if [ -n "$RELEASE_TITLE" ] && [ "$PR_TITLE" != "$TITLE" ]; then
|
|
110
|
+
PR_IS_CURRENT=false
|
|
111
|
+
fi
|
|
112
|
+
if printf '%s' "$RELEASE_SUMMARY" | grep -q 'Bundled release context:'; then
|
|
113
|
+
if ! printf '%s\n%s\n' "$PR_TITLE" "$PR_BODY" | grep -q "BUNDLED-CHANGES-${CURRENT_RELEASE}.md"; then
|
|
114
|
+
PR_IS_CURRENT=false
|
|
115
|
+
fi
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
if [ "$PR_IS_CURRENT" = "true" ]; then
|
|
79
119
|
echo "Open release PR #${PR_NUMBER} already matches ${CURRENT_RELEASE}: ${PR_URL}"
|
|
80
120
|
exit 0
|
|
81
121
|
fi
|