@metasession.co/devaudit-cli 0.3.18 → 0.3.19
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 +35 -34
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/upload-evidence.sh +23 -19
- package/sdlc/files/_common/governance/incident-report.md.template +6 -1
- package/sdlc/files/_common/governance/nil-incident-report.md.template +5 -2
- package/sdlc/files/_common/scripts/check-release-pr-scope.sh +8 -1
- package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +58 -10
- package/sdlc/files/_common/scripts/check-self-hosted-runner.sh +103 -0
- package/sdlc/files/_common/scripts/check-self-hosted-runner.test.sh +74 -0
- package/sdlc/files/_common/scripts/record-uat-execution.sh +195 -0
- package/sdlc/files/_common/scripts/record-uat-execution.test.sh +144 -0
- package/sdlc/files/_common/scripts/render-test-executions.sh +146 -0
- package/sdlc/files/_common/scripts/{render-test-cycles.test.sh → render-test-executions.test.sh} +12 -18
- package/sdlc/files/_common/scripts/{report-test-cycle.sh → report-test-execution.sh} +66 -74
- package/sdlc/files/_common/scripts/report-test-execution.test.sh +177 -0
- package/sdlc/files/_common/scripts/submit-for-uat-review.sh +21 -0
- package/sdlc/files/_common/scripts/upload-evidence.sh +23 -19
- package/sdlc/files/_common/scripts/validate-commits.sh +6 -4
- package/sdlc/files/_common/scripts/validate-commits.test.sh +15 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +9 -7
- package/sdlc/files/_common/skills/e2e-test-engineer/references/e2e-regression-3-tier.yml +4 -1
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +16 -14
- package/sdlc/files/ci/ci.yml.template +54 -44
- package/sdlc/files/ci/compliance-evidence.yml.template +135 -46
- package/sdlc/files/ci/feature-e2e.yml.template +17 -17
- package/sdlc/files/ci/incident-export.yml.template +18 -4
- package/sdlc/files/ci/post-deploy-prod.yml.template +33 -30
- package/sdlc/files/ci/python/ci.yml.template +14 -14
- package/sdlc/files/ci/quality-gates-provenance.yml.template +3 -0
- package/sdlc/package.json +1 -1
- package/sdlc/src/blueprints/3-compile-evidence.raw.md +10 -10
- package/sdlc/src/blueprints/4-submit-for-review.raw.md +1 -1
- package/sdlc/src/blueprints/5-deploy-main.raw.md +1 -1
- package/sdlc/src/blueprints/implementing-an-sdlc-issue.raw.md +3 -3
- package/sdlc/files/_common/scripts/render-test-cycles.sh +0 -227
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metasession.co/devaudit-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.19",
|
|
4
4
|
"description": "DevAudit CLI — installs, syncs, and operates the Metasession SDLC across consumer projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@clack/prompts": "^0.8.2",
|
|
36
|
-
"@metasession.co/devaudit-plugin-sdk": "^0.3.
|
|
36
|
+
"@metasession.co/devaudit-plugin-sdk": "^0.3.19",
|
|
37
37
|
"ajv": "^8.20.0",
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
39
|
"consola": "^3.2.3",
|
|
@@ -42,19 +42,19 @@
|
|
|
42
42
|
# 4 submit-for-review, 5 deploy. Forwarded as
|
|
43
43
|
# `sdlcStage`; unknown to older portals (ignored
|
|
44
44
|
# server-side, no error).
|
|
45
|
-
# --test-
|
|
46
|
-
# ID). Forwarded
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# portals ignore the field (no error).
|
|
45
|
+
# --test-execution <id> Test execution identifier (typically the CI run
|
|
46
|
+
# ID). Forwarded to the portal transport as
|
|
47
|
+
# `testCycleId` until the upload API field is
|
|
48
|
+
# renamed.
|
|
50
49
|
# --evidence-scope <scope> Evidence ownership scope: release | stage |
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
# --test-
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
50
|
+
# execution | approval. `execution` is mapped
|
|
51
|
+
# to the portal transport value `cycle` until
|
|
52
|
+
# the upload API field is renamed.
|
|
53
|
+
# --test-execution-record-id <id>
|
|
54
|
+
# First-class portal test execution UUID.
|
|
55
|
+
# Requires `--evidence-scope execution`.
|
|
56
|
+
# Forwarded to the portal transport as
|
|
57
|
+
# `testCycleRecordId`.
|
|
58
58
|
#
|
|
59
59
|
# Required environment variables:
|
|
60
60
|
# DEVAUDIT_BASE_URL e.g. https://meta-comply-production.up.railway.app
|
|
@@ -136,9 +136,9 @@ while [ "$#" -gt 0 ]; do
|
|
|
136
136
|
# DevAudit-Installer#96.
|
|
137
137
|
--gate-status) GATE_STATUS="$2"; shift 2 ;;
|
|
138
138
|
--sdlc-stage) SDLC_STAGE="$2"; shift 2 ;;
|
|
139
|
-
--test-
|
|
139
|
+
--test-execution) TEST_CYCLE="$2"; shift 2 ;;
|
|
140
140
|
--evidence-scope) EVIDENCE_SCOPE="$2"; shift 2 ;;
|
|
141
|
-
--test-
|
|
141
|
+
--test-execution-record-id) TEST_CYCLE_RECORD_ID="$2"; shift 2 ;;
|
|
142
142
|
# --meta-key key=value (repeatable). Merged into the metadata JSON
|
|
143
143
|
# before posting. Validates the `key=value` shape; rejects bare
|
|
144
144
|
# keys without `=`.
|
|
@@ -166,14 +166,18 @@ if [ -n "$SDLC_STAGE" ] && ! [[ "$SDLC_STAGE" =~ ^[1-5]$ ]]; then
|
|
|
166
166
|
echo "Error: --sdlc-stage must be an integer 1-5 (got: $SDLC_STAGE)"
|
|
167
167
|
exit 1
|
|
168
168
|
fi
|
|
169
|
-
if [ -n "$EVIDENCE_SCOPE" ] && ! [[ "$EVIDENCE_SCOPE" =~ ^(release|stage|
|
|
170
|
-
echo "Error: --evidence-scope must be one of: release, stage,
|
|
169
|
+
if [ -n "$EVIDENCE_SCOPE" ] && ! [[ "$EVIDENCE_SCOPE" =~ ^(release|stage|execution|approval)$ ]]; then
|
|
170
|
+
echo "Error: --evidence-scope must be one of: release, stage, execution, approval"
|
|
171
171
|
exit 1
|
|
172
172
|
fi
|
|
173
|
-
if [ -n "$TEST_CYCLE_RECORD_ID" ] && [ "$EVIDENCE_SCOPE" != "
|
|
174
|
-
echo "Error: --test-
|
|
173
|
+
if [ -n "$TEST_CYCLE_RECORD_ID" ] && [ "$EVIDENCE_SCOPE" != "execution" ]; then
|
|
174
|
+
echo "Error: --test-execution-record-id requires --evidence-scope execution"
|
|
175
175
|
exit 1
|
|
176
176
|
fi
|
|
177
|
+
TRANSPORT_EVIDENCE_SCOPE="$EVIDENCE_SCOPE"
|
|
178
|
+
if [ "$TRANSPORT_EVIDENCE_SCOPE" = "execution" ]; then
|
|
179
|
+
TRANSPORT_EVIDENCE_SCOPE="cycle"
|
|
180
|
+
fi
|
|
177
181
|
|
|
178
182
|
if [ -z "${DEVAUDIT_BASE_URL:-}" ]; then
|
|
179
183
|
echo "Error: DEVAUDIT_BASE_URL environment variable is required"
|
|
@@ -553,7 +557,7 @@ for FILE in "${FILES[@]}"; do
|
|
|
553
557
|
[ -n "$GATE_STATUS" ] && CURL_ARGS+=(-F "gateStatus=${GATE_STATUS}")
|
|
554
558
|
[ -n "$SDLC_STAGE" ] && CURL_ARGS+=(-F "sdlcStage=${SDLC_STAGE}")
|
|
555
559
|
[ -n "$TEST_CYCLE" ] && CURL_ARGS+=(-F "testCycleId=${TEST_CYCLE}")
|
|
556
|
-
[ -n "$
|
|
560
|
+
[ -n "$TRANSPORT_EVIDENCE_SCOPE" ] && CURL_ARGS+=(-F "evidenceScope=${TRANSPORT_EVIDENCE_SCOPE}")
|
|
557
561
|
[ -n "$TEST_CYCLE_RECORD_ID" ] && CURL_ARGS+=(-F "testCycleRecordId=${TEST_CYCLE_RECORD_ID}")
|
|
558
562
|
[ -n "$SENTINEL_CONTENT" ] && CURL_ARGS+=(-F "sentinelContent=${SENTINEL_CONTENT}")
|
|
559
563
|
[ -n "$COMMIT_TIMESTAMP" ] && CURL_ARGS+=(-F "commitTimestamp=${COMMIT_TIMESTAMP}")
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Incident Report"
|
|
3
3
|
incident_id: "REPLACE — e.g. INC-2026-001"
|
|
4
|
+
incident_kind: "incident"
|
|
5
|
+
source_release: "REPLACE — REQ-XXX or vYYYY.MM.DD.N"
|
|
6
|
+
release_scope: "REPLACE — optional broader owning release/scope"
|
|
7
|
+
source_issue: "REPLACE — GitHub issue number"
|
|
8
|
+
semantic_id: "REPLACE — stable id, usually incident_id or source_issue"
|
|
4
9
|
severity: "REPLACE — low | medium | high | critical"
|
|
5
10
|
detected_at: "REPLACE — ISO-8601 with timezone"
|
|
6
11
|
resolved_at: "REPLACE — ISO-8601 or 'ongoing'"
|
|
@@ -30,7 +35,7 @@ last_reviewed_at: "REPLACE — YYYY-MM-DD"
|
|
|
30
35
|
## Uploading this artefact
|
|
31
36
|
|
|
32
37
|
- **File path:** `compliance/governance/incident-report-<id>.md` (per-incident — recommended; the `incident-export.yml` workflow auto-produces these from closed GitHub issues labelled `incident`). The bare `incident-report.md` is the unedited starter — kept on disk as a reference but **skipped by the uploader** until you replace the STARTER TEMPLATE banner.
|
|
33
|
-
- **Upload trigger:** automatic — on every push to `develop` that touches `compliance/**`, `compliance-evidence.yml`
|
|
38
|
+
- **Upload trigger:** automatic — on every push to `develop` that touches `compliance/**`, `compliance-evidence.yml` considers `incident-report*.md` under both layouts, but uploads only files whose frontmatter `source_release` / `release_scope` matches the current derived release. Historical files remain on disk and are skipped unless an explicit bundle manifest grants inheritance; inherited files are still uploaded to their source release, not relabelled as the current release.
|
|
34
39
|
- **Verify after merge:** open `/projects/<slug>/compliance`. `ISO29119.3.5.4` flips to COVERED only when a non-stub `incident-report*.md` lands. `SOC2.CC7.2`, `GDPR.Art-33`, `GDPR.Art-34` flip only when the relevant attribution sections below are non-stub.
|
|
35
40
|
- **Refresh cadence:** none — incidents are point-in-time. Authoring is event-driven.
|
|
36
41
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
incident_id: "NIL-<version>"
|
|
3
|
+
incident_kind: "no_incidents_attestation"
|
|
4
|
+
source_release: "<version>"
|
|
5
|
+
semantic_id: "NIL-<version>"
|
|
3
6
|
severity: "N/A"
|
|
4
7
|
detected_at: "<release date>"
|
|
5
8
|
resolved_at: "N/A"
|
|
@@ -10,12 +13,12 @@ status: "nil"
|
|
|
10
13
|
|
|
11
14
|
## Attestation
|
|
12
15
|
|
|
13
|
-
No incidents or defects were discovered during the test
|
|
16
|
+
No incidents or defects were discovered during the test execution for release `<version>`.
|
|
14
17
|
|
|
15
18
|
## Scope
|
|
16
19
|
|
|
17
20
|
- **Release:** <version>
|
|
18
|
-
- **Test cycle:** <description of test
|
|
21
|
+
- **Test cycle:** <description of test execution — e.g. "E2E Regression run on <date>", "Phase 6 full suite + Phase 3 re-run">
|
|
19
22
|
- **Test cases executed:** <count>
|
|
20
23
|
- **Test cases passed:** <count>
|
|
21
24
|
- **Test cases failed:** 0
|
|
@@ -43,6 +43,9 @@ PY
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
HEAD_REF="${HEAD_REF:-}"
|
|
46
|
+
BASE_REF="${BASE_REF:-}"
|
|
47
|
+
INTEGRATION_BRANCH="${INTEGRATION_BRANCH:-develop}"
|
|
48
|
+
RELEASE_BRANCH="${RELEASE_BRANCH:-main}"
|
|
46
49
|
if is_hotfix_branch "$HEAD_REF"; then
|
|
47
50
|
echo "Hotfix PR detected (${HEAD_REF}) — release scope integrity check skipped."
|
|
48
51
|
exit 0
|
|
@@ -51,7 +54,11 @@ fi
|
|
|
51
54
|
if [ ! -x scripts/derive-release-version.sh ]; then
|
|
52
55
|
chmod +x scripts/derive-release-version.sh 2>/dev/null || true
|
|
53
56
|
fi
|
|
54
|
-
|
|
57
|
+
ALLOW_PENDING_TICKET_FALLBACK=0
|
|
58
|
+
if [ "$HEAD_REF" = "$INTEGRATION_BRANCH" ] && [ "$BASE_REF" = "$RELEASE_BRANCH" ]; then
|
|
59
|
+
ALLOW_PENDING_TICKET_FALLBACK=1
|
|
60
|
+
fi
|
|
61
|
+
CURRENT_RELEASE=$(DEVAUDIT_ALLOW_PENDING_TICKET_FALLBACK="$ALLOW_PENDING_TICKET_FALLBACK" bash scripts/derive-release-version.sh)
|
|
55
62
|
if [ -z "$CURRENT_RELEASE" ]; then
|
|
56
63
|
echo "::error::Could not derive the effective release scope from the current branch head."
|
|
57
64
|
exit 1
|
|
@@ -134,10 +134,58 @@ assert_exit "hotfix branch skips" 0 "$CODE"
|
|
|
134
134
|
assert_grep "hotfix skip message" 'Hotfix PR detected' "$OUT"
|
|
135
135
|
echo ""
|
|
136
136
|
|
|
137
|
-
echo "--- Test 5:
|
|
138
|
-
make_fixture "$WORK/test5" "chore:
|
|
137
|
+
echo "--- Test 5: develop-to-main promotion may use exactly one pending ticket ---"
|
|
138
|
+
make_fixture "$WORK/test5" "chore: integration head without commit tag"
|
|
139
|
+
cat > "$WORK/test5/compliance/pending-releases/RELEASE-TICKET-REQ-094.md" <<'EOF'
|
|
140
|
+
# Release Ticket — REQ-094
|
|
141
|
+
EOF
|
|
142
|
+
OUT="$WORK/test5.out"
|
|
143
|
+
if run_check "$WORK/test5" "$OUT" env HEAD_REF="develop" BASE_REF="main" PR_TITLE="Release: REQ-094" PR_BODY=$'## Release\n- Release: REQ-094\n'; then
|
|
144
|
+
CODE=0
|
|
145
|
+
else
|
|
146
|
+
CODE=$?
|
|
147
|
+
fi
|
|
148
|
+
assert_exit "develop-to-main single pending ticket passes" 0 "$CODE"
|
|
149
|
+
assert_grep "pending ticket promotion resolves REQ" 'Release Scope Integrity verified for REQ-094' "$OUT"
|
|
150
|
+
echo ""
|
|
151
|
+
|
|
152
|
+
echo "--- Test 6: non-promotion context cannot use pending ticket fallback ---"
|
|
153
|
+
make_fixture "$WORK/test6" "chore: integration head without commit tag"
|
|
154
|
+
cat > "$WORK/test6/compliance/pending-releases/RELEASE-TICKET-REQ-094.md" <<'EOF'
|
|
155
|
+
# Release Ticket — REQ-094
|
|
156
|
+
EOF
|
|
157
|
+
OUT="$WORK/test6.out"
|
|
158
|
+
if run_check "$WORK/test6" "$OUT" env HEAD_REF="feature/demo" BASE_REF="develop" PR_TITLE="Release: REQ-094" PR_BODY=$'## Release\n- Release: REQ-094\n'; then
|
|
159
|
+
CODE=0
|
|
160
|
+
else
|
|
161
|
+
CODE=$?
|
|
162
|
+
fi
|
|
163
|
+
assert_exit "non-promotion single pending ticket fails" 1 "$CODE"
|
|
164
|
+
assert_grep "non-promotion derives bare date" 'Derived effective scope: v[0-9]{4}\.[0-9]{2}\.[0-9]{2}' "$OUT"
|
|
165
|
+
echo ""
|
|
166
|
+
|
|
167
|
+
echo "--- Test 7: develop-to-main with ambiguous pending tickets fails closed ---"
|
|
168
|
+
make_fixture "$WORK/test7" "chore: integration head without commit tag"
|
|
169
|
+
cat > "$WORK/test7/compliance/pending-releases/RELEASE-TICKET-REQ-094.md" <<'EOF'
|
|
170
|
+
# Release Ticket — REQ-094
|
|
171
|
+
EOF
|
|
172
|
+
cat > "$WORK/test7/compliance/pending-releases/RELEASE-TICKET-REQ-095.md" <<'EOF'
|
|
173
|
+
# Release Ticket — REQ-095
|
|
174
|
+
EOF
|
|
175
|
+
OUT="$WORK/test7.out"
|
|
176
|
+
if run_check "$WORK/test7" "$OUT" env HEAD_REF="develop" BASE_REF="main" PR_TITLE="Release: REQ-094" PR_BODY=$'## Release\n- Release: REQ-094\n'; then
|
|
177
|
+
CODE=0
|
|
178
|
+
else
|
|
179
|
+
CODE=$?
|
|
180
|
+
fi
|
|
181
|
+
assert_exit "ambiguous pending tickets fail" 1 "$CODE"
|
|
182
|
+
assert_grep "ambiguous promotion falls back to bare date" 'Derived effective scope: v[0-9]{4}\.[0-9]{2}\.[0-9]{2}' "$OUT"
|
|
183
|
+
echo ""
|
|
184
|
+
|
|
185
|
+
echo "--- Test 8: bare-date releases require an explicit standalone declaration ---"
|
|
186
|
+
make_fixture "$WORK/test8" "chore: standalone housekeeping release"
|
|
139
187
|
BARE_DATE_VERSION="v$(date -u +%Y.%m.%d)"
|
|
140
|
-
cat > "$WORK/
|
|
188
|
+
cat > "$WORK/test8/compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${BARE_DATE_VERSION}.json" <<EOF
|
|
141
189
|
{
|
|
142
190
|
"schemaVersion": 1,
|
|
143
191
|
"version": "${BARE_DATE_VERSION}",
|
|
@@ -145,8 +193,8 @@ cat > "$WORK/test5/compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${
|
|
|
145
193
|
"reason": "Urgent operational change cannot wait for the next tracked requirement release."
|
|
146
194
|
}
|
|
147
195
|
EOF
|
|
148
|
-
OUT="$WORK/
|
|
149
|
-
if run_check "$WORK/
|
|
196
|
+
OUT="$WORK/test8.out"
|
|
197
|
+
if run_check "$WORK/test8" "$OUT" env PR_TITLE="Standalone housekeeping promotion: ${BARE_DATE_VERSION}" PR_BODY=$"## Release\n- Release: ${BARE_DATE_VERSION}\n"; then
|
|
150
198
|
CODE=0
|
|
151
199
|
else
|
|
152
200
|
CODE=$?
|
|
@@ -155,9 +203,9 @@ assert_exit "declared standalone housekeeping release passes" 0 "$CODE"
|
|
|
155
203
|
assert_grep "standalone validation is emitted" 'Standalone housekeeping declaration is valid' "$OUT"
|
|
156
204
|
echo ""
|
|
157
205
|
|
|
158
|
-
echo "--- Test
|
|
159
|
-
make_fixture "$WORK/
|
|
160
|
-
cat > "$WORK/
|
|
206
|
+
echo "--- Test 9: bare-date releases without the exception marker fail ---"
|
|
207
|
+
make_fixture "$WORK/test9" "chore: standalone housekeeping release"
|
|
208
|
+
cat > "$WORK/test9/compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${BARE_DATE_VERSION}.json" <<EOF
|
|
161
209
|
{
|
|
162
210
|
"schemaVersion": 1,
|
|
163
211
|
"version": "${BARE_DATE_VERSION}",
|
|
@@ -165,8 +213,8 @@ cat > "$WORK/test6/compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${
|
|
|
165
213
|
"reason": "Urgent operational change cannot wait for the next tracked requirement release."
|
|
166
214
|
}
|
|
167
215
|
EOF
|
|
168
|
-
OUT="$WORK/
|
|
169
|
-
if run_check "$WORK/
|
|
216
|
+
OUT="$WORK/test9.out"
|
|
217
|
+
if run_check "$WORK/test9" "$OUT" env PR_TITLE="Release: ${BARE_DATE_VERSION}" PR_BODY=$"## Release\n- Release: ${BARE_DATE_VERSION}\n"; then
|
|
170
218
|
CODE=0
|
|
171
219
|
else
|
|
172
220
|
CODE=$?
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Validate host-level prerequisites for self-hosted CI runners.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
MIN_WATCHES="${MIN_INOTIFY_WATCHES:-524288}"
|
|
7
|
+
MIN_INSTANCES="${MIN_INOTIFY_INSTANCES:-512}"
|
|
8
|
+
RUNNER_ENVIRONMENT="${DEVAUDIT_RUNNER_ENVIRONMENT:-${RUNNER_ENVIRONMENT:-}}"
|
|
9
|
+
SYSCTL_BIN="${SYSCTL_BIN:-sysctl}"
|
|
10
|
+
SYSCTL_CONF_DIR="${SYSCTL_CONF_DIR:-/etc/sysctl.d}"
|
|
11
|
+
APPLY=false
|
|
12
|
+
FORCE=false
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat <<EOF
|
|
16
|
+
Usage: check-self-hosted-runner.sh [--apply] [--force]
|
|
17
|
+
|
|
18
|
+
Validates Linux self-hosted runner prerequisites needed by Next.js/Turbopack
|
|
19
|
+
and Playwright E2E jobs.
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--apply Persist and apply inotify sysctl minima. Requires permission to
|
|
23
|
+
write ${SYSCTL_CONF_DIR} and run sysctl -w.
|
|
24
|
+
--force Run checks even when DEVAUDIT_RUNNER_ENVIRONMENT is not self-hosted.
|
|
25
|
+
EOF
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
while [ "$#" -gt 0 ]; do
|
|
29
|
+
case "$1" in
|
|
30
|
+
--apply) APPLY=true ;;
|
|
31
|
+
--force) FORCE=true ;;
|
|
32
|
+
-h|--help) usage; exit 0 ;;
|
|
33
|
+
*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
|
|
34
|
+
esac
|
|
35
|
+
shift
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
if [ "${FORCE}" != "true" ] && [ -n "${RUNNER_ENVIRONMENT}" ] && [ "${RUNNER_ENVIRONMENT}" != "self-hosted" ]; then
|
|
39
|
+
echo "Runner environment is ${RUNNER_ENVIRONMENT}; self-hosted prerequisite checks skipped."
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
OS_NAME="$(uname -s 2>/dev/null || echo unknown)"
|
|
44
|
+
if [ "${OS_NAME}" != "Linux" ]; then
|
|
45
|
+
echo "Runner OS is ${OS_NAME}; Linux inotify prerequisite checks skipped."
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
read_sysctl() {
|
|
50
|
+
local key="$1"
|
|
51
|
+
"${SYSCTL_BIN}" -n "$key" 2>/dev/null || true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
write_sysctl_config() {
|
|
55
|
+
mkdir -p "${SYSCTL_CONF_DIR}"
|
|
56
|
+
cat > "${SYSCTL_CONF_DIR}/99-metasession-ci-inotify.conf" <<EOF
|
|
57
|
+
fs.inotify.max_user_watches=${MIN_WATCHES}
|
|
58
|
+
fs.inotify.max_user_instances=${MIN_INSTANCES}
|
|
59
|
+
EOF
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if [ "${APPLY}" = "true" ]; then
|
|
63
|
+
echo "Persisting self-hosted runner inotify prerequisites..."
|
|
64
|
+
write_sysctl_config
|
|
65
|
+
"${SYSCTL_BIN}" -w "fs.inotify.max_user_watches=${MIN_WATCHES}"
|
|
66
|
+
"${SYSCTL_BIN}" -w "fs.inotify.max_user_instances=${MIN_INSTANCES}"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
CURRENT_WATCHES="$(read_sysctl fs.inotify.max_user_watches)"
|
|
70
|
+
CURRENT_INSTANCES="$(read_sysctl fs.inotify.max_user_instances)"
|
|
71
|
+
|
|
72
|
+
if ! [[ "${CURRENT_WATCHES}" =~ ^[0-9]+$ ]] || ! [[ "${CURRENT_INSTANCES}" =~ ^[0-9]+$ ]]; then
|
|
73
|
+
echo "::error::Unable to read Linux inotify sysctl values on this self-hosted runner." >&2
|
|
74
|
+
echo "Run: sudo bash scripts/check-self-hosted-runner.sh --apply" >&2
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
FAILED=false
|
|
79
|
+
if [ "${CURRENT_WATCHES}" -lt "${MIN_WATCHES}" ]; then
|
|
80
|
+
echo "::error::fs.inotify.max_user_watches=${CURRENT_WATCHES}, expected at least ${MIN_WATCHES}." >&2
|
|
81
|
+
FAILED=true
|
|
82
|
+
fi
|
|
83
|
+
if [ "${CURRENT_INSTANCES}" -lt "${MIN_INSTANCES}" ]; then
|
|
84
|
+
echo "::error::fs.inotify.max_user_instances=${CURRENT_INSTANCES}, expected at least ${MIN_INSTANCES}." >&2
|
|
85
|
+
FAILED=true
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ "${FAILED}" = "true" ]; then
|
|
89
|
+
cat >&2 <<EOF
|
|
90
|
+
Self-hosted runner is missing durable inotify capacity for Turbopack/Playwright E2E.
|
|
91
|
+
|
|
92
|
+
Apply on the runner host:
|
|
93
|
+
sudo bash scripts/check-self-hosted-runner.sh --apply
|
|
94
|
+
|
|
95
|
+
Expected persistent file:
|
|
96
|
+
${SYSCTL_CONF_DIR}/99-metasession-ci-inotify.conf
|
|
97
|
+
|
|
98
|
+
Do not classify this as product-test evidence; repair the runner and rerun CI.
|
|
99
|
+
EOF
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
echo "Self-hosted runner prerequisites passed: fs.inotify.max_user_watches=${CURRENT_WATCHES}, fs.inotify.max_user_instances=${CURRENT_INSTANCES}."
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
CHECKER="$SCRIPT_DIR/check-self-hosted-runner.sh"
|
|
6
|
+
|
|
7
|
+
PASS=0
|
|
8
|
+
FAIL=0
|
|
9
|
+
|
|
10
|
+
ok() {
|
|
11
|
+
echo " PASS: $1"
|
|
12
|
+
PASS=$((PASS + 1))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
no() {
|
|
16
|
+
echo " FAIL: $1"
|
|
17
|
+
FAIL=$((FAIL + 1))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
run_case() {
|
|
21
|
+
local name="$1"; shift
|
|
22
|
+
OUT="$(mktemp)"
|
|
23
|
+
set +e
|
|
24
|
+
"$@" >"$OUT" 2>&1
|
|
25
|
+
CODE=$?
|
|
26
|
+
set -e
|
|
27
|
+
echo "$name"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
WORK="$(mktemp -d -t runner-check-test-XXXX)"
|
|
31
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
32
|
+
|
|
33
|
+
SYSCTL_FAKE="$WORK/sysctl"
|
|
34
|
+
cat > "$SYSCTL_FAKE" <<'EOF'
|
|
35
|
+
#!/usr/bin/env bash
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
case "${1:-}" in
|
|
38
|
+
-n)
|
|
39
|
+
case "$2" in
|
|
40
|
+
fs.inotify.max_user_watches) echo "${FAKE_WATCHES:-524288}" ;;
|
|
41
|
+
fs.inotify.max_user_instances) echo "${FAKE_INSTANCES:-512}" ;;
|
|
42
|
+
*) exit 1 ;;
|
|
43
|
+
esac
|
|
44
|
+
;;
|
|
45
|
+
-w)
|
|
46
|
+
echo "$2"
|
|
47
|
+
;;
|
|
48
|
+
*)
|
|
49
|
+
exit 1
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
EOF
|
|
53
|
+
chmod +x "$SYSCTL_FAKE"
|
|
54
|
+
|
|
55
|
+
run_case "Case 1: GitHub-hosted runner no-ops" env DEVAUDIT_RUNNER_ENVIRONMENT=github-hosted SYSCTL_BIN="$SYSCTL_FAKE" "$CHECKER"
|
|
56
|
+
[ "$CODE" -eq 0 ] && grep -q "skipped" "$OUT" && ok "hosted runner skipped" || no "hosted runner should skip"
|
|
57
|
+
|
|
58
|
+
run_case "Case 2: low limits fail with actionable remediation" env DEVAUDIT_RUNNER_ENVIRONMENT=self-hosted FAKE_WATCHES=65536 FAKE_INSTANCES=128 SYSCTL_BIN="$SYSCTL_FAKE" "$CHECKER"
|
|
59
|
+
[ "$CODE" -eq 1 ] && grep -q "sudo bash scripts/check-self-hosted-runner.sh --apply" "$OUT" && ok "low limits fail with remediation" || no "low limits should fail"
|
|
60
|
+
|
|
61
|
+
run_case "Case 3: sufficient limits pass" env DEVAUDIT_RUNNER_ENVIRONMENT=self-hosted FAKE_WATCHES=524288 FAKE_INSTANCES=512 SYSCTL_BIN="$SYSCTL_FAKE" "$CHECKER"
|
|
62
|
+
[ "$CODE" -eq 0 ] && grep -q "prerequisites passed" "$OUT" && ok "sufficient limits pass" || no "sufficient limits should pass"
|
|
63
|
+
|
|
64
|
+
CONF_DIR="$WORK/sysctl.d"
|
|
65
|
+
run_case "Case 4: --apply writes persistent config" env DEVAUDIT_RUNNER_ENVIRONMENT=self-hosted SYSCTL_BIN="$SYSCTL_FAKE" SYSCTL_CONF_DIR="$CONF_DIR" "$CHECKER" --apply
|
|
66
|
+
if [ "$CODE" -eq 0 ] && grep -q "fs.inotify.max_user_watches=524288" "$CONF_DIR/99-metasession-ci-inotify.conf" && grep -q "fs.inotify.max_user_instances=512" "$CONF_DIR/99-metasession-ci-inotify.conf"; then
|
|
67
|
+
ok "--apply persists config"
|
|
68
|
+
else
|
|
69
|
+
no "--apply should persist config"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
echo
|
|
73
|
+
echo "Result: $PASS passed, $FAIL failed"
|
|
74
|
+
[ "$FAIL" = "0" ]
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# record-uat-execution.sh - Record an explicit Stage 4 UAT execution.
|
|
3
|
+
#
|
|
4
|
+
# This script does not submit, approve, or release anything. It records the
|
|
5
|
+
# manual UAT execution that happened while the release was under UAT review.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# ./scripts/record-uat-execution.sh \
|
|
9
|
+
# --project-slug <slug> \
|
|
10
|
+
# --release <REQ-XXX|version> \
|
|
11
|
+
# --outcome <passed|failed|cancelled|skipped|timed_out|action_required> \
|
|
12
|
+
# --executor <identity> \
|
|
13
|
+
# [--tested-sha <sha>] \
|
|
14
|
+
# [--build-version <version>] \
|
|
15
|
+
# [--checklist-ref <path-or-url>] \
|
|
16
|
+
# [--evidence-ref <path-or-url>] \
|
|
17
|
+
# [--remediation-ref <issue-or-url>] \
|
|
18
|
+
# [--executed-at <iso8601>] \
|
|
19
|
+
# [--execution-id <stable-id>] \
|
|
20
|
+
# [--output-file <path>]
|
|
21
|
+
#
|
|
22
|
+
# Required environment:
|
|
23
|
+
# DEVAUDIT_BASE_URL
|
|
24
|
+
# DEVAUDIT_API_KEY
|
|
25
|
+
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
REPORT_TEST_EXECUTION="${REPORT_TEST_EXECUTION_HELPER:-${SCRIPT_DIR}/report-test-execution.sh}"
|
|
30
|
+
|
|
31
|
+
usage() {
|
|
32
|
+
sed -n '1,38p' "$0" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
PROJECT_SLUG=""
|
|
37
|
+
RELEASE_VERSION=""
|
|
38
|
+
OUTCOME=""
|
|
39
|
+
EXECUTOR=""
|
|
40
|
+
TESTED_SHA="${GITHUB_SHA:-}"
|
|
41
|
+
BUILD_VERSION=""
|
|
42
|
+
CHECKLIST_REF=""
|
|
43
|
+
EVIDENCE_REF=""
|
|
44
|
+
REMEDIATION_REF=""
|
|
45
|
+
EXECUTED_AT=""
|
|
46
|
+
EXECUTION_ID=""
|
|
47
|
+
OUTPUT_FILE=""
|
|
48
|
+
|
|
49
|
+
while [ "$#" -gt 0 ]; do
|
|
50
|
+
case "$1" in
|
|
51
|
+
--project-slug) PROJECT_SLUG="${2:-}"; shift 2 ;;
|
|
52
|
+
--release) RELEASE_VERSION="${2:-}"; shift 2 ;;
|
|
53
|
+
--outcome) OUTCOME="${2:-}"; shift 2 ;;
|
|
54
|
+
--executor) EXECUTOR="${2:-}"; shift 2 ;;
|
|
55
|
+
--tested-sha) TESTED_SHA="${2:-}"; shift 2 ;;
|
|
56
|
+
--build-version) BUILD_VERSION="${2:-}"; shift 2 ;;
|
|
57
|
+
--checklist-ref) CHECKLIST_REF="${2:-}"; shift 2 ;;
|
|
58
|
+
--evidence-ref) EVIDENCE_REF="${2:-}"; shift 2 ;;
|
|
59
|
+
--remediation-ref) REMEDIATION_REF="${2:-}"; shift 2 ;;
|
|
60
|
+
--executed-at) EXECUTED_AT="${2:-}"; shift 2 ;;
|
|
61
|
+
--execution-id) EXECUTION_ID="${2:-}"; shift 2 ;;
|
|
62
|
+
--output-file) OUTPUT_FILE="${2:-}"; shift 2 ;;
|
|
63
|
+
-h|--help) usage ;;
|
|
64
|
+
*) echo "Unknown option: $1" >&2; usage ;;
|
|
65
|
+
esac
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
require_arg() {
|
|
69
|
+
local value="$1" name="$2"
|
|
70
|
+
if [ -z "$value" ]; then
|
|
71
|
+
echo "Error: ${name} is required" >&2
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
require_arg "$PROJECT_SLUG" "--project-slug"
|
|
77
|
+
require_arg "$RELEASE_VERSION" "--release"
|
|
78
|
+
require_arg "$OUTCOME" "--outcome"
|
|
79
|
+
require_arg "$EXECUTOR" "--executor"
|
|
80
|
+
|
|
81
|
+
case "$OUTCOME" in
|
|
82
|
+
passed|failed|cancelled|skipped|timed_out|action_required) ;;
|
|
83
|
+
*)
|
|
84
|
+
echo "Error: --outcome must be passed, failed, cancelled, skipped, timed_out, or action_required" >&2
|
|
85
|
+
exit 1
|
|
86
|
+
;;
|
|
87
|
+
esac
|
|
88
|
+
if [ "$OUTCOME" = "failed" ] && [ -z "$REMEDIATION_REF" ]; then
|
|
89
|
+
echo "Error: --remediation-ref is required when --outcome failed" >&2
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
if [ -z "${DEVAUDIT_BASE_URL:-}" ]; then
|
|
94
|
+
echo "Error: DEVAUDIT_BASE_URL environment variable is required" >&2
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
if [ -z "${DEVAUDIT_API_KEY:-}" ]; then
|
|
98
|
+
echo "Error: DEVAUDIT_API_KEY environment variable is required" >&2
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
if [ ! -x "$REPORT_TEST_EXECUTION" ]; then
|
|
102
|
+
echo "Error: report-test-execution helper not found or not executable: $REPORT_TEST_EXECUTION" >&2
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ -z "$TESTED_SHA" ] && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
107
|
+
TESTED_SHA="$(git rev-parse HEAD)"
|
|
108
|
+
fi
|
|
109
|
+
if [ -z "$TESTED_SHA" ]; then
|
|
110
|
+
echo "Error: --tested-sha is required when GITHUB_SHA is unset and git HEAD cannot be resolved" >&2
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
iso_now() {
|
|
115
|
+
date -u +"%Y-%m-%dT%H:%M:%SZ"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
hash_value() {
|
|
119
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
120
|
+
printf '%s' "$1" | sha256sum | awk '{print $1}'
|
|
121
|
+
else
|
|
122
|
+
printf '%s' "$1" | shasum -a 256 | awk '{print $1}'
|
|
123
|
+
fi
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
sanitize_id_part() {
|
|
127
|
+
printf '%s' "$1" | tr -cs 'A-Za-z0-9._:-' '-'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[ -n "$EXECUTED_AT" ] || EXECUTED_AT="$(iso_now)"
|
|
131
|
+
|
|
132
|
+
if [ -z "$EXECUTION_ID" ]; then
|
|
133
|
+
KEY_SOURCE="${PROJECT_SLUG}|${RELEASE_VERSION}|${TESTED_SHA}|${BUILD_VERSION}|${EXECUTOR}|${OUTCOME}|${CHECKLIST_REF}|${EVIDENCE_REF}|${REMEDIATION_REF}"
|
|
134
|
+
KEY_HASH="$(hash_value "$KEY_SOURCE" | cut -c1-16)"
|
|
135
|
+
EXECUTION_ID="manual-uat:$(sanitize_id_part "$RELEASE_VERSION"):$(sanitize_id_part "$TESTED_SHA" | cut -c1-12):${KEY_HASH}"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
IDEMPOTENCY_KEY="manual-uat:${PROJECT_SLUG}:${RELEASE_VERSION}:${EXECUTION_ID}"
|
|
139
|
+
OUTCOME_REASON="executor=${EXECUTOR}; tested_sha=${TESTED_SHA}"
|
|
140
|
+
[ -z "$BUILD_VERSION" ] || OUTCOME_REASON="${OUTCOME_REASON}; build=${BUILD_VERSION}"
|
|
141
|
+
[ -z "$CHECKLIST_REF" ] || OUTCOME_REASON="${OUTCOME_REASON}; checklist=${CHECKLIST_REF}"
|
|
142
|
+
[ -z "$EVIDENCE_REF" ] || OUTCOME_REASON="${OUTCOME_REASON}; evidence=${EVIDENCE_REF}"
|
|
143
|
+
[ -z "$REMEDIATION_REF" ] || OUTCOME_REASON="${OUTCOME_REASON}; remediation=${REMEDIATION_REF}"
|
|
144
|
+
|
|
145
|
+
START_OUTPUT="$(mktemp)"
|
|
146
|
+
COMPLETE_OUTPUT="$(mktemp)"
|
|
147
|
+
cleanup() {
|
|
148
|
+
rm -f "$START_OUTPUT" "$COMPLETE_OUTPUT"
|
|
149
|
+
}
|
|
150
|
+
trap cleanup EXIT
|
|
151
|
+
|
|
152
|
+
COMMON_ARGS=(
|
|
153
|
+
--project-slug "$PROJECT_SLUG"
|
|
154
|
+
--release "$RELEASE_VERSION"
|
|
155
|
+
--sdlc-stage 4
|
|
156
|
+
--environment uat
|
|
157
|
+
--suite-kind uat
|
|
158
|
+
--iteration-key "uat:${RELEASE_VERSION}:${TESTED_SHA}"
|
|
159
|
+
--provider manual_uat
|
|
160
|
+
--external-run-id "$EXECUTION_ID"
|
|
161
|
+
--external-job-id "manual-uat-execution"
|
|
162
|
+
--commit-sha "$TESTED_SHA"
|
|
163
|
+
--branch "uat"
|
|
164
|
+
--workflow-name "Manual UAT execution"
|
|
165
|
+
--workflow-url "${DEVAUDIT_BASE_URL%/}/projects/${PROJECT_SLUG}/requirements/${RELEASE_VERSION}"
|
|
166
|
+
--idempotency-key "$IDEMPOTENCY_KEY"
|
|
167
|
+
--started-at "$EXECUTED_AT"
|
|
168
|
+
)
|
|
169
|
+
COMPLETE_ARGS=(
|
|
170
|
+
"${COMMON_ARGS[@]}"
|
|
171
|
+
--completed-at "$EXECUTED_AT"
|
|
172
|
+
--outcome "$OUTCOME"
|
|
173
|
+
--outcome-reason "$OUTCOME_REASON"
|
|
174
|
+
)
|
|
175
|
+
if [ -n "$REMEDIATION_REF" ]; then
|
|
176
|
+
COMPLETE_ARGS+=(--incident-reference "$REMEDIATION_REF")
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
"$REPORT_TEST_EXECUTION" start "${COMMON_ARGS[@]}" --output-file "$START_OUTPUT"
|
|
180
|
+
"$REPORT_TEST_EXECUTION" complete "${COMPLETE_ARGS[@]}" --output-file "$COMPLETE_OUTPUT"
|
|
181
|
+
|
|
182
|
+
if [ -n "$OUTPUT_FILE" ]; then
|
|
183
|
+
{
|
|
184
|
+
printf 'uat_execution_id=%s\n' "$EXECUTION_ID"
|
|
185
|
+
printf 'uat_idempotency_key=%s\n' "$IDEMPOTENCY_KEY"
|
|
186
|
+
printf 'uat_outcome=%s\n' "$OUTCOME"
|
|
187
|
+
printf 'uat_executed_at=%s\n' "$EXECUTED_AT"
|
|
188
|
+
grep '^execution_' "$COMPLETE_OUTPUT" || true
|
|
189
|
+
} >> "$OUTPUT_FILE"
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
echo "Recorded Stage 4 UAT execution for ${RELEASE_VERSION}: ${OUTCOME}"
|
|
193
|
+
echo " Execution ID: ${EXECUTION_ID}"
|
|
194
|
+
echo " Tested SHA: ${TESTED_SHA}"
|
|
195
|
+
echo " Executor: ${EXECUTOR}"
|