@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
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env bash
2
+ # record-uat-execution.test.sh - Regression tests for the manual UAT execution helper.
3
+
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ HELPER="$SCRIPT_DIR/record-uat-execution.sh"
8
+ [ -x "$HELPER" ] || chmod +x "$HELPER"
9
+
10
+ PASS=0
11
+ FAIL=0
12
+
13
+ ok() { echo " PASS: $1"; PASS=$((PASS + 1)); }
14
+ no() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
15
+
16
+ make_fixture() {
17
+ local dir="$1"
18
+ rm -rf "$dir"
19
+ mkdir -p "$dir/bin"
20
+ cat > "$dir/bin/report-test-execution.sh" <<'EOF'
21
+ #!/usr/bin/env bash
22
+ set -euo pipefail
23
+ LOG_FILE="${UAT_TEST_LOG:?}"
24
+ printf 'CALL:%s\n' "$*" >> "$LOG_FILE"
25
+ mode="$1"
26
+ shift
27
+ out=""
28
+ while [ "$#" -gt 0 ]; do
29
+ case "$1" in
30
+ --output-file) out="$2"; shift 2 ;;
31
+ *) shift ;;
32
+ esac
33
+ done
34
+ if [ -n "$out" ]; then
35
+ {
36
+ printf 'execution_supported=true\n'
37
+ printf 'execution_record_id=execution-%s\n' "$mode"
38
+ printf 'execution_idempotency_key=stub\n'
39
+ } >> "$out"
40
+ fi
41
+ EOF
42
+ chmod +x "$dir/bin/report-test-execution.sh"
43
+ }
44
+
45
+ run_helper() {
46
+ UAT_TEST_LOG="$1/uat.log" \
47
+ DEVAUDIT_BASE_URL="https://devaudit.example.test" \
48
+ DEVAUDIT_API_KEY="mc_test_dummy" \
49
+ REPORT_TEST_EXECUTION_HELPER="$1/bin/report-test-execution.sh" \
50
+ "$HELPER" "${@:2}"
51
+ }
52
+
53
+ assert_contains() {
54
+ local label="$1" needle="$2" file="$3"
55
+ if grep -Fq -- "$needle" "$file"; then ok "$label"; else no "$label"; fi
56
+ }
57
+
58
+ echo "=== record-uat-execution.sh tests ==="
59
+
60
+ case_passed_records_stage_four_pair() {
61
+ echo "case: passed UAT records Stage 4 start and complete"
62
+ local dir out exit_code
63
+ dir=$(mktemp -d)
64
+ make_fixture "$dir"
65
+ out="$dir/out.env"
66
+ run_helper "$dir" \
67
+ --project-slug fixture-app \
68
+ --release REQ-100 \
69
+ --outcome passed \
70
+ --executor "Jane Reviewer" \
71
+ --tested-sha abc123def456 \
72
+ --build-version build-42 \
73
+ --checklist-ref compliance/evidence/REQ-100/uat-checklist.md \
74
+ --evidence-ref https://devaudit.example.test/projects/fixture-app/releases/release-1 \
75
+ --executed-at 2026-07-20T12:00:00Z \
76
+ --output-file "$out" >"$dir/stdout.log" 2>"$dir/stderr.log" && exit_code=0 || exit_code=$?
77
+ if [ "$exit_code" -eq 0 ]; then ok "exit code 0"; else no "expected exit 0"; fi
78
+ assert_contains "called start" "CALL:start" "$dir/uat.log"
79
+ assert_contains "called complete" "CALL:complete" "$dir/uat.log"
80
+ assert_contains "stage 4" "--sdlc-stage 4" "$dir/uat.log"
81
+ assert_contains "uat environment" "--environment uat" "$dir/uat.log"
82
+ assert_contains "uat kind" "--suite-kind uat" "$dir/uat.log"
83
+ assert_contains "manual provider" "--provider manual_uat" "$dir/uat.log"
84
+ assert_contains "tested sha" "--commit-sha abc123def456" "$dir/uat.log"
85
+ assert_contains "passed outcome" "--outcome passed" "$dir/uat.log"
86
+ assert_contains "executor provenance" "executor=Jane Reviewer" "$dir/uat.log"
87
+ if grep -q '^uat_execution_id=' "$out"; then ok "output execution id"; else no "missing output execution id"; fi
88
+ rm -rf "$dir"
89
+ }
90
+
91
+ case_idempotency_is_stable() {
92
+ echo "case: derived execution id is stable for repeat command"
93
+ local dir out1 out2 id1 id2
94
+ dir=$(mktemp -d)
95
+ make_fixture "$dir"
96
+ out1="$dir/out1.env"
97
+ out2="$dir/out2.env"
98
+ run_helper "$dir" --project-slug fixture-app --release REQ-100 --outcome passed --executor "Jane Reviewer" --tested-sha abc123def456 --executed-at 2026-07-20T12:00:00Z --output-file "$out1" >/dev/null
99
+ run_helper "$dir" --project-slug fixture-app --release REQ-100 --outcome passed --executor "Jane Reviewer" --tested-sha abc123def456 --executed-at 2026-07-20T13:00:00Z --output-file "$out2" >/dev/null
100
+ id1=$(grep '^uat_idempotency_key=' "$out1")
101
+ id2=$(grep '^uat_idempotency_key=' "$out2")
102
+ if [ "$id1" = "$id2" ]; then ok "stable idempotency key"; else no "idempotency key changed"; fi
103
+ rm -rf "$dir"
104
+ }
105
+
106
+ case_failed_records_remediation() {
107
+ echo "case: failed UAT includes remediation reference"
108
+ local dir
109
+ dir=$(mktemp -d)
110
+ make_fixture "$dir"
111
+ run_helper "$dir" \
112
+ --project-slug fixture-app \
113
+ --release REQ-100 \
114
+ --outcome failed \
115
+ --executor "Jane Reviewer" \
116
+ --tested-sha abc123def456 \
117
+ --remediation-ref https://github.com/example/repo/issues/10 \
118
+ >/dev/null
119
+ assert_contains "failed outcome" "--outcome failed" "$dir/uat.log"
120
+ assert_contains "incident reference" "--incident-reference https://github.com/example/repo/issues/10" "$dir/uat.log"
121
+ rm -rf "$dir"
122
+ }
123
+
124
+ case_invalid_outcome_fails_before_calls() {
125
+ echo "case: invalid outcome fails before lifecycle calls"
126
+ local dir exit_code
127
+ dir=$(mktemp -d)
128
+ make_fixture "$dir"
129
+ run_helper "$dir" --project-slug fixture-app --release REQ-100 --outcome green --executor Jane --tested-sha abc123 >/dev/null 2>"$dir/stderr.log" && exit_code=0 || exit_code=$?
130
+ if [ "$exit_code" -ne 0 ]; then ok "exit code non-zero"; else no "expected non-zero"; fi
131
+ if [ ! -f "$dir/uat.log" ]; then ok "no lifecycle call"; else no "unexpected lifecycle call"; fi
132
+ rm -rf "$dir"
133
+ }
134
+
135
+ case_passed_records_stage_four_pair
136
+ case_idempotency_is_stable
137
+ case_failed_records_remediation
138
+ case_invalid_outcome_fails_before_calls
139
+
140
+ echo ""
141
+ echo "=== record-uat-execution.test.sh: $PASS passed, $FAIL failed ==="
142
+ if [ "$FAIL" -gt 0 ]; then
143
+ exit 1
144
+ fi
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env bash
2
+ # render-test-executions.sh - Render release test executions as markdown tables.
3
+ #
4
+ # Usage:
5
+ # bash scripts/render-test-executions.sh path/to/release-journey.json
6
+ #
7
+ # Supported input:
8
+ # First-class test execution payloads with `.testExecutions[]`.
9
+ #
10
+ # Output:
11
+ # Markdown suitable for inclusion under `## Test Executions` in
12
+ # `compliance/evidence/REQ-XXX/test-execution-summary.md`.
13
+
14
+ set -euo pipefail
15
+
16
+ INPUT_JSON="${1:-}"
17
+
18
+ if [ -z "$INPUT_JSON" ] || [ ! -f "$INPUT_JSON" ]; then
19
+ echo "Usage: $0 <release-journey.json>" >&2
20
+ exit 2
21
+ fi
22
+
23
+ if ! command -v jq >/dev/null 2>&1; then
24
+ echo "Error: jq is required." >&2
25
+ exit 1
26
+ fi
27
+
28
+ stage_label() {
29
+ case "$1" in
30
+ 1) printf '1 plan' ;;
31
+ 2) printf '2 implement_test' ;;
32
+ 3) printf '3 compile_evidence' ;;
33
+ 4) printf '4 uat_review' ;;
34
+ 5) printf '5 production' ;;
35
+ *) printf '%s unknown' "${1:-?}" ;;
36
+ esac
37
+ }
38
+
39
+ format_date() {
40
+ local value="$1"
41
+ if [ -z "$value" ] || [ "$value" = "null" ]; then
42
+ printf 'Unknown'
43
+ else
44
+ printf '%s' "$value" | sed -E 's/T/ /; s/Z$//; s/[.][0-9]+$//'
45
+ fi
46
+ }
47
+
48
+ render_test_executions() {
49
+ echo "| Source Release | SDLC Stage | Execution | Suite | Outcome | Workflow / Run | Related Evidence | Incident / Remediation | Date |"
50
+ echo "| --- | --- | --- | --- | --- | --- | --- | --- | --- |"
51
+
52
+ declare -A COUNTS=()
53
+ local rows
54
+ rows="$(
55
+ jq -r '
56
+ (.testExecutions // [])
57
+ | sort_by(.sourceRelease // "", .sdlcStage // 0, .startedAt // "", .completedAt // "", .suiteKind // .executionKind // "")
58
+ | .[]
59
+ | {
60
+ sourceRelease: (.sourceRelease // "Unknown"),
61
+ stageCode: ((.sdlcStage // 0) | tostring),
62
+ kind: (.suiteKind // .executionKind // "unknown"),
63
+ outcome: (.outcome // "unknown"),
64
+ workflow: (
65
+ if (.workflowUrl // "") != "" then
66
+ "[" + ((.workflowName // "Workflow")) + "](" + .workflowUrl + ")"
67
+ else
68
+ (.workflowName // "Manual")
69
+ end
70
+ ),
71
+ runMeta: (
72
+ [
73
+ (if (.externalRunId // "") != "" then "run " + .externalRunId else empty end),
74
+ (if (.externalRunAttempt // null) != null then "attempt " + (.externalRunAttempt | tostring) else empty end)
75
+ ]
76
+ | map(select(length > 0))
77
+ | join(", ")
78
+ ),
79
+ evidence: (
80
+ (.relatedEvidence // [])
81
+ | map(.displayName // .fileName // .name // .evidenceType // empty)
82
+ | map(select(length > 0))
83
+ | join(", ")
84
+ ),
85
+ incident: (
86
+ [
87
+ (.incidentReference // empty),
88
+ (.remediationReference // empty),
89
+ (.outcomeReason // empty)
90
+ ]
91
+ | map(select(length > 0))
92
+ | join("; ")
93
+ ),
94
+ date: (.completedAt // .startedAt // "")
95
+ }
96
+ | @base64
97
+ ' "$INPUT_JSON"
98
+ )"
99
+
100
+ if [ -z "$rows" ]; then
101
+ echo "| Unknown | Unknown | None | unknown | unknown | No test execution records returned | None | None | Unknown |"
102
+ echo
103
+ echo "**Final assessment:** No first-class test execution records were returned by the portal."
104
+ return 0
105
+ fi
106
+
107
+ while IFS= read -r row_b64; do
108
+ [ -n "$row_b64" ] || continue
109
+ local source_release stage_code kind outcome workflow run_meta evidence incident date
110
+ source_release="$(printf '%s' "$row_b64" | base64 -d | jq -r '.sourceRelease')"
111
+ stage_code="$(printf '%s' "$row_b64" | base64 -d | jq -r '.stageCode')"
112
+ kind="$(printf '%s' "$row_b64" | base64 -d | jq -r '.kind')"
113
+ outcome="$(printf '%s' "$row_b64" | base64 -d | jq -r '.outcome')"
114
+ workflow="$(printf '%s' "$row_b64" | base64 -d | jq -r '.workflow')"
115
+ run_meta="$(printf '%s' "$row_b64" | base64 -d | jq -r '.runMeta')"
116
+ evidence="$(printf '%s' "$row_b64" | base64 -d | jq -r '.evidence')"
117
+ incident="$(printf '%s' "$row_b64" | base64 -d | jq -r '.incident')"
118
+ date="$(printf '%s' "$row_b64" | base64 -d | jq -r '.date')"
119
+ [ -n "$source_release" ] || continue
120
+ local group_key execution_ordinal workflow_cell evidence_cell incident_cell
121
+ group_key="${source_release}|${stage_code}"
122
+ COUNTS["$group_key"]=$(( ${COUNTS["$group_key"]:-0} + 1 ))
123
+ execution_ordinal="#${COUNTS[$group_key]}"
124
+ workflow_cell="$workflow"
125
+ if [ -n "$run_meta" ]; then
126
+ workflow_cell="${workflow_cell} (${run_meta})"
127
+ fi
128
+ evidence_cell="${evidence:-None}"
129
+ incident_cell="${incident:-None}"
130
+ printf '| %s | %s | %s | %s | %s | %s | %s | %s | %s |\n' \
131
+ "$source_release" \
132
+ "$(stage_label "$stage_code")" \
133
+ "$execution_ordinal" \
134
+ "$kind" \
135
+ "$outcome" \
136
+ "$workflow_cell" \
137
+ "$evidence_cell" \
138
+ "$incident_cell" \
139
+ "$(format_date "$date")"
140
+ done <<<"$rows"
141
+
142
+ echo
143
+ echo "**Final assessment:** Stage-scoped execution numbering is authoritative from first-class portal test execution records."
144
+ }
145
+
146
+ render_test_executions
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env bash
2
- # render-test-cycles.test.sh Tests for render-test-cycles.sh.
2
+ # render-test-executions.test.sh - Tests for render-test-executions.sh.
3
3
 
4
4
  set -euo pipefail
5
5
 
6
6
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
- HELPER="$SCRIPT_DIR/render-test-cycles.sh"
7
+ HELPER="$SCRIPT_DIR/render-test-executions.sh"
8
8
  [ -x "$HELPER" ] || chmod +x "$HELPER"
9
9
 
10
10
  PASS=0
@@ -27,15 +27,15 @@ assert_contains() {
27
27
  fi
28
28
  }
29
29
 
30
- echo "=== render-test-cycles.sh tests ==="
30
+ echo "=== render-test-executions.sh tests ==="
31
31
 
32
32
  cat > "$WORK/first-class.json" <<'EOF'
33
33
  {
34
- "cycles": [
34
+ "testExecutions": [
35
35
  {
36
36
  "sourceRelease": "REQ-091",
37
37
  "sdlcStage": 2,
38
- "cycleKind": "quality_gate",
38
+ "suiteKind": "quality_gate",
39
39
  "outcome": "failed",
40
40
  "workflowName": "Quality Gates",
41
41
  "workflowUrl": "https://github.com/example/repo/actions/runs/100",
@@ -49,7 +49,7 @@ cat > "$WORK/first-class.json" <<'EOF'
49
49
  {
50
50
  "sourceRelease": "REQ-091",
51
51
  "sdlcStage": 2,
52
- "cycleKind": "quality_gate",
52
+ "suiteKind": "quality_gate",
53
53
  "outcome": "passed",
54
54
  "workflowName": "Quality Gates",
55
55
  "workflowUrl": "https://github.com/example/repo/actions/runs/101",
@@ -63,7 +63,7 @@ cat > "$WORK/first-class.json" <<'EOF'
63
63
  {
64
64
  "sourceRelease": "REQ-090",
65
65
  "sdlcStage": 4,
66
- "cycleKind": "uat",
66
+ "suiteKind": "uat",
67
67
  "outcome": "passed",
68
68
  "workflowName": "UAT Review",
69
69
  "workflowUrl": "https://portal.example/releases/req-090",
@@ -76,32 +76,26 @@ cat > "$WORK/first-class.json" <<'EOF'
76
76
  EOF
77
77
 
78
78
  OUTPUT="$(bash "$HELPER" "$WORK/first-class.json")"
79
- assert_contains "first-class heading rendered" "| Source Release | SDLC Stage | Cycle | Kind | Outcome | Workflow / Run | Related Evidence | Incident / Remediation | Date |" "$OUTPUT"
79
+ assert_contains "first-class heading rendered" "| Source Release | SDLC Stage | Execution | Suite | Outcome | Workflow / Run | Related Evidence | Incident / Remediation | Date |" "$OUTPUT"
80
80
  assert_contains "stage label rendered" "| REQ-091 | 2 implement_test | #1 | quality_gate | failed | [Quality Gates](https://github.com/example/repo/actions/runs/100) (run 100, attempt 1) | quality-gates.json | #501 | 2026-07-15 08:05:00 |" "$OUTPUT"
81
81
  assert_contains "ordinal increments within source release + stage" "| REQ-091 | 2 implement_test | #2 | quality_gate | passed | [Quality Gates](https://github.com/example/repo/actions/runs/101) (run 101, attempt 2) | quality-gates-rerun.json | PR #507 | 2026-07-15 09:07:00 |" "$OUTPUT"
82
82
  assert_contains "different release/stage restarts numbering" "| REQ-090 | 4 uat_review | #1 | uat | passed | [UAT Review](https://portal.example/releases/req-090) | uat-checklist.md | None | 2026-07-15 10:12:00 |" "$OUTPUT"
83
- assert_contains "first-class final assessment rendered" "Stage-scoped cycle numbering is authoritative from first-class portal cycle records." "$OUTPUT"
83
+ assert_contains "first-class final assessment rendered" "Stage-scoped execution numbering is authoritative from first-class portal test execution records." "$OUTPUT"
84
84
 
85
85
  cat > "$WORK/legacy.json" <<'EOF'
86
86
  {
87
87
  "legacyCycles": [
88
88
  {
89
89
  "testCycleId": "287654321",
90
- "workflowName": "Legacy E2E",
91
- "workflowUrl": "https://github.com/example/repo/actions/runs/287654321",
92
- "relatedEvidence": [{ "fileName": "e2e-results.json" }, { "fileName": "screenshot.png" }],
93
- "notes": "grouped from uploaded evidence only",
94
- "date": "2026-07-14T18:30:00Z"
90
+ "workflowName": "Legacy E2E"
95
91
  }
96
92
  ]
97
93
  }
98
94
  EOF
99
95
 
100
96
  OUTPUT="$(bash "$HELPER" "$WORK/legacy.json")"
101
- assert_contains "legacy fallback note rendered" "Legacy portal fallback — first-class cycle records unavailable; grouped by uploaded evidence" "$OUTPUT"
102
- assert_contains "legacy row rendered" "| Legacy grouped evidence | legacy grouping | 287654321 | legacy | unknown | [Legacy E2E](https://github.com/example/repo/actions/runs/287654321) | e2e-results.json, screenshot.png | grouped from uploaded evidence only | 2026-07-14 18:30:00 |" "$OUTPUT"
103
- assert_contains "legacy final assessment rendered" "Legacy grouping was used because the portal did not expose first-class cycle records." "$OUTPUT"
97
+ assert_contains "legacy grouping is ignored" "No first-class test execution records were returned by the portal." "$OUTPUT"
104
98
 
105
99
  echo
106
- echo "=== render-test-cycles.test.sh: $PASS passed, $FAIL failed ==="
100
+ echo "=== render-test-executions.test.sh: $PASS passed, $FAIL failed ==="
107
101
  [ "$FAIL" -eq 0 ]
@@ -1,18 +1,20 @@
1
1
  #!/usr/bin/env bash
2
- # report-test-cycle.sh Producer helper for first-class cycle lifecycle events.
2
+ # report-test-execution.sh - Producer helper for first-class test execution lifecycle events.
3
3
  #
4
4
  # Usage:
5
- # ./scripts/report-test-cycle.sh start|complete [flags...]
5
+ # ./scripts/report-test-execution.sh start|complete [flags...]
6
6
  #
7
7
  # Required flags:
8
8
  # --project-slug <slug>
9
9
  # --release <version>
10
10
  # --sdlc-stage <1-5>
11
11
  # --environment <ci|uat|production>
12
- # --cycle-kind <kind>
12
+ # --suite-kind <kind>
13
13
  # --idempotency-key <key>
14
14
  #
15
15
  # Optional flags:
16
+ # --iteration-key <key>
17
+ # --iteration-ordinal <n>
16
18
  # --provider <name> Defaults to github_actions
17
19
  # --external-run-id <id>
18
20
  # --external-run-attempt <n>
@@ -26,17 +28,18 @@
26
28
  # --outcome <value> Terminal outcome for `complete`
27
29
  # --outcome-reason <text>
28
30
  # --incident-reference <text>
31
+ # --remediation-reference <text>
29
32
  # --output-file <path> Writes key=value outputs for callers
30
33
  #
31
34
  # Output keys:
32
- # cycle_supported=true|false
33
- # cycle_release_id=<uuid-if-resolved>
34
- # cycle_release_version=<exact-version-if-resolved>
35
- # cycle_record_id=<uuid-if-created-or-updated>
36
- # cycle_idempotency_key=<input-key>
37
- # cycle_started_at=<iso8601-if-known>
38
- # cycle_completed_at=<iso8601-if-known>
39
- # cycle_endpoint=<start|complete|reconcile-if-used>
35
+ # execution_supported=true
36
+ # execution_release_id=<uuid>
37
+ # execution_release_version=<exact-version>
38
+ # execution_record_id=<uuid-if-created-or-updated>
39
+ # execution_idempotency_key=<input-key>
40
+ # execution_started_at=<iso8601-if-known>
41
+ # execution_completed_at=<iso8601-if-known>
42
+ # execution_endpoint=<start|complete|reconcile-if-used>
40
43
 
41
44
  set -euo pipefail
42
45
 
@@ -58,7 +61,9 @@ PROJECT_SLUG=""
58
61
  RELEASE_VERSION=""
59
62
  SDLC_STAGE=""
60
63
  ENVIRONMENT=""
61
- CYCLE_KIND=""
64
+ SUITE_KIND=""
65
+ ITERATION_KEY=""
66
+ ITERATION_ORDINAL=""
62
67
  PROVIDER="github_actions"
63
68
  EXTERNAL_RUN_ID=""
64
69
  EXTERNAL_RUN_ATTEMPT=""
@@ -73,6 +78,7 @@ COMPLETED_AT=""
73
78
  OUTCOME=""
74
79
  OUTCOME_REASON=""
75
80
  INCIDENT_REFERENCE=""
81
+ REMEDIATION_REFERENCE=""
76
82
  OUTPUT_FILE=""
77
83
 
78
84
  while [ "$#" -gt 0 ]; do
@@ -81,7 +87,9 @@ while [ "$#" -gt 0 ]; do
81
87
  --release) RELEASE_VERSION="$2"; shift 2 ;;
82
88
  --sdlc-stage) SDLC_STAGE="$2"; shift 2 ;;
83
89
  --environment) ENVIRONMENT="$2"; shift 2 ;;
84
- --cycle-kind) CYCLE_KIND="$2"; shift 2 ;;
90
+ --suite-kind) SUITE_KIND="$2"; shift 2 ;;
91
+ --iteration-key) ITERATION_KEY="$2"; shift 2 ;;
92
+ --iteration-ordinal) ITERATION_ORDINAL="$2"; shift 2 ;;
85
93
  --provider) PROVIDER="$2"; shift 2 ;;
86
94
  --external-run-id) EXTERNAL_RUN_ID="$2"; shift 2 ;;
87
95
  --external-run-attempt) EXTERNAL_RUN_ATTEMPT="$2"; shift 2 ;;
@@ -96,6 +104,7 @@ while [ "$#" -gt 0 ]; do
96
104
  --outcome) OUTCOME="$2"; shift 2 ;;
97
105
  --outcome-reason) OUTCOME_REASON="$2"; shift 2 ;;
98
106
  --incident-reference) INCIDENT_REFERENCE="$2"; shift 2 ;;
107
+ --remediation-reference) REMEDIATION_REFERENCE="$2"; shift 2 ;;
99
108
  --output-file) OUTPUT_FILE="$2"; shift 2 ;;
100
109
  *) echo "Unknown option: $1"; exit 1 ;;
101
110
  esac
@@ -113,7 +122,7 @@ require_arg "$PROJECT_SLUG" "--project-slug"
113
122
  require_arg "$RELEASE_VERSION" "--release"
114
123
  require_arg "$SDLC_STAGE" "--sdlc-stage"
115
124
  require_arg "$ENVIRONMENT" "--environment"
116
- require_arg "$CYCLE_KIND" "--cycle-kind"
125
+ require_arg "$SUITE_KIND" "--suite-kind"
117
126
  require_arg "$IDEMPOTENCY_KEY" "--idempotency-key"
118
127
 
119
128
  if [ -z "${DEVAUDIT_BASE_URL:-}" ]; then
@@ -146,11 +155,16 @@ fi
146
155
  case "$OUTCOME" in
147
156
  ""|passed|failed|cancelled|skipped|timed_out|action_required|unknown) ;;
148
157
  *)
149
- echo "Error: --outcome must be a terminal cycle outcome" >&2
158
+ echo "Error: --outcome must be a terminal test execution outcome" >&2
150
159
  exit 1
151
160
  ;;
152
161
  esac
153
162
 
163
+ if [ -n "$ITERATION_ORDINAL" ] && ! [[ "$ITERATION_ORDINAL" =~ ^[0-9]+$ ]]; then
164
+ echo "Error: --iteration-ordinal must be a positive integer" >&2
165
+ exit 1
166
+ fi
167
+
154
168
  iso_now() {
155
169
  date -u +"%Y-%m-%dT%H:%M:%SZ"
156
170
  }
@@ -171,18 +185,6 @@ write_output() {
171
185
  fi
172
186
  }
173
187
 
174
- emit_fallback() {
175
- local release_id="${1:-}" resolved_version="${2:-}"
176
- write_output cycle_supported false
177
- write_output cycle_release_id "$release_id"
178
- write_output cycle_release_version "$resolved_version"
179
- write_output cycle_record_id ""
180
- write_output cycle_idempotency_key "$IDEMPOTENCY_KEY"
181
- write_output cycle_started_at "$STARTED_AT"
182
- write_output cycle_completed_at "$COMPLETED_AT"
183
- write_output cycle_endpoint "$MODE"
184
- }
185
-
186
188
  uri_escape() {
187
189
  jq -rn --arg v "$1" '$v|@uri'
188
190
  }
@@ -199,23 +201,21 @@ resolve_release_exact() {
199
201
  --max-time "$MAX_TIME_SECONDS" \
200
202
  "$url")
201
203
  if [ "$code" -lt 200 ] || [ "$code" -ge 300 ]; then
202
- echo "::warning::Cycle lifecycle skipped: could not resolve release ${RELEASE_VERSION} (HTTP ${code})." >&2
204
+ echo "Error: could not resolve release ${RELEASE_VERSION} for test execution lifecycle (HTTP ${code})." >&2
203
205
  rm -f "$body"
204
- emit_fallback "" ""
205
206
  return 1
206
207
  fi
207
208
  latest_id=$(jq -r '.latest.id // empty' "$body" 2>/dev/null || true)
208
209
  latest_version=$(jq -r '.latest.version // empty' "$body" 2>/dev/null || true)
209
210
  rm -f "$body"
210
211
  if [ -z "$latest_id" ] || [ "$latest_version" != "$RELEASE_VERSION" ]; then
211
- echo "::warning::Cycle lifecycle skipped: release resolve for ${RELEASE_VERSION} was empty or ambiguous (latest=${latest_version:-none})." >&2
212
- emit_fallback "$latest_id" "$latest_version"
212
+ echo "Error: release resolve for ${RELEASE_VERSION} was empty or ambiguous (latest=${latest_version:-none})." >&2
213
213
  return 1
214
214
  fi
215
215
  RELEASE_ID="$latest_id"
216
216
  RESOLVED_RELEASE_VERSION="$latest_version"
217
- write_output cycle_release_id "$RELEASE_ID"
218
- write_output cycle_release_version "$RESOLVED_RELEASE_VERSION"
217
+ write_output execution_release_id "$RELEASE_ID"
218
+ write_output execution_release_version "$RESOLVED_RELEASE_VERSION"
219
219
  return 0
220
220
  }
221
221
 
@@ -231,7 +231,9 @@ build_payload() {
231
231
  --arg mode "$mode" \
232
232
  --arg sdlcStage "$SDLC_STAGE" \
233
233
  --arg environment "$ENVIRONMENT" \
234
- --arg cycleKind "$CYCLE_KIND" \
234
+ --arg suiteKind "$SUITE_KIND" \
235
+ --arg iterationKey "$ITERATION_KEY" \
236
+ --arg iterationOrdinal "$ITERATION_ORDINAL" \
235
237
  --arg provider "$PROVIDER" \
236
238
  --arg externalRunId "$EXTERNAL_RUN_ID" \
237
239
  --argjson externalRunAttempt "$EXTERNAL_RUN_ATTEMPT_JSON" \
@@ -246,12 +248,16 @@ build_payload() {
246
248
  --arg workflowName "$WORKFLOW_NAME" \
247
249
  --arg workflowUrl "$WORKFLOW_URL" \
248
250
  --arg incidentReference "$INCIDENT_REFERENCE" \
251
+ --arg remediationReference "$REMEDIATION_REFERENCE" \
249
252
  '
250
253
  {
251
254
  schemaVersion: $schemaVersion,
252
255
  sdlcStage: ($sdlcStage | tonumber),
253
256
  environment: $environment,
254
- cycleKind: $cycleKind,
257
+ cycleKind: $suiteKind,
258
+ suiteKind: $suiteKind,
259
+ iterationKey: ($iterationKey | if . == "" then null else . end),
260
+ iterationOrdinal: ($iterationOrdinal | if . == "" then null else tonumber end),
255
261
  provider: $provider,
256
262
  externalRunId: ($externalRunId | if . == "" then null else . end),
257
263
  externalRunAttempt: $externalRunAttempt,
@@ -262,7 +268,8 @@ build_payload() {
262
268
  branch: ($branch | if . == "" then null else . end),
263
269
  workflowName: ($workflowName | if . == "" then null else . end),
264
270
  workflowUrl: ($workflowUrl | if . == "" then null else . end),
265
- incidentReference: ($incidentReference | if . == "" then null else . end)
271
+ incidentReference: ($incidentReference | if . == "" then null else . end),
272
+ remediationReference: ($remediationReference | if . == "" then null else . end)
266
273
  }
267
274
  + (if $mode == "start"
268
275
  then { outcome: "running" }
@@ -275,7 +282,7 @@ build_payload() {
275
282
  '
276
283
  }
277
284
 
278
- post_cycle_event() {
285
+ post_execution_event() {
279
286
  local endpoint="$1" payload="$2" body code
280
287
  body=$(mktemp)
281
288
  code=$(curl -sS -o "$body" -w "%{http_code}" \
@@ -286,59 +293,44 @@ post_cycle_event() {
286
293
  --max-time "$MAX_TIME_SECONDS" \
287
294
  "${DEVAUDIT_BASE_URL}/api/ci/releases/${RELEASE_ID}/cycles/${endpoint}" \
288
295
  -d "$payload")
289
- CYCLE_HTTP_CODE="$code"
290
- CYCLE_RESPONSE_BODY_FILE="$body"
291
- }
292
-
293
- handle_unavailable_endpoint() {
294
- case "$CYCLE_HTTP_CODE" in
295
- 404|405|501)
296
- echo "::warning::Cycle lifecycle endpoint ${MODE} unavailable on this portal (HTTP ${CYCLE_HTTP_CODE}); continuing in legacy testCycleId mode." >&2
297
- emit_fallback "$RELEASE_ID" "$RESOLVED_RELEASE_VERSION"
298
- rm -f "$CYCLE_RESPONSE_BODY_FILE"
299
- return 0
300
- ;;
301
- esac
302
- return 1
296
+ EXECUTION_HTTP_CODE="$code"
297
+ EXECUTION_RESPONSE_BODY_FILE="$body"
303
298
  }
304
299
 
305
300
  if ! resolve_release_exact; then
306
- exit 0
301
+ exit 1
307
302
  fi
308
303
 
309
304
  PAYLOAD="$(build_payload "$MODE")"
310
305
  ENDPOINT="$MODE"
311
- post_cycle_event "$ENDPOINT" "$PAYLOAD"
306
+ post_execution_event "$ENDPOINT" "$PAYLOAD"
312
307
 
313
- if [ "$MODE" = "complete" ] && [ "$CYCLE_HTTP_CODE" -eq 400 ]; then
314
- if grep -q 'different terminal outcome' "$CYCLE_RESPONSE_BODY_FILE" 2>/dev/null; then
308
+ if [ "$MODE" = "complete" ] && [ "$EXECUTION_HTTP_CODE" -eq 400 ]; then
309
+ if grep -q 'different terminal outcome' "$EXECUTION_RESPONSE_BODY_FILE" 2>/dev/null; then
315
310
  ENDPOINT="reconcile"
316
- post_cycle_event "$ENDPOINT" "$PAYLOAD"
311
+ post_execution_event "$ENDPOINT" "$PAYLOAD"
317
312
  fi
318
313
  fi
319
314
 
320
- if [ "$CYCLE_HTTP_CODE" -lt 200 ] || [ "$CYCLE_HTTP_CODE" -ge 300 ]; then
321
- if handle_unavailable_endpoint; then
322
- exit 0
323
- fi
324
- BODY_EXCERPT="$(head -c 500 "$CYCLE_RESPONSE_BODY_FILE" 2>/dev/null || true)"
325
- rm -f "$CYCLE_RESPONSE_BODY_FILE"
326
- echo "Error: cycle ${ENDPOINT} failed for ${RELEASE_VERSION} (HTTP ${CYCLE_HTTP_CODE})${BODY_EXCERPT:+ — ${BODY_EXCERPT}}" >&2
315
+ if [ "$EXECUTION_HTTP_CODE" -lt 200 ] || [ "$EXECUTION_HTTP_CODE" -ge 300 ]; then
316
+ BODY_EXCERPT="$(head -c 500 "$EXECUTION_RESPONSE_BODY_FILE" 2>/dev/null || true)"
317
+ rm -f "$EXECUTION_RESPONSE_BODY_FILE"
318
+ echo "Error: test execution ${ENDPOINT} failed for ${RELEASE_VERSION} (HTTP ${EXECUTION_HTTP_CODE})${BODY_EXCERPT:+ - ${BODY_EXCERPT}}" >&2
327
319
  exit 1
328
320
  fi
329
321
 
330
- CYCLE_RECORD_ID=$(jq -r '.id // empty' "$CYCLE_RESPONSE_BODY_FILE" 2>/dev/null || true)
331
- rm -f "$CYCLE_RESPONSE_BODY_FILE"
332
- if [ -z "$CYCLE_RECORD_ID" ]; then
333
- echo "Error: cycle ${ENDPOINT} succeeded but response did not include an id" >&2
322
+ EXECUTION_RECORD_ID=$(jq -r '.id // empty' "$EXECUTION_RESPONSE_BODY_FILE" 2>/dev/null || true)
323
+ rm -f "$EXECUTION_RESPONSE_BODY_FILE"
324
+ if [ -z "$EXECUTION_RECORD_ID" ]; then
325
+ echo "Error: test execution ${ENDPOINT} succeeded but response did not include an id" >&2
334
326
  exit 1
335
327
  fi
336
328
 
337
- write_output cycle_supported true
338
- write_output cycle_record_id "$CYCLE_RECORD_ID"
339
- write_output cycle_idempotency_key "$IDEMPOTENCY_KEY"
340
- write_output cycle_started_at "$STARTED_AT"
341
- write_output cycle_completed_at "$COMPLETED_AT"
342
- write_output cycle_endpoint "$ENDPOINT"
329
+ write_output execution_supported true
330
+ write_output execution_record_id "$EXECUTION_RECORD_ID"
331
+ write_output execution_idempotency_key "$IDEMPOTENCY_KEY"
332
+ write_output execution_started_at "$STARTED_AT"
333
+ write_output execution_completed_at "$COMPLETED_AT"
334
+ write_output execution_endpoint "$ENDPOINT"
343
335
 
344
- echo "Cycle ${ENDPOINT} recorded for ${RELEASE_VERSION}: ${CYCLE_RECORD_ID}"
336
+ echo "Test execution ${ENDPOINT} recorded for ${RELEASE_VERSION}: ${EXECUTION_RECORD_ID}"