@metasession.co/devaudit-cli 0.3.13 → 0.3.15
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 +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/sdlc/files/_common/scripts/check-release-pr-scope.sh +11 -0
- package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +42 -1
- package/sdlc/files/_common/scripts/generate-bundled-changes.sh +13 -4
- package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +5 -0
- package/sdlc/files/_common/scripts/report-release-check.sh +86 -0
- package/sdlc/files/_common/scripts/report-release-check.test.sh +97 -0
- package/sdlc/files/_common/scripts/standalone-housekeeping-release.sh +98 -0
- package/sdlc/files/_common/scripts/standalone-housekeeping-release.test.sh +45 -0
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +9 -13
- package/sdlc/files/ci/ci.yml.template +69 -12
- package/sdlc/files/ci/compliance-evidence.yml.template +45 -49
- package/sdlc/files/ci/post-deploy-prod.yml.template +181 -24
- package/sdlc/package.json +1 -1
- package/sdlc/src/blueprints/5-deploy-main.raw.md +23 -50
|
@@ -32,7 +32,8 @@ jobs:
|
|
|
32
32
|
# ──────────────────────────────────────────────
|
|
33
33
|
quality-gates:
|
|
34
34
|
name: Quality Gates
|
|
35
|
-
|
|
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
|
|
@@ -310,6 +336,26 @@ jobs:
|
|
|
310
336
|
--environment uat --category planning \
|
|
311
337
|
--git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
|
|
312
338
|
|
|
339
|
+
- name: Upload standalone housekeeping declaration
|
|
340
|
+
if: env.DEVAUDIT_BASE_URL != '' && steps.version.outputs.version != 'skip'
|
|
341
|
+
run: |
|
|
342
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
343
|
+
if ! [[ "$VERSION" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then
|
|
344
|
+
exit 0
|
|
345
|
+
fi
|
|
346
|
+
DECLARATION="compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${VERSION}.json"
|
|
347
|
+
if [ ! -f "$DECLARATION" ]; then
|
|
348
|
+
echo "${VERSION} is integration housekeeping; no standalone declaration uploaded."
|
|
349
|
+
exit 0
|
|
350
|
+
fi
|
|
351
|
+
chmod +x scripts/standalone-housekeeping-release.sh scripts/upload-evidence.sh 2>/dev/null || true
|
|
352
|
+
bash scripts/standalone-housekeeping-release.sh validate "$VERSION" "$DECLARATION"
|
|
353
|
+
bash scripts/upload-evidence.sh \
|
|
354
|
+
{{PROJECT_SLUG}} "$VERSION" release_ticket "$DECLARATION" \
|
|
355
|
+
--release "$VERSION" --create-release-if-missing \
|
|
356
|
+
--environment uat --category release_artifact --sdlc-stage 2 \
|
|
357
|
+
--git-sha ${{ github.sha }} --branch ${{ github.ref_name }}
|
|
358
|
+
|
|
313
359
|
- name: Generate and upload bundled changes (REQ releases only)
|
|
314
360
|
if: env.DEVAUDIT_BASE_URL != '' && steps.version.outputs.version != 'skip'
|
|
315
361
|
run: |
|
|
@@ -418,7 +464,7 @@ jobs:
|
|
|
418
464
|
# evidence — `status=failed` is itself the audit trail. `!cancelled()`
|
|
419
465
|
# still guards against partial state on operator-cancel.
|
|
420
466
|
# DevAudit-Installer#96.
|
|
421
|
-
if: ${{ always() &&
|
|
467
|
+
if: ${{ always() && github.ref_name == '{{INTEGRATION_BRANCH}}' && needs.register-release.result == 'success' }}
|
|
422
468
|
env:
|
|
423
469
|
DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
|
|
424
470
|
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
@@ -465,15 +511,12 @@ jobs:
|
|
|
465
511
|
bash scripts/report-test-cycle.sh start \
|
|
466
512
|
--project-slug {{PROJECT_SLUG}} \
|
|
467
513
|
--release "${{ needs.register-release.outputs.version }}" \
|
|
468
|
-
--sdlc-stage 2 \
|
|
469
|
-
--
|
|
470
|
-
--cycle-kind quality_gate \
|
|
471
|
-
--provider github_actions \
|
|
472
|
-
--external-run-id "${{ github.run_id }}" \
|
|
514
|
+
--sdlc-stage 2 --environment ci --cycle-kind quality_gate \
|
|
515
|
+
--provider github_actions --external-run-id "${{ github.run_id }}" \
|
|
473
516
|
--external-run-attempt "${{ github.run_attempt }}" \
|
|
517
|
+
--external-job-id "quality-gates" \
|
|
474
518
|
--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 }}" \
|
|
519
|
+
--commit-sha "${{ github.sha }}" --branch "${{ github.ref_name }}" \
|
|
477
520
|
--workflow-name "Quality Gates" \
|
|
478
521
|
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
479
522
|
--output-file "$CYCLE_OUT"
|
|
@@ -882,27 +925,41 @@ jobs:
|
|
|
882
925
|
- name: Complete primary quality-gate cycle
|
|
883
926
|
if: always() && env.DEVAUDIT_BASE_URL != ''
|
|
884
927
|
run: |
|
|
885
|
-
case "${{
|
|
928
|
+
case "${{ needs.quality-gates.result }}" in
|
|
886
929
|
success) OUTCOME=passed ;;
|
|
887
930
|
cancelled) OUTCOME=cancelled ;;
|
|
888
931
|
*) OUTCOME=failed ;;
|
|
889
932
|
esac
|
|
890
|
-
chmod +x scripts/report-test-cycle.sh 2>/dev/null || true
|
|
933
|
+
chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
|
|
891
934
|
bash scripts/report-test-cycle.sh complete \
|
|
892
935
|
--project-slug {{PROJECT_SLUG}} \
|
|
893
936
|
--release "${{ needs.register-release.outputs.version }}" \
|
|
894
937
|
--sdlc-stage 2 \
|
|
895
|
-
--environment
|
|
938
|
+
--environment ci \
|
|
896
939
|
--cycle-kind quality_gate \
|
|
897
940
|
--provider github_actions \
|
|
898
941
|
--external-run-id "${{ github.run_id }}" \
|
|
899
942
|
--external-run-attempt "${{ github.run_attempt }}" \
|
|
943
|
+
--external-job-id "quality-gates" \
|
|
900
944
|
--idempotency-key "github:${{ github.repository }}:quality-gates.quality_gate:${{ github.run_id }}:${{ github.run_attempt }}:2:${{ needs.register-release.outputs.version }}" \
|
|
901
945
|
--commit-sha "${{ github.sha }}" \
|
|
902
946
|
--branch "${{ github.ref_name }}" \
|
|
903
947
|
--workflow-name "Quality Gates" \
|
|
904
948
|
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
905
949
|
--outcome "$OUTCOME"
|
|
950
|
+
case "$OUTCOME" in
|
|
951
|
+
passed) CHECK_STATUS=successful ;;
|
|
952
|
+
*) CHECK_STATUS="$OUTCOME" ;;
|
|
953
|
+
esac
|
|
954
|
+
bash scripts/report-release-check.sh \
|
|
955
|
+
--project-slug {{PROJECT_SLUG}} \
|
|
956
|
+
--release "${{ needs.register-release.outputs.version }}" \
|
|
957
|
+
--check-key "quality-gates:${{ github.run_id }}:${{ github.run_attempt }}" \
|
|
958
|
+
--label "Quality Gates" --provider github_actions --status "$CHECK_STATUS" \
|
|
959
|
+
--external-run-id "${{ github.run_id }}" \
|
|
960
|
+
--external-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
961
|
+
--commit-sha "${{ github.sha }}" --branch "${{ github.ref_name }}" \
|
|
962
|
+
--details-json '{"executionSource":"needs.quality-gates.result","evidenceUploadJob":"${{ job.status }}"}'
|
|
906
963
|
|
|
907
964
|
- name: Summary
|
|
908
965
|
run: echo "Evidence uploaded for ${{ needs.register-release.outputs.version }} (UAT)"
|
|
@@ -49,26 +49,11 @@ jobs:
|
|
|
49
49
|
# double-upload every compliance doc.
|
|
50
50
|
if: github.event_name != 'workflow_run'
|
|
51
51
|
runs-on: {{RUNNER}}
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# 2. dispatches `ci.yml` on develop after that stub PR merges, so
|
|
56
|
-
# compliance-only housekeeping releases still get the gate evidence
|
|
57
|
-
# the portal approval path currently requires (#361)
|
|
58
|
-
#
|
|
59
|
-
# Without these the github-actions[bot] token gets HTTP 403 on the push,
|
|
60
|
-
# PR create, or workflow dispatch and the housekeeping path leaves a red
|
|
61
|
-
# badge or an unapprovable release behind.
|
|
62
|
-
#
|
|
63
|
-
# `DEVAUDIT_USER_TOKEN` is no longer consulted by this workflow (the
|
|
64
|
-
# auto-stub step now uses `github.token` directly — see the env block
|
|
65
|
-
# on that step). The permissions below grant everything `gh pr create`
|
|
66
|
-
# needs; preferring a (potentially stale) PAT was the cause of the
|
|
67
|
-
# 2026-06-07 401 chain on this issue.
|
|
52
|
+
# #409: this workflow only uploads evidence. Normal housekeeping is
|
|
53
|
+
# integration history, so it must not create PRs, dispatch gates, or
|
|
54
|
+
# receive write-scoped GitHub credentials.
|
|
68
55
|
permissions:
|
|
69
|
-
|
|
70
|
-
contents: write # push the auto-PR branch
|
|
71
|
-
pull-requests: write # gh pr create
|
|
56
|
+
contents: read
|
|
72
57
|
env:
|
|
73
58
|
DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
|
|
74
59
|
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
@@ -141,28 +126,13 @@ jobs:
|
|
|
141
126
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
142
127
|
echo "Release version: ${VERSION}"
|
|
143
128
|
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
# open a PR that the operator reviews + signs off + merges. Once
|
|
152
|
-
# merged, the next compliance-evidence.yml run uploads them as
|
|
153
|
-
# evidence and the matrix flips both items to ✓.
|
|
154
|
-
#
|
|
155
|
-
# devaudit#284 — release reconciliation merges (from close-out-release.yml)
|
|
156
|
-
# carry a `Release-Closeout: REQ-XXX` marker in the commit body.
|
|
157
|
-
# derive-release-version.sh detects this marker and emits an empty
|
|
158
|
-
# version, which this workflow converts to the explicit `skip` sentinel.
|
|
159
|
-
# Reconciliation pushes therefore never create housekeeping stubs and
|
|
160
|
-
# never attach fresh evidence to an already released tracked record.
|
|
161
|
-
#
|
|
162
|
-
# Mirrors the `incident-export.yml` pattern: auto-PR, operator fills
|
|
163
|
-
# in sign-off, merge to land.
|
|
164
|
-
- name: Auto-generate housekeeping stubs (if needed)
|
|
165
|
-
if: steps.resolve.outputs.skip != 'true' && steps.version.outputs.version != 'skip'
|
|
129
|
+
# #409: normal bare-date changes are integration CI history, not portal
|
|
130
|
+
# releases awaiting approval. The old auto-generated ticket/security
|
|
131
|
+
# summary path is deliberately disabled. An independently promoted
|
|
132
|
+
# exception must carry the reviewed standalone declaration validated by
|
|
133
|
+
# check-release-pr-scope.sh and post-deploy-prod.yml.
|
|
134
|
+
- name: Legacy housekeeping approval path (disabled)
|
|
135
|
+
if: ${{ false }}
|
|
166
136
|
env:
|
|
167
137
|
# Use the workflow's GITHUB_TOKEN directly. The `permissions:`
|
|
168
138
|
# block on the job grants `contents: write` + `pull-requests:
|
|
@@ -241,8 +211,8 @@ jobs:
|
|
|
241
211
|
echo "PR #${EXISTING} already open for ${VERSION} — branch updated in place."
|
|
242
212
|
fi
|
|
243
213
|
|
|
244
|
-
- name:
|
|
245
|
-
if:
|
|
214
|
+
- name: Legacy housekeeping gate dispatch (disabled)
|
|
215
|
+
if: ${{ false }}
|
|
246
216
|
env:
|
|
247
217
|
GH_TOKEN: ${{ github.token }}
|
|
248
218
|
VERSION: ${{ steps.version.outputs.version }}
|
|
@@ -768,6 +738,7 @@ jobs:
|
|
|
768
738
|
if: steps.resolve.outputs.skip != 'true' && steps.version.outputs.version != 'skip'
|
|
769
739
|
run: |
|
|
770
740
|
set -euo pipefail
|
|
741
|
+
chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
|
|
771
742
|
DERIVED_RELEASE="${{ steps.version.outputs.version }}"
|
|
772
743
|
|
|
773
744
|
# Tier metadata for the portal (the operator filters/groups by
|
|
@@ -873,6 +844,7 @@ jobs:
|
|
|
873
844
|
--branch "${PRIOR_BRANCH}" \
|
|
874
845
|
--workflow-name "E2E Regression" \
|
|
875
846
|
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
|
|
847
|
+
--started-at "${{ github.event.workflow_run.run_started_at }}" \
|
|
876
848
|
--output-file "$CYCLE_OUT"
|
|
877
849
|
. "$CYCLE_OUT"
|
|
878
850
|
rm -f "$CYCLE_OUT"
|
|
@@ -927,11 +899,15 @@ jobs:
|
|
|
927
899
|
REQ_FAILURES=$((REQ_FAILURES + 1))
|
|
928
900
|
fi
|
|
929
901
|
fi
|
|
930
|
-
|
|
931
|
-
REQ_OUTCOME=
|
|
932
|
-
|
|
933
|
-
REQ_OUTCOME=
|
|
934
|
-
|
|
902
|
+
case "${{ github.event.workflow_run.conclusion }}" in
|
|
903
|
+
success) REQ_OUTCOME=passed ;;
|
|
904
|
+
failure) REQ_OUTCOME=failed ;;
|
|
905
|
+
cancelled) REQ_OUTCOME=cancelled ;;
|
|
906
|
+
skipped) REQ_OUTCOME=skipped ;;
|
|
907
|
+
timed_out) REQ_OUTCOME=timed_out ;;
|
|
908
|
+
action_required) REQ_OUTCOME=action_required ;;
|
|
909
|
+
*) REQ_OUTCOME=unknown ;;
|
|
910
|
+
esac
|
|
935
911
|
bash scripts/report-test-cycle.sh complete \
|
|
936
912
|
--project-slug {{PROJECT_SLUG}} \
|
|
937
913
|
--release "${DERIVED_RELEASE}" \
|
|
@@ -946,8 +922,28 @@ jobs:
|
|
|
946
922
|
--branch "${PRIOR_BRANCH}" \
|
|
947
923
|
--workflow-name "E2E Regression" \
|
|
948
924
|
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
|
|
949
|
-
--started-at "${
|
|
925
|
+
--started-at "${{ github.event.workflow_run.run_started_at }}" \
|
|
926
|
+
--completed-at "${{ github.event.workflow_run.updated_at }}" \
|
|
927
|
+
--outcome-reason "authoritative workflow_run conclusion: ${{ github.event.workflow_run.conclusion }}" \
|
|
950
928
|
--outcome "${REQ_OUTCOME}"
|
|
929
|
+
case "$REQ_OUTCOME" in
|
|
930
|
+
passed) CHECK_STATUS=successful ;;
|
|
931
|
+
*) CHECK_STATUS="$REQ_OUTCOME" ;;
|
|
932
|
+
esac
|
|
933
|
+
CHECK_DETAILS=$(jq -cn \
|
|
934
|
+
--arg conclusion "${{ github.event.workflow_run.conclusion }}" \
|
|
935
|
+
--argjson artifactUploadFailures "$REQ_FAILURES" \
|
|
936
|
+
'{executionSource:"workflow_run.conclusion",conclusion:$conclusion,artifactUploadFailures:$artifactUploadFailures}')
|
|
937
|
+
bash scripts/report-release-check.sh \
|
|
938
|
+
--project-slug {{PROJECT_SLUG}} --release "${DERIVED_RELEASE}" \
|
|
939
|
+
--check-key "e2e-regression:${{ github.event.workflow_run.id }}:${{ github.event.workflow_run.run_attempt }}:${REQ}" \
|
|
940
|
+
--label "E2E Regression (${REQ})" --provider github_actions --status "$CHECK_STATUS" \
|
|
941
|
+
--external-run-id "${{ github.event.workflow_run.id }}" \
|
|
942
|
+
--external-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
|
|
943
|
+
--commit-sha "${{ github.event.workflow_run.head_sha }}" --branch "${PRIOR_BRANCH}" \
|
|
944
|
+
--started-at "${{ github.event.workflow_run.run_started_at }}" \
|
|
945
|
+
--completed-at "${{ github.event.workflow_run.updated_at }}" \
|
|
946
|
+
--details-json "$CHECK_DETAILS"
|
|
951
947
|
done
|
|
952
948
|
|
|
953
949
|
# Per-spec failure screenshots from test-results/ are NOT
|
|
@@ -97,10 +97,18 @@ jobs:
|
|
|
97
97
|
# release ticket (the same set the dev/UAT pipeline versioned via
|
|
98
98
|
# derive-release-version.sh → REQ-XXX). A bundled develop→main PR
|
|
99
99
|
# carries several; promote ALL of them, not just the first. A manual
|
|
100
|
-
# dispatch can target one via the `release` input.
|
|
101
|
-
#
|
|
100
|
+
# dispatch can target one via the `release` input. A ticketless
|
|
101
|
+
# main promotion is only valid when its explicit standalone
|
|
102
|
+
# housekeeping declaration is present and valid.
|
|
102
103
|
if [ -n "${RELEASE_INPUT}" ]; then
|
|
103
104
|
REQS="${RELEASE_INPUT}"
|
|
105
|
+
if [[ "$REQS" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then
|
|
106
|
+
chmod +x scripts/standalone-housekeeping-release.sh 2>/dev/null || true
|
|
107
|
+
DECLARATION="compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${REQS}.json"
|
|
108
|
+
bash scripts/standalone-housekeeping-release.sh validate "$REQS" "$DECLARATION"
|
|
109
|
+
echo "STANDALONE_DECLARATION=${DECLARATION}" >> "$GITHUB_ENV"
|
|
110
|
+
echo "TERMINAL_STATUS=released" >> "$GITHUB_ENV"
|
|
111
|
+
fi
|
|
104
112
|
else
|
|
105
113
|
REQS=""
|
|
106
114
|
if [ -d compliance/pending-releases ]; then
|
|
@@ -111,25 +119,66 @@ jobs:
|
|
|
111
119
|
fi
|
|
112
120
|
REQS=$(echo ${REQS} | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/[[:space:]]*$//')
|
|
113
121
|
if [ -z "${REQS}" ]; then
|
|
114
|
-
|
|
122
|
+
chmod +x scripts/derive-release-version.sh scripts/standalone-housekeeping-release.sh 2>/dev/null || true
|
|
123
|
+
VERSION=$(bash scripts/derive-release-version.sh)
|
|
124
|
+
DECLARATION="compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${VERSION}.json"
|
|
125
|
+
if [ -f "$DECLARATION" ]; then
|
|
126
|
+
bash scripts/standalone-housekeeping-release.sh validate "$VERSION" "$DECLARATION"
|
|
127
|
+
REQS="$VERSION"
|
|
128
|
+
echo "STANDALONE_DECLARATION=${DECLARATION}" >> "$GITHUB_ENV"
|
|
129
|
+
# Standalone housekeeping is reviewed in its GitHub promotion
|
|
130
|
+
# PR. It does not enter the portal UAT/production queue unless
|
|
131
|
+
# a project deliberately supplies a tracked REQ instead.
|
|
132
|
+
echo "TERMINAL_STATUS=released" >> "$GITHUB_ENV"
|
|
133
|
+
else
|
|
134
|
+
echo "No tracked release ticket or standalone declaration: ordinary housekeeping has no portal promotion."
|
|
135
|
+
fi
|
|
115
136
|
fi
|
|
116
137
|
fi
|
|
117
138
|
echo "In-scope releases to promote: ${REQS}"
|
|
118
139
|
echo "REQS=${REQS}" >> "$GITHUB_ENV"
|
|
119
140
|
|
|
141
|
+
- name: Start production deployment cycles
|
|
142
|
+
run: |
|
|
143
|
+
chmod +x scripts/report-test-cycle.sh 2>/dev/null || true
|
|
144
|
+
for PREFIX in ${REQS}; do
|
|
145
|
+
RESP=$(curl -fsS -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
146
|
+
"${BASE}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${PREFIX}")
|
|
147
|
+
VERSION=$(echo "$RESP" | jq -r '.latest.version // empty')
|
|
148
|
+
[ -n "$VERSION" ] || VERSION="$PREFIX"
|
|
149
|
+
bash scripts/report-test-cycle.sh start \
|
|
150
|
+
--project-slug "$PROJECT_SLUG" --release "$VERSION" \
|
|
151
|
+
--sdlc-stage 5 --environment production --cycle-kind deployment \
|
|
152
|
+
--provider github_actions --external-run-id "$CI_RUN" \
|
|
153
|
+
--external-run-attempt "${{ github.run_attempt }}" --external-job-id "host-deployment" \
|
|
154
|
+
--idempotency-key "github:${{ github.repository }}:post-deploy-prod.deployment:${CI_RUN}:${{ github.run_attempt }}:5:${VERSION}" \
|
|
155
|
+
--commit-sha "$GIT_SHA" --branch main \
|
|
156
|
+
--workflow-name "Post-Deploy Production" \
|
|
157
|
+
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
158
|
+
done
|
|
159
|
+
|
|
120
160
|
- name: Wait for production deployment
|
|
161
|
+
id: wait_deployment
|
|
121
162
|
run: |
|
|
163
|
+
DEPLOY_READY=false
|
|
122
164
|
for i in $(seq 1 30); do
|
|
123
165
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${PROD_URL}/" || echo "000")
|
|
124
166
|
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 400 ]; then
|
|
125
167
|
echo "Production is up (HTTP ${HTTP_CODE})"
|
|
168
|
+
DEPLOY_READY=true
|
|
126
169
|
break
|
|
127
170
|
fi
|
|
128
171
|
echo "Attempt ${i}/30: HTTP ${HTTP_CODE} — waiting 10s..."
|
|
129
172
|
sleep 10
|
|
130
173
|
done
|
|
174
|
+
if [ "$DEPLOY_READY" != "true" ]; then
|
|
175
|
+
echo "::error::Production did not become ready within the deployment window"
|
|
176
|
+
exit 1
|
|
177
|
+
fi
|
|
131
178
|
|
|
132
179
|
- name: Confirm terminal host deployment success
|
|
180
|
+
id: host_deployment
|
|
181
|
+
if: steps.wait_deployment.outcome == 'success'
|
|
133
182
|
env:
|
|
134
183
|
GH_TOKEN: ${{ github.token }}
|
|
135
184
|
run: |
|
|
@@ -140,7 +189,68 @@ jobs:
|
|
|
140
189
|
--max-attempts=30 \
|
|
141
190
|
--poll-seconds=10
|
|
142
191
|
|
|
192
|
+
- name: Complete production deployment cycles
|
|
193
|
+
if: always() && env.BASE != ''
|
|
194
|
+
run: |
|
|
195
|
+
if [ "${{ steps.wait_deployment.outcome }}" = "success" ] && [ "${{ steps.host_deployment.outcome }}" = "success" ]; then
|
|
196
|
+
OUTCOME=passed
|
|
197
|
+
CHECK_STATUS=successful
|
|
198
|
+
elif [ "${{ steps.wait_deployment.outcome }}" = "cancelled" ] || [ "${{ steps.host_deployment.outcome }}" = "cancelled" ]; then
|
|
199
|
+
OUTCOME=cancelled
|
|
200
|
+
CHECK_STATUS=cancelled
|
|
201
|
+
else
|
|
202
|
+
OUTCOME=failed
|
|
203
|
+
CHECK_STATUS=failed
|
|
204
|
+
fi
|
|
205
|
+
chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
|
|
206
|
+
for PREFIX in ${REQS}; do
|
|
207
|
+
RESP=$(curl -fsS -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
208
|
+
"${BASE}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${PREFIX}")
|
|
209
|
+
VERSION=$(echo "$RESP" | jq -r '.latest.version // empty')
|
|
210
|
+
[ -n "$VERSION" ] || VERSION="$PREFIX"
|
|
211
|
+
bash scripts/report-test-cycle.sh complete \
|
|
212
|
+
--project-slug "$PROJECT_SLUG" --release "$VERSION" \
|
|
213
|
+
--sdlc-stage 5 --environment production --cycle-kind deployment \
|
|
214
|
+
--provider github_actions --external-run-id "$CI_RUN" \
|
|
215
|
+
--external-run-attempt "${{ github.run_attempt }}" --external-job-id "host-deployment" \
|
|
216
|
+
--idempotency-key "github:${{ github.repository }}:post-deploy-prod.deployment:${CI_RUN}:${{ github.run_attempt }}:5:${VERSION}" \
|
|
217
|
+
--commit-sha "$GIT_SHA" --branch main \
|
|
218
|
+
--workflow-name "Post-Deploy Production" \
|
|
219
|
+
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
220
|
+
--outcome "$OUTCOME" --outcome-reason "host deployment: ${{ steps.host_deployment.outcome }}"
|
|
221
|
+
bash scripts/report-release-check.sh \
|
|
222
|
+
--project-slug "$PROJECT_SLUG" --release "$VERSION" \
|
|
223
|
+
--check-key "production-deployment:${CI_RUN}:${{ github.run_attempt }}" \
|
|
224
|
+
--label "Production Deployment" --provider github_actions --status "$CHECK_STATUS" \
|
|
225
|
+
--external-run-id "$CI_RUN" \
|
|
226
|
+
--external-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
227
|
+
--commit-sha "$GIT_SHA" --branch main \
|
|
228
|
+
--details-json '{"waitOutcome":"${{ steps.wait_deployment.outcome }}","hostOutcome":"${{ steps.host_deployment.outcome }}"}'
|
|
229
|
+
done
|
|
230
|
+
|
|
231
|
+
- name: Start production smoke cycles
|
|
232
|
+
if: steps.wait_deployment.outcome == 'success' && steps.host_deployment.outcome == 'success'
|
|
233
|
+
run: |
|
|
234
|
+
chmod +x scripts/report-test-cycle.sh 2>/dev/null || true
|
|
235
|
+
for PREFIX in ${REQS}; do
|
|
236
|
+
RESP=$(curl -fsS -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
237
|
+
"${BASE}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${PREFIX}")
|
|
238
|
+
VERSION=$(echo "$RESP" | jq -r '.latest.version // empty')
|
|
239
|
+
[ -n "$VERSION" ] || VERSION="$PREFIX"
|
|
240
|
+
bash scripts/report-test-cycle.sh start \
|
|
241
|
+
--project-slug "$PROJECT_SLUG" --release "$VERSION" \
|
|
242
|
+
--sdlc-stage 5 --environment production --cycle-kind smoke \
|
|
243
|
+
--provider github_actions --external-run-id "$CI_RUN" \
|
|
244
|
+
--external-run-attempt "${{ github.run_attempt }}" --external-job-id "production-smoke" \
|
|
245
|
+
--idempotency-key "github:${{ github.repository }}:post-deploy-prod.smoke:${CI_RUN}:${{ github.run_attempt }}:5:${VERSION}" \
|
|
246
|
+
--commit-sha "$GIT_SHA" --branch main \
|
|
247
|
+
--workflow-name "Post-Deploy Production" \
|
|
248
|
+
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
249
|
+
done
|
|
250
|
+
|
|
143
251
|
- name: Production smoke tests (read-only)
|
|
252
|
+
id: production_smoke
|
|
253
|
+
if: steps.wait_deployment.outcome == 'success' && steps.host_deployment.outcome == 'success'
|
|
144
254
|
run: |
|
|
145
255
|
echo "=== Production Smoke Tests ==="
|
|
146
256
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${PROD_URL}/")
|
|
@@ -163,8 +273,43 @@ jobs:
|
|
|
163
273
|
}
|
|
164
274
|
RESULTS_EOF
|
|
165
275
|
|
|
276
|
+
- name: Complete production smoke cycles
|
|
277
|
+
if: always() && env.BASE != '' && steps.host_deployment.outcome == 'success'
|
|
278
|
+
run: |
|
|
279
|
+
case "${{ steps.production_smoke.outcome }}" in
|
|
280
|
+
success) OUTCOME=passed; CHECK_STATUS=successful ;;
|
|
281
|
+
cancelled) OUTCOME=cancelled; CHECK_STATUS=cancelled ;;
|
|
282
|
+
skipped) OUTCOME=skipped; CHECK_STATUS=skipped ;;
|
|
283
|
+
*) OUTCOME=failed; CHECK_STATUS=failed ;;
|
|
284
|
+
esac
|
|
285
|
+
chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
|
|
286
|
+
for PREFIX in ${REQS}; do
|
|
287
|
+
RESP=$(curl -fsS -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
288
|
+
"${BASE}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${PREFIX}")
|
|
289
|
+
VERSION=$(echo "$RESP" | jq -r '.latest.version // empty')
|
|
290
|
+
[ -n "$VERSION" ] || VERSION="$PREFIX"
|
|
291
|
+
bash scripts/report-test-cycle.sh complete \
|
|
292
|
+
--project-slug "$PROJECT_SLUG" --release "$VERSION" \
|
|
293
|
+
--sdlc-stage 5 --environment production --cycle-kind smoke \
|
|
294
|
+
--provider github_actions --external-run-id "$CI_RUN" \
|
|
295
|
+
--external-run-attempt "${{ github.run_attempt }}" --external-job-id "production-smoke" \
|
|
296
|
+
--idempotency-key "github:${{ github.repository }}:post-deploy-prod.smoke:${CI_RUN}:${{ github.run_attempt }}:5:${VERSION}" \
|
|
297
|
+
--commit-sha "$GIT_SHA" --branch main \
|
|
298
|
+
--workflow-name "Post-Deploy Production" \
|
|
299
|
+
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
300
|
+
--outcome "$OUTCOME" --outcome-reason "production smoke: ${{ steps.production_smoke.outcome }}"
|
|
301
|
+
bash scripts/report-release-check.sh \
|
|
302
|
+
--project-slug "$PROJECT_SLUG" --release "$VERSION" \
|
|
303
|
+
--check-key "production-smoke:${CI_RUN}:${{ github.run_attempt }}" \
|
|
304
|
+
--label "Production Smoke" --provider github_actions --status "$CHECK_STATUS" \
|
|
305
|
+
--external-run-id "$CI_RUN" \
|
|
306
|
+
--external-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
307
|
+
--commit-sha "$GIT_SHA" --branch main \
|
|
308
|
+
--details-json '{"executionSource":"steps.production_smoke.outcome"}'
|
|
309
|
+
done
|
|
310
|
+
|
|
166
311
|
- name: File incident on smoke failure
|
|
167
|
-
if: failure
|
|
312
|
+
if: steps.production_smoke.outcome == 'failure'
|
|
168
313
|
env:
|
|
169
314
|
# GitHub issue/label mutations use the workflow token. DevAudit
|
|
170
315
|
# user tokens are portal PATs, not guaranteed GitHub PATs (#305).
|
|
@@ -241,9 +386,11 @@ jobs:
|
|
|
241
386
|
fi
|
|
242
387
|
|
|
243
388
|
- name: Promote in-scope releases (evidence + status)
|
|
389
|
+
if: steps.production_smoke.outcome == 'success'
|
|
244
390
|
run: |
|
|
245
|
-
chmod +x scripts/upload-evidence.sh scripts/report-test-cycle.sh 2>/dev/null || true
|
|
391
|
+
chmod +x scripts/upload-evidence.sh scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
|
|
246
392
|
PROMOTED=0
|
|
393
|
+
EVIDENCE_FAILURES=0
|
|
247
394
|
for PREFIX in ${REQS}; do
|
|
248
395
|
echo "=== Promoting ${PREFIX} ==="
|
|
249
396
|
RESP=$(curl -s -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
@@ -263,6 +410,7 @@ jobs:
|
|
|
263
410
|
--provider github_actions \
|
|
264
411
|
--external-run-id "${CI_RUN}" \
|
|
265
412
|
--external-run-attempt "${{ github.run_attempt }}" \
|
|
413
|
+
--external-job-id "production-smoke" \
|
|
266
414
|
--idempotency-key "github:${{ github.repository }}:post-deploy-prod.smoke:${CI_RUN}:${{ github.run_attempt }}:5:${VERSION}" \
|
|
267
415
|
--commit-sha "${GIT_SHA}" \
|
|
268
416
|
--branch main \
|
|
@@ -286,6 +434,9 @@ jobs:
|
|
|
286
434
|
# Carry the release ticket into the production environment so the
|
|
287
435
|
# prod-review gate is self-contained.
|
|
288
436
|
TICKET=""
|
|
437
|
+
if [ -n "${STANDALONE_DECLARATION:-}" ] && [ "$VERSION" = "${REQS}" ]; then
|
|
438
|
+
TICKET="$STANDALONE_DECLARATION"
|
|
439
|
+
fi
|
|
289
440
|
for DIR in compliance/pending-releases compliance/approved-releases; do
|
|
290
441
|
if [ -f "${DIR}/RELEASE-TICKET-${VERSION}.md" ]; then
|
|
291
442
|
TICKET="${DIR}/RELEASE-TICKET-${VERSION}.md"; break
|
|
@@ -296,33 +447,35 @@ jobs:
|
|
|
296
447
|
"${PROJECT_SLUG}" "${VERSION}" release_ticket "$TICKET" \
|
|
297
448
|
--release "${VERSION}" --create-release-if-missing --environment production \
|
|
298
449
|
--category release_artifact --sdlc-stage 5 --git-sha "${GIT_SHA}" --ci-run-id "${CI_RUN}" --branch main \
|
|
299
|
-
|| echo "Warning: ticket upload failed for ${VERSION}"
|
|
450
|
+
|| { echo "Warning: ticket upload failed for ${VERSION}"; REQ_FAILURES=$((REQ_FAILURES + 1)); }
|
|
300
451
|
else
|
|
301
452
|
echo "No RELEASE-TICKET-${VERSION}.md found — skipping ticket (date-versioned or archived)."
|
|
302
453
|
fi
|
|
303
454
|
if [ "$REQ_FAILURES" -gt 0 ]; then
|
|
304
|
-
|
|
455
|
+
EVIDENCE_STATUS=failed
|
|
456
|
+
EVIDENCE_FAILURES=$((EVIDENCE_FAILURES + 1))
|
|
305
457
|
else
|
|
306
|
-
|
|
458
|
+
EVIDENCE_STATUS=successful
|
|
459
|
+
fi
|
|
460
|
+
bash scripts/report-release-check.sh \
|
|
461
|
+
--project-slug "$PROJECT_SLUG" --release "$VERSION" \
|
|
462
|
+
--check-key "production-evidence:${CI_RUN}:${{ github.run_attempt }}" \
|
|
463
|
+
--label "Production Evidence Completeness" --provider devaudit_installer \
|
|
464
|
+
--status "$EVIDENCE_STATUS" --external-run-id "$CI_RUN" \
|
|
465
|
+
--external-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
466
|
+
--commit-sha "$GIT_SHA" --branch main \
|
|
467
|
+
--details-json "{\"uploadFailures\":${REQ_FAILURES}}"
|
|
468
|
+
if [ "$REQ_FAILURES" -gt 0 ]; then
|
|
469
|
+
echo "::error::Skipping status promotion for ${VERSION}: production evidence is incomplete"
|
|
470
|
+
continue
|
|
307
471
|
fi
|
|
308
|
-
bash scripts/report-test-cycle.sh complete \
|
|
309
|
-
--project-slug "${PROJECT_SLUG}" \
|
|
310
|
-
--release "${VERSION}" \
|
|
311
|
-
--sdlc-stage 5 \
|
|
312
|
-
--environment production \
|
|
313
|
-
--cycle-kind smoke \
|
|
314
|
-
--provider github_actions \
|
|
315
|
-
--external-run-id "${CI_RUN}" \
|
|
316
|
-
--external-run-attempt "${{ github.run_attempt }}" \
|
|
317
|
-
--idempotency-key "github:${{ github.repository }}:post-deploy-prod.smoke:${CI_RUN}:${{ github.run_attempt }}:5:${VERSION}" \
|
|
318
|
-
--commit-sha "${GIT_SHA}" \
|
|
319
|
-
--branch main \
|
|
320
|
-
--workflow-name "Post-Deploy Production" \
|
|
321
|
-
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
322
|
-
--started-at "${cycle_started_at:-}" \
|
|
323
|
-
--outcome "${REQ_OUTCOME}"
|
|
324
472
|
# Advance status (idempotent — re-PATCHing an already-promoted release is a no-op).
|
|
325
473
|
if [ -n "$RELEASE_ID" ]; then
|
|
474
|
+
if [ -n "${STANDALONE_DECLARATION:-}" ] && [ "$VERSION" = "${REQS}" ]; then
|
|
475
|
+
chmod +x scripts/standalone-housekeeping-release.sh 2>/dev/null || true
|
|
476
|
+
bash scripts/standalone-housekeeping-release.sh promote \
|
|
477
|
+
"$PROJECT_SLUG" "$VERSION" "$STANDALONE_DECLARATION"
|
|
478
|
+
fi
|
|
326
479
|
curl -s -o /dev/null -w " ${VERSION} status patch: HTTP %{http_code}\n" \
|
|
327
480
|
-X PATCH "${BASE}/api/ci/releases/${RELEASE_ID}" \
|
|
328
481
|
-H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
@@ -334,6 +487,10 @@ jobs:
|
|
|
334
487
|
echo "::warning::No release_id resolved for ${PREFIX} — skipping status patch"
|
|
335
488
|
fi
|
|
336
489
|
done
|
|
490
|
+
if [ "$EVIDENCE_FAILURES" -gt 0 ]; then
|
|
491
|
+
echo "::error::${EVIDENCE_FAILURES} release(s) have incomplete production evidence"
|
|
492
|
+
exit 1
|
|
493
|
+
fi
|
|
337
494
|
echo "Promoted ${PROMOTED} release(s) to ${TERMINAL_STATUS}."
|
|
338
495
|
if [ "${TERMINAL_STATUS}" = "prod_review" ]; then
|
|
339
496
|
echo "Next: a human in the portal clicks 'Approve Production' then 'Mark as Released' for each."
|
package/sdlc/package.json
CHANGED