@metasession.co/devaudit-cli 0.3.13 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metasession.co/devaudit-cli",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
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.13",
36
+ "@metasession.co/devaudit-plugin-sdk": "^0.3.14",
37
37
  "ajv": "^8.20.0",
38
38
  "commander": "^12.1.0",
39
39
  "consola": "^3.2.3",
@@ -76,7 +76,7 @@ extract_ticket_title() {
76
76
  printf '%s' "$line" \
77
77
  | sed -E 's/^\*\*Requirement:\*\*[[:space:]]*//' \
78
78
  | sed -E 's/^REQ-[0-9]+[[:space:]]*[—–:-][[:space:]]*//' \
79
- | sed -E 's/[[:space:]]*$//'
79
+ | sed -E 's/^[[:space:]]+|[[:space:]]+$//g'
80
80
  return 0
81
81
  fi
82
82
  line=$(grep -m1 '^# ' "$file" 2>/dev/null || true)
@@ -227,6 +227,7 @@ for version in "${EXPLICIT_PREDECESSORS[@]}"; do
227
227
  --arg commitTo "$commit_to" \
228
228
  --arg prNumber "$pr_number" \
229
229
  --arg prUrl "$pr_url" \
230
+ --arg originalTitle "${title:-Release $version}" \
230
231
  '. + [{
231
232
  version: $version,
232
233
  role: $role,
@@ -236,7 +237,14 @@ for version in "${EXPLICIT_PREDECESSORS[@]}"; do
236
237
  commitFrom: (if $commitFrom == "" then null else $commitFrom end),
237
238
  commitTo: (if $commitTo == "" then null else $commitTo end),
238
239
  prNumber: (if $prNumber == "" then null else ($prNumber | tonumber) end),
239
- prUrl: (if $prUrl == "" then null else $prUrl end)
240
+ prUrl: (if $prUrl == "" then null else $prUrl end),
241
+ originalTitle: $originalTitle,
242
+ evidenceInheritancePolicy: {
243
+ mode: "all_eligible",
244
+ includeCycles: true,
245
+ scopes: ["release", "stage", "cycle"],
246
+ reason: $reason
247
+ }
240
248
  }]' <<<"$MEMBERS"
241
249
  )"
242
250
  PREDECESSOR_LINES+=("- \`${version}\` (${role}/${relationship}) — ${title:-Untitled release ticket}")
@@ -267,6 +275,7 @@ fi
267
275
  GENERATOR_VERSION="$(jq -r '.version' cli/package.json 2>/dev/null || echo '0.0.0')"
268
276
  REPOSITORY_SLUG="$(git config --get remote.origin.url 2>/dev/null | sed -E 's#(git@github.com:|https://github.com/)##; s#\.git$##' || true)"
269
277
  GENERATED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
278
+ [ -n "$REPOSITORY_SLUG" ] || REPOSITORY_SLUG="${GITHUB_REPOSITORY:-local/unknown}"
270
279
 
271
280
  MANIFEST_BASE="$(
272
281
  jq -c -n \
@@ -277,7 +286,7 @@ MANIFEST_BASE="$(
277
286
  --arg repository "$REPOSITORY_SLUG" \
278
287
  --arg generatedAt "$GENERATED_AT" \
279
288
  '{
280
- schemaVersion: 1,
289
+ schemaVersion: 2,
281
290
  approvalRelease: { version: $version },
282
291
  coreRelease: { version: $version },
283
292
  members: ($members | fromjson),
@@ -291,7 +300,7 @@ MANIFEST_BASE="$(
291
300
  }'
292
301
  )"
293
302
 
294
- HASH_INPUT="$(jq -S 'del(.generator.generatedAt)' <<<"$MANIFEST_BASE")"
303
+ HASH_INPUT="$(jq -cS 'del(.generator.generatedAt)' <<<"$MANIFEST_BASE")"
295
304
  MANIFEST_HASH="sha256:$(printf '%s' "$HASH_INPUT" | sha256sum | awk '{print $1}')"
296
305
  MANIFEST_JSON="$(jq --arg manifestHash "$MANIFEST_HASH" '. + { manifestHash: $manifestHash }' <<<"$MANIFEST_BASE")"
297
306
 
@@ -150,6 +150,11 @@ assert_eq "member version recorded" "REQ-041" "$(jq -r '.members[0].version' "$J
150
150
  assert_eq "member role recorded" "predecessor" "$(jq -r '.members[0].role' "$JSON_OUT")"
151
151
  assert_eq "member relationship recorded" "superseded" "$(jq -r '.members[0].relationship' "$JSON_OUT")"
152
152
  assert_eq "manifest hash present" "true" "$(jq -r 'has("manifestHash")' "$JSON_OUT")"
153
+ assert_eq "manifest schema v2" "2" "$(jq -r '.schemaVersion' "$JSON_OUT")"
154
+ assert_eq "original title recorded" "Prior tracked release" "$(jq -r '.members[0].originalTitle' "$JSON_OUT")"
155
+ assert_eq "inheritance policy explicit" "all_eligible" "$(jq -r '.members[0].evidenceInheritancePolicy.mode' "$JSON_OUT")"
156
+ EXPECTED_HASH="sha256:$(jq -cS 'del(.manifestHash, .generator.generatedAt)' "$JSON_OUT" | tr -d '\n' | sha256sum | awk '{print $1}')"
157
+ assert_eq "canonical manifest hash verifies" "$EXPECTED_HASH" "$(jq -r '.manifestHash' "$JSON_OUT")"
153
158
  echo
154
159
 
155
160
  # Test 3: ambiguous predecessor ownership fails.
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bash
2
+ # Records authoritative required-check state independently of evidence upload.
3
+ set -euo pipefail
4
+
5
+ PROJECT_SLUG="" RELEASE_VERSION="" CHECK_KEY="" LABEL="" PROVIDER="github_actions"
6
+ STATUS="" REQUIRED="true" EXTERNAL_RUN_ID="" EXTERNAL_URL="" COMMIT_SHA="" BRANCH=""
7
+ STARTED_AT="" COMPLETED_AT="" DETAILS_JSON='{}'
8
+
9
+ while [ "$#" -gt 0 ]; do
10
+ case "$1" in
11
+ --project-slug) PROJECT_SLUG="$2"; shift 2 ;;
12
+ --release) RELEASE_VERSION="$2"; shift 2 ;;
13
+ --check-key) CHECK_KEY="$2"; shift 2 ;;
14
+ --label) LABEL="$2"; shift 2 ;;
15
+ --provider) PROVIDER="$2"; shift 2 ;;
16
+ --status) STATUS="$2"; shift 2 ;;
17
+ --required) REQUIRED="$2"; shift 2 ;;
18
+ --external-run-id) EXTERNAL_RUN_ID="$2"; shift 2 ;;
19
+ --external-url) EXTERNAL_URL="$2"; shift 2 ;;
20
+ --commit-sha) COMMIT_SHA="$2"; shift 2 ;;
21
+ --branch) BRANCH="$2"; shift 2 ;;
22
+ --started-at) STARTED_AT="$2"; shift 2 ;;
23
+ --completed-at) COMPLETED_AT="$2"; shift 2 ;;
24
+ --details-json) DETAILS_JSON="$2"; shift 2 ;;
25
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
26
+ esac
27
+ done
28
+
29
+ for value in PROJECT_SLUG RELEASE_VERSION CHECK_KEY LABEL STATUS; do
30
+ if [ -z "${!value}" ]; then echo "Error: ${value} is required" >&2; exit 1; fi
31
+ done
32
+ case "$STATUS" in
33
+ queued|running|successful|failed|cancelled|timed_out|skipped|action_required|unknown) ;;
34
+ *) echo "Error: invalid check status '$STATUS'" >&2; exit 1 ;;
35
+ esac
36
+ jq -e 'type == "object"' >/dev/null <<<"$DETAILS_JSON" || {
37
+ echo "Error: --details-json must be a JSON object" >&2; exit 1;
38
+ }
39
+
40
+ BASE="${DEVAUDIT_BASE_URL%/}"
41
+ if [ -z "$BASE" ] || [ -z "${DEVAUDIT_API_KEY:-}" ]; then
42
+ echo "::warning::Release check reporting skipped: DevAudit URL/API key missing" >&2
43
+ exit 0
44
+ fi
45
+ ENCODED_SLUG=$(jq -rn --arg v "$PROJECT_SLUG" '$v|@uri')
46
+ ENCODED_VERSION=$(jq -rn --arg v "$RELEASE_VERSION" '$v|@uri')
47
+ RESOLVE=$(curl -fsS \
48
+ -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
49
+ "${BASE}/api/ci/releases/resolve?projectSlug=${ENCODED_SLUG}&versionPrefix=${ENCODED_VERSION}" || true)
50
+ RELEASE_ID=$(jq -r --arg version "$RELEASE_VERSION" \
51
+ 'if .latest.version == $version then .latest.id // empty else empty end' <<<"$RESOLVE" 2>/dev/null || true)
52
+ if [ -z "$RELEASE_ID" ]; then
53
+ echo "::warning::Release check reporting skipped: exact release ${RELEASE_VERSION} not found" >&2
54
+ exit 0
55
+ fi
56
+
57
+ PAYLOAD=$(jq -n \
58
+ --arg checkKey "$CHECK_KEY" --arg displayLabel "$LABEL" --arg provider "$PROVIDER" \
59
+ --arg status "$STATUS" --argjson required "$REQUIRED" \
60
+ --arg externalRunId "$EXTERNAL_RUN_ID" --arg externalUrl "$EXTERNAL_URL" \
61
+ --arg commitSha "$COMMIT_SHA" --arg branch "$BRANCH" \
62
+ --arg startedAt "$STARTED_AT" --arg completedAt "$COMPLETED_AT" \
63
+ --argjson details "$DETAILS_JSON" \
64
+ '{checkKey:$checkKey,label:$displayLabel,provider:$provider,status:$status,required:$required,
65
+ externalRunId:(if $externalRunId=="" then null else $externalRunId end),
66
+ externalUrl:(if $externalUrl=="" then null else $externalUrl end),
67
+ commitSha:(if $commitSha=="" then null else $commitSha end),
68
+ branch:(if $branch=="" then null else $branch end),
69
+ startedAt:(if $startedAt=="" then null else $startedAt end),
70
+ completedAt:(if $completedAt=="" then null else $completedAt end),details:$details}')
71
+
72
+ RESPONSE_FILE=$(mktemp)
73
+ trap 'rm -f "$RESPONSE_FILE"' EXIT
74
+ HTTP_CODE=$(curl -sS -o "$RESPONSE_FILE" -w '%{http_code}' \
75
+ -X POST "${BASE}/api/ci/releases/${RELEASE_ID}/checks" \
76
+ -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" -H 'Content-Type: application/json' \
77
+ -d "$PAYLOAD")
78
+ if [ "$HTTP_CODE" = "404" ]; then
79
+ echo "::warning::Portal does not support first-class release checks yet" >&2
80
+ exit 0
81
+ fi
82
+ if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
83
+ cat "$RESPONSE_FILE" >&2 || true
84
+ echo "Error: release check API returned HTTP ${HTTP_CODE}" >&2
85
+ exit 1
86
+ fi
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env bash
2
+ # Focused regression tests for authoritative release-check reporting.
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ HELPER="$SCRIPT_DIR/report-release-check.sh"
7
+ PASS=0
8
+ FAIL=0
9
+
10
+ ok() { echo " PASS: $1"; PASS=$((PASS + 1)); }
11
+ no() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
12
+
13
+ make_fixture() {
14
+ local dir="$1"
15
+ mkdir -p "$dir/bin"
16
+ cat > "$dir/bin/curl" <<'EOF'
17
+ #!/usr/bin/env bash
18
+ set -euo pipefail
19
+ URL=""
20
+ OUT=""
21
+ BODY=""
22
+ ARGS=("$@")
23
+ for ((i=0; i<${#ARGS[@]}; i++)); do
24
+ case "${ARGS[$i]}" in
25
+ -o) OUT="${ARGS[$((i+1))]}" ;;
26
+ -d) BODY="${ARGS[$((i+1))]}" ;;
27
+ http://*|https://*) URL="${ARGS[$i]}" ;;
28
+ esac
29
+ done
30
+ printf 'URL:%s\nBODY:%s\n' "$URL" "$BODY" >> "${CHECK_TEST_LOG:?}"
31
+ if [[ "$URL" == *'/api/ci/releases/resolve'* ]]; then
32
+ printf '%s' "${RESOLVE_BODY:-{}}"
33
+ exit 0
34
+ fi
35
+ if [ -n "$OUT" ]; then printf '%s' "${CHECK_BODY:-{}}" > "$OUT"; fi
36
+ printf '%s' "${CHECK_STATUS:-200}"
37
+ EOF
38
+ chmod +x "$dir/bin/curl"
39
+ }
40
+
41
+ run_helper() {
42
+ PATH="$1/bin:$PATH" \
43
+ CHECK_TEST_LOG="$1/curl.log" \
44
+ DEVAUDIT_BASE_URL="https://devaudit.example.test" \
45
+ DEVAUDIT_API_KEY="mc_test_dummy" \
46
+ "$HELPER" "${@:2}"
47
+ }
48
+
49
+ base_args=(
50
+ --project-slug fixture-app
51
+ --release REQ-100
52
+ --check-key quality-gates
53
+ --label "Quality Gates"
54
+ --status failed
55
+ --external-run-id 123
56
+ --commit-sha abc123
57
+ --details-json '{"artifactUpload":"successful"}'
58
+ )
59
+
60
+ echo "=== report-release-check.sh tests ==="
61
+
62
+ dir=$(mktemp -d)
63
+ make_fixture "$dir"
64
+ RESOLVE_BODY='{"latest":{"id":"release-1","version":"REQ-100"}}' \
65
+ CHECK_STATUS=201 run_helper "$dir" "${base_args[@]}"
66
+ if grep -q '/api/ci/releases/release-1/checks' "$dir/curl.log"; then ok "posts to exact release"; else no "missing check endpoint"; fi
67
+ if grep -q 'status.*failed' "$dir/curl.log" && grep -q 'artifactUpload.*successful' "$dir/curl.log"; then
68
+ ok "keeps execution outcome separate from upload detail"
69
+ else
70
+ no "incorrect check payload"
71
+ fi
72
+ rm -rf "$dir"
73
+
74
+ dir=$(mktemp -d)
75
+ make_fixture "$dir"
76
+ RESOLVE_BODY='{"latest":{"id":"release-1","version":"REQ-100"}}' \
77
+ CHECK_STATUS=404 run_helper "$dir" "${base_args[@]}" >/dev/null 2>&1 && ok "tolerates older portal" || no "404 should be tolerant"
78
+ rm -rf "$dir"
79
+
80
+ dir=$(mktemp -d)
81
+ make_fixture "$dir"
82
+ if RESOLVE_BODY='{"latest":{"id":"release-1","version":"REQ-100"}}' \
83
+ CHECK_STATUS=500 CHECK_BODY='{"error":"failed"}' run_helper "$dir" "${base_args[@]}" >/dev/null 2>&1; then
84
+ no "500 should fail"
85
+ else
86
+ ok "fails on portal error"
87
+ fi
88
+ rm -rf "$dir"
89
+
90
+ if "$HELPER" "${base_args[@]/failed/not-a-status}" >/dev/null 2>&1; then
91
+ no "invalid status should fail"
92
+ else
93
+ ok "rejects invalid status"
94
+ fi
95
+
96
+ echo "=== report-release-check.sh: $PASS passed, $FAIL failed ==="
97
+ [ "$FAIL" -eq 0 ]
@@ -32,7 +32,8 @@ jobs:
32
32
  # ──────────────────────────────────────────────
33
33
  quality-gates:
34
34
  name: Quality Gates
35
- if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'chore/close-out-') }}
35
+ needs: [register-release]
36
+ if: ${{ always() && (github.event_name != 'pull_request' || !startsWith(github.head_ref, 'chore/close-out-')) && (github.event_name == 'pull_request' || needs.register-release.result == 'success') }}
36
37
  runs-on: {{RUNNER}}
37
38
 
38
39
  services:
@@ -44,6 +45,8 @@ jobs:
44
45
  env:
45
46
  {{DATABASE_ENV}}
46
47
  {{APP_ENV}}
48
+ DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
49
+ DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
47
50
 
48
51
  steps:
49
52
  - uses: actions/checkout@v6
@@ -54,6 +57,29 @@ jobs:
54
57
  # tag each captured screenshot as feature vs regression.
55
58
  fetch-depth: 0
56
59
 
60
+ - name: Start authoritative quality-gate cycle
61
+ if: github.event_name != 'pull_request' && github.ref_name == '{{INTEGRATION_BRANCH}}' && needs.register-release.outputs.version != 'skip'
62
+ run: |
63
+ BASE=""
64
+ if [ -f sdlc-config.json ]; then
65
+ BASE=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
66
+ fi
67
+ [ -n "$BASE" ] || BASE="$DEVAUDIT_BASE_URL_VAR"
68
+ [ -n "$BASE" ] || { echo "::warning::No DevAudit URL; cycle lifecycle disabled"; exit 0; }
69
+ echo "DEVAUDIT_BASE_URL=${BASE%/}" >> "$GITHUB_ENV"
70
+ chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
71
+ bash scripts/report-test-cycle.sh start \
72
+ --project-slug {{PROJECT_SLUG}} \
73
+ --release "${{ needs.register-release.outputs.version }}" \
74
+ --sdlc-stage 2 --environment ci --cycle-kind quality_gate \
75
+ --provider github_actions --external-run-id "${{ github.run_id }}" \
76
+ --external-run-attempt "${{ github.run_attempt }}" \
77
+ --external-job-id "quality-gates" \
78
+ --idempotency-key "github:${{ github.repository }}:quality-gates.quality_gate:${{ github.run_id }}:${{ github.run_attempt }}:2:${{ needs.register-release.outputs.version }}" \
79
+ --commit-sha "${{ github.sha }}" --branch "${{ github.ref_name }}" \
80
+ --workflow-name "Quality Gates" \
81
+ --workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
82
+
57
83
  # ── Cached installs (skip if already present on self-hosted runner) ──
58
84
 
59
85
  - uses: actions/setup-node@v6
@@ -418,7 +444,7 @@ jobs:
418
444
  # evidence — `status=failed` is itself the audit trail. `!cancelled()`
419
445
  # still guards against partial state on operator-cancel.
420
446
  # DevAudit-Installer#96.
421
- if: ${{ always() && !cancelled() && github.ref_name == '{{INTEGRATION_BRANCH}}' && needs.register-release.result == 'success' }}
447
+ if: ${{ always() && github.ref_name == '{{INTEGRATION_BRANCH}}' && needs.register-release.result == 'success' }}
422
448
  env:
423
449
  DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
424
450
  DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
@@ -465,15 +491,12 @@ jobs:
465
491
  bash scripts/report-test-cycle.sh start \
466
492
  --project-slug {{PROJECT_SLUG}} \
467
493
  --release "${{ needs.register-release.outputs.version }}" \
468
- --sdlc-stage 2 \
469
- --environment uat \
470
- --cycle-kind quality_gate \
471
- --provider github_actions \
472
- --external-run-id "${{ github.run_id }}" \
494
+ --sdlc-stage 2 --environment ci --cycle-kind quality_gate \
495
+ --provider github_actions --external-run-id "${{ github.run_id }}" \
473
496
  --external-run-attempt "${{ github.run_attempt }}" \
497
+ --external-job-id "quality-gates" \
474
498
  --idempotency-key "github:${{ github.repository }}:quality-gates.quality_gate:${{ github.run_id }}:${{ github.run_attempt }}:2:${{ needs.register-release.outputs.version }}" \
475
- --commit-sha "${{ github.sha }}" \
476
- --branch "${{ github.ref_name }}" \
499
+ --commit-sha "${{ github.sha }}" --branch "${{ github.ref_name }}" \
477
500
  --workflow-name "Quality Gates" \
478
501
  --workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
479
502
  --output-file "$CYCLE_OUT"
@@ -882,27 +905,41 @@ jobs:
882
905
  - name: Complete primary quality-gate cycle
883
906
  if: always() && env.DEVAUDIT_BASE_URL != ''
884
907
  run: |
885
- case "${{ job.status }}" in
908
+ case "${{ needs.quality-gates.result }}" in
886
909
  success) OUTCOME=passed ;;
887
910
  cancelled) OUTCOME=cancelled ;;
888
911
  *) OUTCOME=failed ;;
889
912
  esac
890
- chmod +x scripts/report-test-cycle.sh 2>/dev/null || true
913
+ chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
891
914
  bash scripts/report-test-cycle.sh complete \
892
915
  --project-slug {{PROJECT_SLUG}} \
893
916
  --release "${{ needs.register-release.outputs.version }}" \
894
917
  --sdlc-stage 2 \
895
- --environment uat \
918
+ --environment ci \
896
919
  --cycle-kind quality_gate \
897
920
  --provider github_actions \
898
921
  --external-run-id "${{ github.run_id }}" \
899
922
  --external-run-attempt "${{ github.run_attempt }}" \
923
+ --external-job-id "quality-gates" \
900
924
  --idempotency-key "github:${{ github.repository }}:quality-gates.quality_gate:${{ github.run_id }}:${{ github.run_attempt }}:2:${{ needs.register-release.outputs.version }}" \
901
925
  --commit-sha "${{ github.sha }}" \
902
926
  --branch "${{ github.ref_name }}" \
903
927
  --workflow-name "Quality Gates" \
904
928
  --workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
905
929
  --outcome "$OUTCOME"
930
+ case "$OUTCOME" in
931
+ passed) CHECK_STATUS=successful ;;
932
+ *) CHECK_STATUS="$OUTCOME" ;;
933
+ esac
934
+ bash scripts/report-release-check.sh \
935
+ --project-slug {{PROJECT_SLUG}} \
936
+ --release "${{ needs.register-release.outputs.version }}" \
937
+ --check-key "quality-gates:${{ github.run_id }}:${{ github.run_attempt }}" \
938
+ --label "Quality Gates" --provider github_actions --status "$CHECK_STATUS" \
939
+ --external-run-id "${{ github.run_id }}" \
940
+ --external-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
941
+ --commit-sha "${{ github.sha }}" --branch "${{ github.ref_name }}" \
942
+ --details-json '{"executionSource":"needs.quality-gates.result","evidenceUploadJob":"${{ job.status }}"}'
906
943
 
907
944
  - name: Summary
908
945
  run: echo "Evidence uploaded for ${{ needs.register-release.outputs.version }} (UAT)"
@@ -768,6 +768,7 @@ jobs:
768
768
  if: steps.resolve.outputs.skip != 'true' && steps.version.outputs.version != 'skip'
769
769
  run: |
770
770
  set -euo pipefail
771
+ chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
771
772
  DERIVED_RELEASE="${{ steps.version.outputs.version }}"
772
773
 
773
774
  # Tier metadata for the portal (the operator filters/groups by
@@ -873,6 +874,7 @@ jobs:
873
874
  --branch "${PRIOR_BRANCH}" \
874
875
  --workflow-name "E2E Regression" \
875
876
  --workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
877
+ --started-at "${{ github.event.workflow_run.run_started_at }}" \
876
878
  --output-file "$CYCLE_OUT"
877
879
  . "$CYCLE_OUT"
878
880
  rm -f "$CYCLE_OUT"
@@ -927,11 +929,15 @@ jobs:
927
929
  REQ_FAILURES=$((REQ_FAILURES + 1))
928
930
  fi
929
931
  fi
930
- if [ "$REQ_FAILURES" -gt 0 ]; then
931
- REQ_OUTCOME=failed
932
- else
933
- REQ_OUTCOME=passed
934
- fi
932
+ case "${{ github.event.workflow_run.conclusion }}" in
933
+ success) REQ_OUTCOME=passed ;;
934
+ failure) REQ_OUTCOME=failed ;;
935
+ cancelled) REQ_OUTCOME=cancelled ;;
936
+ skipped) REQ_OUTCOME=skipped ;;
937
+ timed_out) REQ_OUTCOME=timed_out ;;
938
+ action_required) REQ_OUTCOME=action_required ;;
939
+ *) REQ_OUTCOME=unknown ;;
940
+ esac
935
941
  bash scripts/report-test-cycle.sh complete \
936
942
  --project-slug {{PROJECT_SLUG}} \
937
943
  --release "${DERIVED_RELEASE}" \
@@ -946,8 +952,28 @@ jobs:
946
952
  --branch "${PRIOR_BRANCH}" \
947
953
  --workflow-name "E2E Regression" \
948
954
  --workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
949
- --started-at "${cycle_started_at:-}" \
955
+ --started-at "${{ github.event.workflow_run.run_started_at }}" \
956
+ --completed-at "${{ github.event.workflow_run.updated_at }}" \
957
+ --outcome-reason "authoritative workflow_run conclusion: ${{ github.event.workflow_run.conclusion }}" \
950
958
  --outcome "${REQ_OUTCOME}"
959
+ case "$REQ_OUTCOME" in
960
+ passed) CHECK_STATUS=successful ;;
961
+ *) CHECK_STATUS="$REQ_OUTCOME" ;;
962
+ esac
963
+ CHECK_DETAILS=$(jq -cn \
964
+ --arg conclusion "${{ github.event.workflow_run.conclusion }}" \
965
+ --argjson artifactUploadFailures "$REQ_FAILURES" \
966
+ '{executionSource:"workflow_run.conclusion",conclusion:$conclusion,artifactUploadFailures:$artifactUploadFailures}')
967
+ bash scripts/report-release-check.sh \
968
+ --project-slug {{PROJECT_SLUG}} --release "${DERIVED_RELEASE}" \
969
+ --check-key "e2e-regression:${{ github.event.workflow_run.id }}:${{ github.event.workflow_run.run_attempt }}:${REQ}" \
970
+ --label "E2E Regression (${REQ})" --provider github_actions --status "$CHECK_STATUS" \
971
+ --external-run-id "${{ github.event.workflow_run.id }}" \
972
+ --external-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
973
+ --commit-sha "${{ github.event.workflow_run.head_sha }}" --branch "${PRIOR_BRANCH}" \
974
+ --started-at "${{ github.event.workflow_run.run_started_at }}" \
975
+ --completed-at "${{ github.event.workflow_run.updated_at }}" \
976
+ --details-json "$CHECK_DETAILS"
951
977
  done
952
978
 
953
979
  # Per-spec failure screenshots from test-results/ are NOT