@metasession.co/devaudit-cli 0.3.12 → 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/dist/index.js +53 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/upload-evidence.sh +55 -1
- package/sdlc/files/_common/0-project-setup.md +10 -4
- package/sdlc/files/_common/Periodic_Security_Review_Schedule.md +5 -5
- package/sdlc/files/_common/README_TEMPLATE.md +8 -8
- package/sdlc/files/_common/Test_Architecture.md +15 -15
- package/sdlc/files/_common/Test_Plan_TEMPLATE.md +13 -13
- package/sdlc/files/_common/Test_Policy.md +2 -2
- package/sdlc/files/_common/Test_Strategy.md +10 -10
- package/sdlc/files/_common/governance/ai-disclosure.md.template +3 -3
- package/sdlc/files/_common/governance/dpia.md.template +3 -3
- package/sdlc/files/_common/governance/incident-report.md.template +3 -3
- package/sdlc/files/_common/governance/nil-incident-report.md.template +1 -1
- package/sdlc/files/_common/governance/periodic-review.md.template +3 -3
- package/sdlc/files/_common/governance/risk-register.md.template +1 -1
- package/sdlc/files/_common/governance/ropa.md.template +2 -2
- package/sdlc/files/_common/joining-an-existing-project.md +4 -4
- package/sdlc/files/_common/scripts/check-host-deployment.sh +107 -0
- package/sdlc/files/_common/scripts/check-host-deployment.test.sh +119 -0
- package/sdlc/files/_common/scripts/check-release-pr-scope.sh +95 -0
- package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +137 -0
- package/sdlc/files/_common/scripts/close-out-release.sh +175 -54
- package/sdlc/files/_common/scripts/close-out-release.test.sh +90 -0
- package/sdlc/files/_common/scripts/extract-release-metadata.sh +27 -0
- package/sdlc/files/_common/scripts/extract-release-metadata.test.sh +13 -1
- package/sdlc/files/_common/scripts/generate-bundled-changes.sh +311 -64
- package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +133 -82
- package/sdlc/files/_common/scripts/generate-housekeeping-release-ticket.sh +18 -9
- package/sdlc/files/_common/scripts/generate-housekeeping-release-ticket.test.sh +66 -0
- package/sdlc/files/_common/scripts/generate-security-summary.sh +14 -5
- package/sdlc/files/_common/scripts/generate-security-summary.test.sh +57 -0
- package/sdlc/files/_common/scripts/prepare-release-pr.sh +44 -4
- package/sdlc/files/_common/scripts/render-test-cycles.sh +227 -0
- package/sdlc/files/_common/scripts/render-test-cycles.test.sh +107 -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/report-test-cycle.sh +344 -0
- package/sdlc/files/_common/scripts/submit-bundle-manifest.sh +100 -0
- package/sdlc/files/_common/scripts/submit-bundle-manifest.test.sh +138 -0
- package/sdlc/files/_common/scripts/upload-evidence.sh +24 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +16 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.test.sh +14 -0
- package/sdlc/files/_common/scripts/validate-test-summary.test.sh +4 -4
- package/sdlc/files/_common/skills/adr-author/SKILL.md +2 -2
- package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +2 -2
- package/sdlc/files/_common/skills/e2e-test-engineer/references/bootstrap.md +5 -5
- package/sdlc/files/_common/skills/governance-doc-author/references/incident-classification.md +1 -1
- package/sdlc/files/_common/skills/requirements-aligner/SKILL.md +4 -4
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +21 -2
- package/sdlc/files/_common/skills/sdlc-implementer/references/change-request-loop.md +1 -1
- package/sdlc/files/_common/skills/sdlc-implementer/references/compliance-constraints.md +7 -14
- package/sdlc/files/ci/ci.yml.template +119 -28
- package/sdlc/files/ci/close-out-release.yml.template +7 -4
- package/sdlc/files/ci/compliance-evidence.yml.template +230 -20
- package/sdlc/files/ci/feature-e2e.yml.template +84 -1
- package/sdlc/files/ci/post-deploy-prod.yml.template +189 -6
- package/sdlc/files/ci/python/ci.yml.template +60 -12
- package/sdlc/files/ci/quality-gates-provenance.yml.template +123 -28
- package/sdlc/package.json +1 -1
- package/sdlc/src/bin/devaudit-sdlc.js +44 -2
- package/sdlc/src/blueprints/1-plan-requirement.raw.md +6 -1
- package/sdlc/src/blueprints/2-implement-and-test.raw.md +1 -1
- package/sdlc/src/blueprints/3-compile-evidence.raw.md +34 -20
- package/sdlc/src/blueprints/4-submit-for-review.raw.md +2 -2
- package/sdlc/src/blueprints/5-deploy-main.raw.md +2 -2
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
# - workflow_dispatch — manual catch-up for an already-released requirement.
|
|
12
12
|
#
|
|
13
13
|
# It merges origin/main into a branch from develop (devaudit#281), runs
|
|
14
|
-
# scripts/close-out-release.sh for the requirement (flip ticket +
|
|
15
|
-
# ticket to approved-releases
|
|
16
|
-
#
|
|
17
|
-
#
|
|
14
|
+
# scripts/close-out-release.sh for the requirement (flip the governing ticket +
|
|
15
|
+
# RTM, move the governing ticket to approved-releases/, and move any absorbed
|
|
16
|
+
# predecessor tickets to superseded-releases/), then opens one PR to develop
|
|
17
|
+
# containing both the main→develop sync and the compliance-tree close-out. The
|
|
18
|
+
# script is idempotent, so a no-op produces no PR.
|
|
18
19
|
|
|
19
20
|
name: Release Close-out
|
|
20
21
|
|
|
@@ -133,6 +134,7 @@ jobs:
|
|
|
133
134
|
- release ticket moved to approved-releases/
|
|
134
135
|
- release ticket status → RELEASED
|
|
135
136
|
- RTM row → RELEASED
|
|
137
|
+
- absorbed predecessor tickets → superseded-releases/ (if the bundle manifest listed any)
|
|
136
138
|
|
|
137
139
|
Ref: ${REQ}"
|
|
138
140
|
git push -u origin "$BRANCH" --force-with-lease
|
|
@@ -144,6 +146,7 @@ jobs:
|
|
|
144
146
|
- **main → develop reconciliation** — merges released code back into develop
|
|
145
147
|
- **release ticket** moved to \`approved-releases/\`, status \`RELEASED\`
|
|
146
148
|
- **RTM row** flipped to \`RELEASED\`
|
|
149
|
+
- **absorbed predecessor tickets** moved to \`superseded-releases/\` when the release bundle lists them
|
|
147
150
|
|
|
148
151
|
Review + merge to develop."
|
|
149
152
|
gh pr create --base develop --head "$BRANCH" \
|
|
@@ -49,13 +49,16 @@ jobs:
|
|
|
49
49
|
# double-upload every compliance doc.
|
|
50
50
|
if: github.event_name != 'workflow_run'
|
|
51
51
|
runs-on: {{RUNNER}}
|
|
52
|
-
# Permissions are needed because the
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
52
|
+
# Permissions are needed because the housekeeping path now does two
|
|
53
|
+
# GitHub mutations:
|
|
54
|
+
# 1. pushes a stub branch + opens a PR via `gh pr create`
|
|
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.
|
|
59
62
|
#
|
|
60
63
|
# `DEVAUDIT_USER_TOKEN` is no longer consulted by this workflow (the
|
|
61
64
|
# auto-stub step now uses `github.token` directly — see the env block
|
|
@@ -63,6 +66,7 @@ jobs:
|
|
|
63
66
|
# needs; preferring a (potentially stale) PAT was the cause of the
|
|
64
67
|
# 2026-06-07 401 chain on this issue.
|
|
65
68
|
permissions:
|
|
69
|
+
actions: write # gh workflow run ci.yml --ref develop
|
|
66
70
|
contents: write # push the auto-PR branch
|
|
67
71
|
pull-requests: write # gh pr create
|
|
68
72
|
env:
|
|
@@ -237,6 +241,43 @@ jobs:
|
|
|
237
241
|
echo "PR #${EXISTING} already open for ${VERSION} — branch updated in place."
|
|
238
242
|
fi
|
|
239
243
|
|
|
244
|
+
- name: Dispatch ci.yml for merged housekeeping stub
|
|
245
|
+
if: github.event_name == 'push' && steps.resolve.outputs.skip != 'true' && steps.version.outputs.version != 'skip'
|
|
246
|
+
env:
|
|
247
|
+
GH_TOKEN: ${{ github.token }}
|
|
248
|
+
VERSION: ${{ steps.version.outputs.version }}
|
|
249
|
+
run: |
|
|
250
|
+
# devaudit-installer#361 — compliance-only housekeeping pushes
|
|
251
|
+
# intentionally skip the heavy quality-gates workflow, but the
|
|
252
|
+
# portal approval path still requires gate evidence before a
|
|
253
|
+
# housekeeping release can be approved. When the current develop
|
|
254
|
+
# push actually carries the housekeeping release-ticket / security-
|
|
255
|
+
# summary stubs, dispatch ci.yml once so the gates run on that
|
|
256
|
+
# exact HEAD and upload the missing evidence automatically.
|
|
257
|
+
if ! [[ "$VERSION" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then
|
|
258
|
+
echo "Version ${VERSION} is not housekeeping; skipping ci.yml dispatch."
|
|
259
|
+
exit 0
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
TICKET_PATH="compliance/pending-releases/RELEASE-TICKET-${VERSION}.md"
|
|
263
|
+
SECSUM_PATH="compliance/security-summary-${VERSION}.md"
|
|
264
|
+
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r "${{ github.sha }}" || true)
|
|
265
|
+
|
|
266
|
+
if ! echo "$CHANGED_FILES" | grep -Fxq "$TICKET_PATH" \
|
|
267
|
+
&& ! echo "$CHANGED_FILES" | grep -Fxq "$SECSUM_PATH"; then
|
|
268
|
+
echo "Current push did not modify housekeeping stubs for ${VERSION}; skipping ci.yml dispatch."
|
|
269
|
+
exit 0
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
if [ ! -f "$TICKET_PATH" ] || [ ! -f "$SECSUM_PATH" ]; then
|
|
273
|
+
echo "Housekeeping stubs for ${VERSION} are not both present on develop; skipping ci.yml dispatch."
|
|
274
|
+
exit 0
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
echo "Dispatching ci.yml on develop so housekeeping release ${VERSION} gets gate evidence."
|
|
278
|
+
gh workflow run ci.yml --ref develop
|
|
279
|
+
echo "Triggered ci.yml workflow_dispatch for housekeeping release ${VERSION}."
|
|
280
|
+
|
|
240
281
|
- name: Upload compliance documents
|
|
241
282
|
if: steps.resolve.outputs.skip != 'true' && steps.version.outputs.version != 'skip'
|
|
242
283
|
run: |
|
|
@@ -250,7 +291,6 @@ jobs:
|
|
|
250
291
|
# release. Note: upload-evidence.sh keeps the LAST --release seen.
|
|
251
292
|
FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
|
|
252
293
|
FLAGS="${FLAGS} --create-release-if-missing --environment uat --sdlc-stage 3"
|
|
253
|
-
FLAGS="${FLAGS} --test-cycle ${{ github.run_id }}"
|
|
254
294
|
DERIVED_RELEASE="${{ steps.version.outputs.version }}"
|
|
255
295
|
|
|
256
296
|
# Derive change_type for the bare-date (housekeeping) DERIVED_RELEASE:
|
|
@@ -463,6 +503,30 @@ jobs:
|
|
|
463
503
|
if [ -n "$CT" ]; then printf -- '--change-type %q ' "$CT"; fi
|
|
464
504
|
}
|
|
465
505
|
|
|
506
|
+
# Upload root-level housekeeping security summaries:
|
|
507
|
+
# compliance/security-summary-vYYYY.MM.DD.md
|
|
508
|
+
# compliance/security-summary-vYYYY.MM.DD.N.md
|
|
509
|
+
#
|
|
510
|
+
# These are generated by the housekeeping stub path and belong to
|
|
511
|
+
# the bare-date housekeeping release encoded in the filename, not
|
|
512
|
+
# to whichever release the current commit happened to derive.
|
|
513
|
+
shopt -s nullglob
|
|
514
|
+
for SECSUM in compliance/security-summary-*.md; do
|
|
515
|
+
[ -f "$SECSUM" ] || continue
|
|
516
|
+
SECSUM_VER=$(basename "$SECSUM" .md | sed 's/^security-summary-//')
|
|
517
|
+
if ! echo "$SECSUM_VER" | grep -qE '^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}([.][0-9]+)?$'; then
|
|
518
|
+
echo "::warning::Skipping $(basename "$SECSUM"): filename does not encode a housekeeping release version."
|
|
519
|
+
continue
|
|
520
|
+
fi
|
|
521
|
+
echo "Uploading housekeeping security summary: $(basename "$SECSUM") -> release ${SECSUM_VER}"
|
|
522
|
+
bash scripts/upload-evidence.sh \
|
|
523
|
+
{{PROJECT_SLUG}} _compliance-docs security_summary "$SECSUM" \
|
|
524
|
+
--category security ${FLAGS} --release "${SECSUM_VER}" \
|
|
525
|
+
"${DERIVED_META[@]}" \
|
|
526
|
+
|| echo "Warning: Failed to upload $(basename "$SECSUM")"
|
|
527
|
+
done
|
|
528
|
+
shopt -u nullglob
|
|
529
|
+
|
|
466
530
|
# Upload release tickets (pending only)
|
|
467
531
|
if [ -d "compliance/pending-releases" ]; then
|
|
468
532
|
for TICKET in compliance/pending-releases/*.md; do
|
|
@@ -704,6 +768,7 @@ jobs:
|
|
|
704
768
|
if: steps.resolve.outputs.skip != 'true' && steps.version.outputs.version != 'skip'
|
|
705
769
|
run: |
|
|
706
770
|
set -euo pipefail
|
|
771
|
+
chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
|
|
707
772
|
DERIVED_RELEASE="${{ steps.version.outputs.version }}"
|
|
708
773
|
|
|
709
774
|
# Tier metadata for the portal (the operator filters/groups by
|
|
@@ -724,7 +789,6 @@ jobs:
|
|
|
724
789
|
--ci-run-id ${{ github.event.workflow_run.id }} \
|
|
725
790
|
--branch ${PRIOR_BRANCH}"
|
|
726
791
|
[ -n "$STAGE" ] && FLAGS="${FLAGS} --sdlc-stage ${STAGE}"
|
|
727
|
-
FLAGS="${FLAGS} --test-cycle ${{ github.event.workflow_run.id }}"
|
|
728
792
|
|
|
729
793
|
# In-scope REQs from pending release tickets at the triggering
|
|
730
794
|
# SHA. If the ticket was created after the triggering E2E run,
|
|
@@ -793,6 +857,30 @@ jobs:
|
|
|
793
857
|
|
|
794
858
|
UPLOAD_FAILURES=0
|
|
795
859
|
for REQ in "${REQS[@]}"; do
|
|
860
|
+
REQ_FAILURES=0
|
|
861
|
+
LINEAGE_FLAGS=(--test-cycle "${{ github.event.workflow_run.id }}")
|
|
862
|
+
CYCLE_OUT="$(mktemp)"
|
|
863
|
+
bash scripts/report-test-cycle.sh start \
|
|
864
|
+
--project-slug {{PROJECT_SLUG}} \
|
|
865
|
+
--release "${DERIVED_RELEASE}" \
|
|
866
|
+
--sdlc-stage "${STAGE:-2}" \
|
|
867
|
+
--environment uat \
|
|
868
|
+
--cycle-kind e2e \
|
|
869
|
+
--provider github_actions \
|
|
870
|
+
--external-run-id "${{ github.event.workflow_run.id }}" \
|
|
871
|
+
--external-run-attempt "${{ github.event.workflow_run.run_attempt }}" \
|
|
872
|
+
--idempotency-key "github:${{ github.repository }}:e2e-regression.${REQ}.e2e:${{ github.event.workflow_run.id }}:${{ github.event.workflow_run.run_attempt }}:${STAGE:-2}:${DERIVED_RELEASE}" \
|
|
873
|
+
--commit-sha "${{ github.event.workflow_run.head_sha }}" \
|
|
874
|
+
--branch "${PRIOR_BRANCH}" \
|
|
875
|
+
--workflow-name "E2E Regression" \
|
|
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 }}" \
|
|
878
|
+
--output-file "$CYCLE_OUT"
|
|
879
|
+
. "$CYCLE_OUT"
|
|
880
|
+
rm -f "$CYCLE_OUT"
|
|
881
|
+
if [ "${cycle_supported:-false}" = "true" ] && [ -n "${cycle_record_id:-}" ]; then
|
|
882
|
+
LINEAGE_FLAGS+=(--evidence-scope cycle --test-cycle-record-id "${cycle_record_id}")
|
|
883
|
+
fi
|
|
796
884
|
# 1. JSON reporter output → e2e_result. The single canonical
|
|
797
885
|
# machine-readable artefact for the run.
|
|
798
886
|
if [ -f e2e-artifacts/e2e-regression-results.json ]; then
|
|
@@ -800,12 +888,14 @@ jobs:
|
|
|
800
888
|
{{PROJECT_SLUG}} "$REQ" e2e_result \
|
|
801
889
|
e2e-artifacts/e2e-regression-results.json \
|
|
802
890
|
--category e2e_result ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
891
|
+
"${LINEAGE_FLAGS[@]}" \
|
|
803
892
|
--meta-key "tier=${TIER}"
|
|
804
893
|
then
|
|
805
894
|
:
|
|
806
895
|
else
|
|
807
896
|
echo "::warning::e2e_result upload failed for ${REQ}"
|
|
808
897
|
UPLOAD_FAILURES=$((UPLOAD_FAILURES + 1))
|
|
898
|
+
REQ_FAILURES=$((REQ_FAILURES + 1))
|
|
809
899
|
fi
|
|
810
900
|
fi
|
|
811
901
|
# 2. Playwright HTML report zip → e2e_report. Operator-
|
|
@@ -830,13 +920,60 @@ jobs:
|
|
|
830
920
|
{{PROJECT_SLUG}} "$REQ" e2e_report \
|
|
831
921
|
e2e-artifacts/playwright-report.zip \
|
|
832
922
|
--category e2e_report ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
923
|
+
"${LINEAGE_FLAGS[@]}" \
|
|
833
924
|
--meta-key "tier=${TIER}"
|
|
834
925
|
then
|
|
835
926
|
:
|
|
836
927
|
else
|
|
837
928
|
echo "::warning::playwright HTML report upload failed for ${REQ} (non-blocking — JSON results are canonical evidence)"
|
|
929
|
+
REQ_FAILURES=$((REQ_FAILURES + 1))
|
|
838
930
|
fi
|
|
839
931
|
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
|
|
941
|
+
bash scripts/report-test-cycle.sh complete \
|
|
942
|
+
--project-slug {{PROJECT_SLUG}} \
|
|
943
|
+
--release "${DERIVED_RELEASE}" \
|
|
944
|
+
--sdlc-stage "${STAGE:-2}" \
|
|
945
|
+
--environment uat \
|
|
946
|
+
--cycle-kind e2e \
|
|
947
|
+
--provider github_actions \
|
|
948
|
+
--external-run-id "${{ github.event.workflow_run.id }}" \
|
|
949
|
+
--external-run-attempt "${{ github.event.workflow_run.run_attempt }}" \
|
|
950
|
+
--idempotency-key "github:${{ github.repository }}:e2e-regression.${REQ}.e2e:${{ github.event.workflow_run.id }}:${{ github.event.workflow_run.run_attempt }}:${STAGE:-2}:${DERIVED_RELEASE}" \
|
|
951
|
+
--commit-sha "${{ github.event.workflow_run.head_sha }}" \
|
|
952
|
+
--branch "${PRIOR_BRANCH}" \
|
|
953
|
+
--workflow-name "E2E Regression" \
|
|
954
|
+
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
|
|
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 }}" \
|
|
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"
|
|
840
977
|
done
|
|
841
978
|
|
|
842
979
|
# Per-spec failure screenshots from test-results/ are NOT
|
|
@@ -894,19 +1031,88 @@ jobs:
|
|
|
894
1031
|
APP_DEFECT_COUNT=0
|
|
895
1032
|
|
|
896
1033
|
# Parse the Playwright JSON reporter output and classify each failing test.
|
|
897
|
-
#
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1034
|
+
# Walk suites/specs/tests/results recursively so nested suite structure,
|
|
1035
|
+
# multi-test specs, and richer error payloads are handled reliably.
|
|
1036
|
+
FAILING_TESTS=$(python3 - <<'PY'
|
|
1037
|
+
import json
|
|
1038
|
+
from pathlib import Path
|
|
1039
|
+
|
|
1040
|
+
path = Path("e2e-artifacts/e2e-regression-results.json")
|
|
1041
|
+
if not path.exists():
|
|
1042
|
+
raise SystemExit(0)
|
|
1043
|
+
|
|
1044
|
+
try:
|
|
1045
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
1046
|
+
except Exception:
|
|
1047
|
+
raise SystemExit(0)
|
|
1048
|
+
|
|
1049
|
+
failing_states = {"failed", "timedOut", "interrupted", "flaky"}
|
|
1050
|
+
rows = []
|
|
1051
|
+
|
|
1052
|
+
def first_error_message(result):
|
|
1053
|
+
error = result.get("error") or {}
|
|
1054
|
+
if isinstance(error, dict):
|
|
1055
|
+
message = str(error.get("message") or "").strip()
|
|
1056
|
+
if message:
|
|
1057
|
+
return message
|
|
1058
|
+
stack = str(error.get("stack") or "").strip()
|
|
1059
|
+
if stack:
|
|
1060
|
+
for line in stack.splitlines():
|
|
1061
|
+
line = line.strip()
|
|
1062
|
+
if line:
|
|
1063
|
+
return line
|
|
1064
|
+
return "no error message"
|
|
1065
|
+
|
|
1066
|
+
def visit_suite(suite, parent_titles):
|
|
1067
|
+
local_title = str(suite.get("title") or "").strip()
|
|
1068
|
+
titles = parent_titles + ([local_title] if local_title else [])
|
|
1069
|
+
for child in suite.get("suites") or []:
|
|
1070
|
+
if isinstance(child, dict):
|
|
1071
|
+
visit_suite(child, titles)
|
|
1072
|
+
for spec in suite.get("specs") or []:
|
|
1073
|
+
if not isinstance(spec, dict):
|
|
1074
|
+
continue
|
|
1075
|
+
spec_file = (
|
|
1076
|
+
str((spec.get("location") or {}).get("file") or "").strip()
|
|
1077
|
+
or str(spec.get("file") or "").strip()
|
|
1078
|
+
or "(unknown spec file)"
|
|
1079
|
+
)
|
|
1080
|
+
spec_title = str(spec.get("title") or "").strip()
|
|
1081
|
+
title_parts = titles + ([spec_title] if spec_title else [])
|
|
1082
|
+
for test in spec.get("tests") or []:
|
|
1083
|
+
if not isinstance(test, dict):
|
|
1084
|
+
continue
|
|
1085
|
+
test_title = str(test.get("title") or "").strip()
|
|
1086
|
+
full_title = " › ".join(part for part in (title_parts + ([test_title] if test_title else [])) if part)
|
|
1087
|
+
results = [r for r in (test.get("results") or []) if isinstance(r, dict)]
|
|
1088
|
+
final_result = next((r for r in reversed(results) if (r.get("status") or "") in failing_states), None)
|
|
1089
|
+
status = str((final_result or {}).get("status") or test.get("status") or spec.get("status") or "").strip()
|
|
1090
|
+
if status not in failing_states:
|
|
1091
|
+
continue
|
|
1092
|
+
rows.append((spec_file, full_title or spec_title or test_title or "(untitled test)", status, first_error_message(final_result or {})))
|
|
1093
|
+
|
|
1094
|
+
for root_suite in payload.get("suites") or []:
|
|
1095
|
+
if isinstance(root_suite, dict):
|
|
1096
|
+
visit_suite(root_suite, [])
|
|
1097
|
+
|
|
1098
|
+
seen = set()
|
|
1099
|
+
for spec_file, title, status, message in rows:
|
|
1100
|
+
key = (spec_file, title, status, message)
|
|
1101
|
+
if key in seen:
|
|
1102
|
+
continue
|
|
1103
|
+
seen.add(key)
|
|
1104
|
+
print("\t".join([spec_file, title.replace("\t", " "), status, message.replace("\t", " ")]))
|
|
1105
|
+
if len(seen) >= 20:
|
|
1106
|
+
break
|
|
1107
|
+
PY
|
|
1108
|
+
)
|
|
903
1109
|
|
|
904
1110
|
if [ -z "$FAILING_TESTS" ]; then
|
|
905
1111
|
echo "No failing tests found in JSON report — nothing to triage."
|
|
906
1112
|
exit 0
|
|
907
1113
|
fi
|
|
908
1114
|
|
|
909
|
-
while IFS=$'\t' read -r TEST_NAME TEST_STATUS ERROR_MSG; do
|
|
1115
|
+
while IFS=$'\t' read -r SPEC_FILE TEST_NAME TEST_STATUS ERROR_MSG; do
|
|
910
1116
|
[ -z "$TEST_NAME" ] && continue
|
|
911
1117
|
|
|
912
1118
|
# --- Classification ---
|
|
@@ -962,10 +1168,10 @@ jobs:
|
|
|
962
1168
|
# --- File issue ---
|
|
963
1169
|
if [ "$FILE_LABEL" = "incident" ]; then
|
|
964
1170
|
# Dedup: check for existing open incident issue for the same test name.
|
|
965
|
-
EXISTING=$(gh issue list --label incident --state open --search "$TEST_NAME" --json number --jq '.[0].number' || true)
|
|
1171
|
+
EXISTING=$(gh issue list --label incident --state open --search "$SPEC_FILE $TEST_NAME" --json number --jq '.[0].number' || true)
|
|
966
1172
|
if [ -n "$EXISTING" ]; then
|
|
967
1173
|
echo " [DEDUP] $TEST_NAME — existing incident #$EXISTING already open. Posting comment."
|
|
968
|
-
gh issue comment "$EXISTING" --body "Additional failure detected in workflow run ${WORKFLOW_URL} (SHA ${GIT_SHA}). Classification: ${CLASSIFICATION}. Rationale: ${RATIONALE}"
|
|
1174
|
+
gh issue comment "$EXISTING" --body "Additional failure detected in workflow run ${WORKFLOW_URL} (SHA ${GIT_SHA}). Spec: ${SPEC_FILE}. Classification: ${CLASSIFICATION}. Rationale: ${RATIONALE}"
|
|
969
1175
|
continue
|
|
970
1176
|
fi
|
|
971
1177
|
|
|
@@ -978,7 +1184,9 @@ jobs:
|
|
|
978
1184
|
ISSUE_BODY=$(cat <<EOF
|
|
979
1185
|
## E2E Regression Failure — ${CLASSIFICATION}
|
|
980
1186
|
|
|
1187
|
+
**Spec file:** ${SPEC_FILE}
|
|
981
1188
|
**Test name:** ${TEST_NAME}
|
|
1189
|
+
**Final status:** ${TEST_STATUS}
|
|
982
1190
|
**Error message:**
|
|
983
1191
|
\`\`\`
|
|
984
1192
|
${ERROR_MSG}
|
|
@@ -1010,7 +1218,7 @@ jobs:
|
|
|
1010
1218
|
ISSUE_BODY=$(echo "$ISSUE_BODY" | sed 's/^ //')
|
|
1011
1219
|
|
|
1012
1220
|
gh issue create \
|
|
1013
|
-
--title "[REGRESSION] ${TEST_NAME}" \
|
|
1221
|
+
--title "[REGRESSION] ${SPEC_FILE} :: ${TEST_NAME}" \
|
|
1014
1222
|
--label "incident,${CLASSIFICATION}" \
|
|
1015
1223
|
--body "$ISSUE_BODY"
|
|
1016
1224
|
|
|
@@ -1021,7 +1229,9 @@ jobs:
|
|
|
1021
1229
|
ISSUE_BODY=$(cat <<EOF
|
|
1022
1230
|
## E2E Regression Failure — ${CLASSIFICATION}
|
|
1023
1231
|
|
|
1232
|
+
**Spec file:** ${SPEC_FILE}
|
|
1024
1233
|
**Test name:** ${TEST_NAME}
|
|
1234
|
+
**Final status:** ${TEST_STATUS}
|
|
1025
1235
|
**Error message:**
|
|
1026
1236
|
\`\`\`
|
|
1027
1237
|
${ERROR_MSG}
|
|
@@ -1040,7 +1250,7 @@ jobs:
|
|
|
1040
1250
|
ISSUE_BODY=$(echo "$ISSUE_BODY" | sed 's/^ //')
|
|
1041
1251
|
|
|
1042
1252
|
gh issue create \
|
|
1043
|
-
--title "[TEST-BUG] ${TEST_NAME}" \
|
|
1253
|
+
--title "[TEST-BUG] ${SPEC_FILE} :: ${TEST_NAME}" \
|
|
1044
1254
|
--label "test-bug" \
|
|
1045
1255
|
--body "$ISSUE_BODY"
|
|
1046
1256
|
fi
|
|
@@ -15,6 +15,7 @@ on:
|
|
|
15
15
|
jobs:
|
|
16
16
|
detect-req:
|
|
17
17
|
name: Detect REQ from branch
|
|
18
|
+
if: ${{ !startsWith(github.head_ref, 'chore/close-out-') }}
|
|
18
19
|
runs-on: {{RUNNER}}
|
|
19
20
|
outputs:
|
|
20
21
|
req_id: ${{ steps.detect.outputs.req_id }}
|
|
@@ -108,6 +109,46 @@ jobs:
|
|
|
108
109
|
echo "No new e2e specs detected — all captures will tag origin=regression."
|
|
109
110
|
fi
|
|
110
111
|
|
|
112
|
+
- name: Register feature release and start feature E2E cycle
|
|
113
|
+
id: cycle
|
|
114
|
+
if: always()
|
|
115
|
+
run: |
|
|
116
|
+
REQ_ID="${{ needs.detect-req.outputs.req_id }}"
|
|
117
|
+
: > "$GITHUB_OUTPUT"
|
|
118
|
+
CONFIG_URL=""
|
|
119
|
+
if [ -f sdlc-config.json ]; then
|
|
120
|
+
CONFIG_URL=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
|
|
121
|
+
fi
|
|
122
|
+
BASE="${CONFIG_URL:-$DEVAUDIT_BASE_URL_VAR}"
|
|
123
|
+
if [ -z "$BASE" ] || [ -z "$DEVAUDIT_API_KEY" ]; then
|
|
124
|
+
echo "::warning::DevAudit not configured — skipping feature E2E cycle lifecycle."
|
|
125
|
+
echo "cycle_supported=false" >> "$GITHUB_OUTPUT"
|
|
126
|
+
exit 0
|
|
127
|
+
fi
|
|
128
|
+
export DEVAUDIT_BASE_URL="${BASE%/}"
|
|
129
|
+
chmod +x scripts/upload-evidence.sh scripts/report-test-cycle.sh 2>/dev/null || true
|
|
130
|
+
bash scripts/upload-evidence.sh \
|
|
131
|
+
{{PROJECT_SLUG}} _compliance-docs release_registration README.md \
|
|
132
|
+
--release "$REQ_ID" --create-release-if-missing \
|
|
133
|
+
--environment uat --category planning \
|
|
134
|
+
--git-sha "${{ github.event.pull_request.head.sha }}" \
|
|
135
|
+
--branch "${{ github.head_ref }}" || true
|
|
136
|
+
bash scripts/report-test-cycle.sh start \
|
|
137
|
+
--project-slug {{PROJECT_SLUG}} \
|
|
138
|
+
--release "$REQ_ID" \
|
|
139
|
+
--sdlc-stage 2 \
|
|
140
|
+
--environment uat \
|
|
141
|
+
--cycle-kind e2e \
|
|
142
|
+
--provider github_actions \
|
|
143
|
+
--external-run-id "${{ github.run_id }}" \
|
|
144
|
+
--external-run-attempt "${{ github.run_attempt }}" \
|
|
145
|
+
--idempotency-key "github:${{ github.repository }}:feature-in-scope-e2e.e2e:${{ github.run_id }}:${{ github.run_attempt }}:2:${REQ_ID}" \
|
|
146
|
+
--commit-sha "${{ github.event.pull_request.head.sha }}" \
|
|
147
|
+
--branch "${{ github.head_ref }}" \
|
|
148
|
+
--workflow-name "Feature In-Scope E2E" \
|
|
149
|
+
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
150
|
+
--output-file "$GITHUB_OUTPUT"
|
|
151
|
+
|
|
111
152
|
{{E2E_FEATURE_TEST_STEP}}
|
|
112
153
|
- name: Upload feature E2E evidence (stage 2, origin=feature)
|
|
113
154
|
if: always()
|
|
@@ -127,13 +168,55 @@ jobs:
|
|
|
127
168
|
zip -qr playwright-report.zip playwright-report/ 2>/dev/null || true
|
|
128
169
|
fi
|
|
129
170
|
if [ -f playwright-report.zip ]; then
|
|
171
|
+
LINEAGE_FLAGS=(--test-cycle "${{ github.run_id }}")
|
|
172
|
+
if [ "${{ steps.cycle.outputs.cycle_supported }}" = "true" ] && [ -n "${{ steps.cycle.outputs.cycle_record_id }}" ]; then
|
|
173
|
+
LINEAGE_FLAGS+=(--evidence-scope cycle --test-cycle-record-id "${{ steps.cycle.outputs.cycle_record_id }}")
|
|
174
|
+
fi
|
|
130
175
|
bash scripts/upload-evidence.sh \
|
|
131
176
|
{{PROJECT_SLUG}} "$REQ_ID" feature_e2e_report playwright-report.zip \
|
|
132
177
|
--category e2e_report --release "$REQ_ID" --create-release-if-missing \
|
|
133
178
|
--environment uat --sdlc-stage 2 \
|
|
134
179
|
--git-sha "${{ github.event.pull_request.head.sha }}" \
|
|
135
180
|
--ci-run-id "${{ github.run_id }}" --branch "${{ github.head_ref }}" \
|
|
136
|
-
|
|
181
|
+
"${LINEAGE_FLAGS[@]}" \
|
|
137
182
|
--meta-key "origin=feature" \
|
|
138
183
|
|| echo "::warning::feature E2E report upload failed"
|
|
139
184
|
fi
|
|
185
|
+
|
|
186
|
+
- name: Complete feature E2E cycle
|
|
187
|
+
if: always()
|
|
188
|
+
run: |
|
|
189
|
+
if [ "${{ steps.cycle.outputs.cycle_supported }}" != "true" ] || [ -z "${{ steps.cycle.outputs.cycle_record_id }}" ]; then
|
|
190
|
+
exit 0
|
|
191
|
+
fi
|
|
192
|
+
CONFIG_URL=""
|
|
193
|
+
if [ -f sdlc-config.json ]; then
|
|
194
|
+
CONFIG_URL=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
|
|
195
|
+
fi
|
|
196
|
+
BASE="${CONFIG_URL:-$DEVAUDIT_BASE_URL_VAR}"
|
|
197
|
+
if [ -z "$BASE" ] || [ -z "$DEVAUDIT_API_KEY" ]; then
|
|
198
|
+
exit 0
|
|
199
|
+
fi
|
|
200
|
+
case "${{ job.status }}" in
|
|
201
|
+
success) OUTCOME=passed ;;
|
|
202
|
+
cancelled) OUTCOME=cancelled ;;
|
|
203
|
+
*) OUTCOME=failed ;;
|
|
204
|
+
esac
|
|
205
|
+
export DEVAUDIT_BASE_URL="${BASE%/}"
|
|
206
|
+
chmod +x scripts/report-test-cycle.sh 2>/dev/null || true
|
|
207
|
+
bash scripts/report-test-cycle.sh complete \
|
|
208
|
+
--project-slug {{PROJECT_SLUG}} \
|
|
209
|
+
--release "${{ needs.detect-req.outputs.req_id }}" \
|
|
210
|
+
--sdlc-stage 2 \
|
|
211
|
+
--environment uat \
|
|
212
|
+
--cycle-kind e2e \
|
|
213
|
+
--provider github_actions \
|
|
214
|
+
--external-run-id "${{ github.run_id }}" \
|
|
215
|
+
--external-run-attempt "${{ github.run_attempt }}" \
|
|
216
|
+
--idempotency-key "github:${{ github.repository }}:feature-in-scope-e2e.e2e:${{ github.run_id }}:${{ github.run_attempt }}:2:${{ needs.detect-req.outputs.req_id }}" \
|
|
217
|
+
--commit-sha "${{ github.event.pull_request.head.sha }}" \
|
|
218
|
+
--branch "${{ github.head_ref }}" \
|
|
219
|
+
--workflow-name "Feature In-Scope E2E" \
|
|
220
|
+
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
221
|
+
--started-at "${{ steps.cycle.outputs.cycle_started_at }}" \
|
|
222
|
+
--outcome "$OUTCOME"
|