@metasession.co/devaudit-cli 0.3.18 → 0.3.20

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.
Files changed (36) hide show
  1. package/dist/index.js +35 -34
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/scripts/upload-evidence.sh +23 -19
  5. package/sdlc/files/_common/governance/incident-report.md.template +6 -1
  6. package/sdlc/files/_common/governance/nil-incident-report.md.template +5 -2
  7. package/sdlc/files/_common/scripts/check-release-pr-scope.sh +8 -1
  8. package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +58 -10
  9. package/sdlc/files/_common/scripts/check-self-hosted-runner.sh +103 -0
  10. package/sdlc/files/_common/scripts/check-self-hosted-runner.test.sh +74 -0
  11. package/sdlc/files/_common/scripts/record-uat-execution.sh +195 -0
  12. package/sdlc/files/_common/scripts/record-uat-execution.test.sh +144 -0
  13. package/sdlc/files/_common/scripts/render-test-executions.sh +146 -0
  14. package/sdlc/files/_common/scripts/{render-test-cycles.test.sh → render-test-executions.test.sh} +12 -18
  15. package/sdlc/files/_common/scripts/{report-test-cycle.sh → report-test-execution.sh} +66 -74
  16. package/sdlc/files/_common/scripts/report-test-execution.test.sh +177 -0
  17. package/sdlc/files/_common/scripts/submit-for-uat-review.sh +21 -0
  18. package/sdlc/files/_common/scripts/upload-evidence.sh +23 -19
  19. package/sdlc/files/_common/scripts/validate-commits.sh +6 -4
  20. package/sdlc/files/_common/scripts/validate-commits.test.sh +15 -0
  21. package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +9 -7
  22. package/sdlc/files/_common/skills/e2e-test-engineer/references/e2e-regression-3-tier.yml +4 -1
  23. package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +16 -14
  24. package/sdlc/files/ci/ci.yml.template +54 -44
  25. package/sdlc/files/ci/compliance-evidence.yml.template +135 -46
  26. package/sdlc/files/ci/feature-e2e.yml.template +58 -18
  27. package/sdlc/files/ci/incident-export.yml.template +18 -4
  28. package/sdlc/files/ci/post-deploy-prod.yml.template +33 -30
  29. package/sdlc/files/ci/python/ci.yml.template +14 -14
  30. package/sdlc/files/ci/quality-gates-provenance.yml.template +3 -0
  31. package/sdlc/package.json +1 -1
  32. package/sdlc/src/blueprints/3-compile-evidence.raw.md +10 -10
  33. package/sdlc/src/blueprints/4-submit-for-review.raw.md +1 -1
  34. package/sdlc/src/blueprints/5-deploy-main.raw.md +1 -1
  35. package/sdlc/src/blueprints/implementing-an-sdlc-issue.raw.md +3 -3
  36. 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.18",
3
+ "version": "0.3.20",
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.18",
36
+ "@metasession.co/devaudit-plugin-sdk": "^0.3.20",
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-cycle <id> Test cycle identifier (typically the CI run
46
- # ID). Forwarded as `testCycleId`; lets the
47
- # portal group evidence by test cycle per
48
- # ISO/IEC/IEEE 29119-3. Optional — older
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
- # cycle | approval. Forwarded as
52
- # `evidenceScope`; optional and ignored by
53
- # older portals.
54
- # --test-cycle-record-id <id> First-class portal cycle UUID. Requires
55
- # `--evidence-scope cycle`. Forwarded as
56
- # `testCycleRecordId`; preserved alongside
57
- # legacy `--test-cycle` during dual-write.
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-cycle) TEST_CYCLE="$2"; shift 2 ;;
139
+ --test-execution) TEST_CYCLE="$2"; shift 2 ;;
140
140
  --evidence-scope) EVIDENCE_SCOPE="$2"; shift 2 ;;
141
- --test-cycle-record-id) TEST_CYCLE_RECORD_ID="$2"; shift 2 ;;
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|cycle|approval)$ ]]; then
170
- echo "Error: --evidence-scope must be one of: release, stage, cycle, approval"
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" != "cycle" ]; then
174
- echo "Error: --test-cycle-record-id requires --evidence-scope cycle"
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 "$EVIDENCE_SCOPE" ] && CURL_ARGS+=(-F "evidenceScope=${EVIDENCE_SCOPE}")
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` globs `incident-report*.md` under both layouts and uploads each non-stub file as `incident_report` evidence via the `upload_governance` helper. The starter stub is filtered out centrally by `upload-evidence.sh` (devaudit#133).
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 cycle for release `<version>`.
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 cycle — e.g. "E2E Regression run on <date>", "Phase 6 full suite + Phase 3 re-run">
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
- CURRENT_RELEASE=$(bash scripts/derive-release-version.sh)
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: bare-date releases require an explicit standalone declaration ---"
138
- make_fixture "$WORK/test5" "chore: standalone housekeeping release"
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/test5/compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${BARE_DATE_VERSION}.json" <<EOF
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/test5.out"
149
- if run_check "$WORK/test5" "$OUT" env PR_TITLE="Standalone housekeeping promotion: ${BARE_DATE_VERSION}" PR_BODY=$"## Release\n- Release: ${BARE_DATE_VERSION}\n"; then
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 6: bare-date releases without the exception marker fail ---"
159
- make_fixture "$WORK/test6" "chore: standalone housekeeping release"
160
- cat > "$WORK/test6/compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${BARE_DATE_VERSION}.json" <<EOF
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/test6.out"
169
- if run_check "$WORK/test6" "$OUT" env PR_TITLE="Release: ${BARE_DATE_VERSION}" PR_BODY=$"## Release\n- Release: ${BARE_DATE_VERSION}\n"; then
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}"