@metasession.co/devaudit-cli 0.3.17 → 0.3.19
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 +36 -34
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/upload-evidence.sh +23 -19
- package/sdlc/files/_common/governance/incident-report.md.template +6 -1
- package/sdlc/files/_common/governance/nil-incident-report.md.template +5 -2
- package/sdlc/files/_common/scripts/check-host-deployment.sh +58 -8
- package/sdlc/files/_common/scripts/check-host-deployment.test.sh +35 -3
- package/sdlc/files/_common/scripts/check-release-pr-scope.sh +8 -1
- package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +61 -12
- package/sdlc/files/_common/scripts/check-self-hosted-runner.sh +103 -0
- package/sdlc/files/_common/scripts/check-self-hosted-runner.test.sh +74 -0
- package/sdlc/files/_common/scripts/derive-release-version.sh +7 -9
- package/sdlc/files/_common/scripts/derive-release-version.test.sh +18 -18
- package/sdlc/files/_common/scripts/generate-bundled-changes.sh +24 -3
- package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +9 -5
- package/sdlc/files/_common/scripts/reconcile-railway-deployment.sh +37 -0
- package/sdlc/files/_common/scripts/reconcile-railway-deployment.test.sh +30 -0
- package/sdlc/files/_common/scripts/record-uat-execution.sh +195 -0
- package/sdlc/files/_common/scripts/record-uat-execution.test.sh +144 -0
- package/sdlc/files/_common/scripts/render-test-executions.sh +146 -0
- package/sdlc/files/_common/scripts/{render-test-cycles.test.sh → render-test-executions.test.sh} +12 -18
- package/sdlc/files/_common/scripts/{report-test-cycle.sh → report-test-execution.sh} +66 -74
- package/sdlc/files/_common/scripts/report-test-execution.test.sh +177 -0
- package/sdlc/files/_common/scripts/submit-for-uat-review.sh +21 -0
- package/sdlc/files/_common/scripts/upload-evidence.sh +23 -19
- package/sdlc/files/_common/scripts/validate-commits.sh +6 -4
- package/sdlc/files/_common/scripts/validate-commits.test.sh +15 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +9 -7
- package/sdlc/files/_common/skills/e2e-test-engineer/references/e2e-regression-3-tier.yml +62 -10
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +19 -16
- package/sdlc/files/ci/ci.yml.template +86 -46
- package/sdlc/files/ci/compliance-evidence.yml.template +173 -49
- package/sdlc/files/ci/feature-e2e.yml.template +17 -17
- package/sdlc/files/ci/incident-export.yml.template +18 -4
- package/sdlc/files/ci/post-deploy-prod.yml.template +92 -48
- package/sdlc/files/ci/python/ci.yml.template +14 -14
- package/sdlc/files/ci/quality-gates-provenance.yml.template +3 -0
- package/sdlc/files/ci/reconcile-deployment.yml.template +59 -0
- package/sdlc/package.json +1 -1
- package/sdlc/src/blueprints/3-compile-evidence.raw.md +10 -10
- package/sdlc/src/blueprints/4-submit-for-review.raw.md +1 -1
- package/sdlc/src/blueprints/5-deploy-main.raw.md +1 -1
- package/sdlc/src/blueprints/implementing-an-sdlc-issue.raw.md +3 -3
- package/sdlc/files/_common/scripts/render-test-cycles.sh +0 -227
|
@@ -345,28 +345,99 @@ jobs:
|
|
|
345
345
|
# (operator's choice — both layouts are common).
|
|
346
346
|
upload_governance compliance/periodic-review.md periodic_review
|
|
347
347
|
upload_governance compliance/governance/periodic-review.md periodic_review
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
348
|
+
frontmatter_value() {
|
|
349
|
+
local FILE="$1" KEY="$2"
|
|
350
|
+
awk -v key="$KEY" '
|
|
351
|
+
NR == 1 && $0 == "---" { in_fm=1; next }
|
|
352
|
+
in_fm && $0 == "---" { exit }
|
|
353
|
+
in_fm && $0 ~ "^[[:space:]]*" key ":" {
|
|
354
|
+
sub("^[[:space:]]*" key ":[[:space:]]*", "")
|
|
355
|
+
gsub(/^["'\'' ]+|["'\'' ]+$/, "")
|
|
356
|
+
print
|
|
357
|
+
exit
|
|
358
|
+
}
|
|
359
|
+
' "$FILE" 2>/dev/null || true
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
bundle_manifest_allows_source_release() {
|
|
363
|
+
local SOURCE_RELEASE="$1" MANIFEST=""
|
|
364
|
+
[ -n "$SOURCE_RELEASE" ] || return 1
|
|
365
|
+
for MANIFEST in \
|
|
366
|
+
"compliance/pending-releases/BUNDLED-CHANGES-${DERIVED_RELEASE}.json" \
|
|
367
|
+
"compliance/approved-releases/BUNDLED-CHANGES-${DERIVED_RELEASE}.json" \
|
|
368
|
+
"compliance/superseded-releases/BUNDLED-CHANGES-${DERIVED_RELEASE}.json"; do
|
|
369
|
+
[ -f "$MANIFEST" ] || continue
|
|
370
|
+
if jq -e --arg source "$SOURCE_RELEASE" '
|
|
371
|
+
any(.members[]?;
|
|
372
|
+
.version == $source
|
|
373
|
+
and (
|
|
374
|
+
.evidenceInheritancePolicy.mode == "all_eligible"
|
|
375
|
+
or any(.evidenceInheritancePolicy.scopes[]?; . == "release" or . == "stage" or . == "approval")
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
' "$MANIFEST" >/dev/null 2>&1; then
|
|
379
|
+
return 0
|
|
380
|
+
fi
|
|
381
|
+
done
|
|
382
|
+
return 1
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
# Incident reports are selected by frontmatter ownership, not by
|
|
386
|
+
# repository-wide glob attribution. Historical files may stay on disk,
|
|
387
|
+
# but only the incident/no-incident artefact whose source release is
|
|
388
|
+
# the current derived release is uploaded to that release. If the
|
|
389
|
+
# current release has an explicit bundle manifest that grants
|
|
390
|
+
# inheritance for a predecessor, upload the file to the SOURCE release
|
|
391
|
+
# so the portal can inherit by lineage without relabelling ownership.
|
|
367
392
|
upload_incident_report() {
|
|
368
393
|
local FILE="$1"
|
|
369
394
|
if [ ! -f "$FILE" ]; then return 0; fi
|
|
395
|
+
local KIND SOURCE_RELEASE RELEASE_SCOPE SOURCE_ISSUE SEMANTIC_ID CONTENT_HASH TARGET_RELEASE
|
|
396
|
+
KIND="$(frontmatter_value "$FILE" incident_kind)"
|
|
397
|
+
SOURCE_RELEASE="$(frontmatter_value "$FILE" source_release)"
|
|
398
|
+
RELEASE_SCOPE="$(frontmatter_value "$FILE" release_scope)"
|
|
399
|
+
SOURCE_ISSUE="$(frontmatter_value "$FILE" source_issue)"
|
|
400
|
+
SEMANTIC_ID="$(frontmatter_value "$FILE" semantic_id)"
|
|
401
|
+
[ -n "$SEMANTIC_ID" ] || SEMANTIC_ID="$(frontmatter_value "$FILE" incident_id)"
|
|
402
|
+
|
|
403
|
+
case "$KIND" in
|
|
404
|
+
incident|no_incidents_attestation) ;;
|
|
405
|
+
"")
|
|
406
|
+
echo "::error::$(basename "$FILE"): missing required frontmatter incident_kind. Expected incident or no_incidents_attestation."
|
|
407
|
+
return 1 ;;
|
|
408
|
+
*)
|
|
409
|
+
echo "::error::$(basename "$FILE"): invalid incident_kind '${KIND}'. Expected incident or no_incidents_attestation."
|
|
410
|
+
return 1 ;;
|
|
411
|
+
esac
|
|
412
|
+
if [ "$KIND" = "no_incidents_attestation" ] && [ -z "$SOURCE_RELEASE" ]; then
|
|
413
|
+
echo "::error::$(basename "$FILE"): nil incident reports require source_release frontmatter."
|
|
414
|
+
return 1
|
|
415
|
+
fi
|
|
416
|
+
if [ "$KIND" = "incident" ]; then
|
|
417
|
+
if [ -z "$SOURCE_RELEASE" ] && [ -z "$RELEASE_SCOPE" ]; then
|
|
418
|
+
echo "::error::$(basename "$FILE"): incident reports require source_release or release_scope frontmatter."
|
|
419
|
+
return 1
|
|
420
|
+
fi
|
|
421
|
+
if [ -z "$SOURCE_ISSUE" ]; then
|
|
422
|
+
echo "::error::$(basename "$FILE"): incident reports require source_issue frontmatter."
|
|
423
|
+
return 1
|
|
424
|
+
fi
|
|
425
|
+
fi
|
|
426
|
+
if [ -z "$SEMANTIC_ID" ]; then
|
|
427
|
+
echo "::error::$(basename "$FILE"): incident reports require semantic_id or incident_id frontmatter."
|
|
428
|
+
return 1
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
TARGET_RELEASE="${SOURCE_RELEASE:-$RELEASE_SCOPE}"
|
|
432
|
+
if [ "$TARGET_RELEASE" != "$DERIVED_RELEASE" ]; then
|
|
433
|
+
if bundle_manifest_allows_source_release "$TARGET_RELEASE"; then
|
|
434
|
+
echo "Incident $(basename "$FILE") is inherited by ${DERIVED_RELEASE}; preserving source release ${TARGET_RELEASE}."
|
|
435
|
+
else
|
|
436
|
+
echo "::notice::Skipping $(basename "$FILE"): source release ${TARGET_RELEASE} is not ${DERIVED_RELEASE} and no explicit bundle manifest grants inheritance."
|
|
437
|
+
return 0
|
|
438
|
+
fi
|
|
439
|
+
fi
|
|
440
|
+
|
|
370
441
|
# Completeness gate — check for REPLACE markers in structured sections.
|
|
371
442
|
# §4 Root cause, §5 Impact, §9 Sign-off (populated reports).
|
|
372
443
|
# For nil reports, only check the Sign-off section.
|
|
@@ -399,10 +470,17 @@ jobs:
|
|
|
399
470
|
return 0
|
|
400
471
|
fi
|
|
401
472
|
fi
|
|
402
|
-
|
|
473
|
+
CONTENT_HASH="sha256:$(sha256sum "$FILE" | awk '{print $1}')"
|
|
474
|
+
echo "Uploading governance: $(basename "$FILE") (type=incident_report, source_release=${TARGET_RELEASE})"
|
|
403
475
|
bash scripts/upload-evidence.sh \
|
|
404
476
|
{{PROJECT_SLUG}} _compliance-docs incident_report "$FILE" \
|
|
405
|
-
--category planning ${FLAGS} --release "${
|
|
477
|
+
--category planning ${FLAGS} --release "${TARGET_RELEASE}" \
|
|
478
|
+
--evidence-scope release \
|
|
479
|
+
--meta-key "incident_kind=${KIND}" \
|
|
480
|
+
--meta-key "source_release=${TARGET_RELEASE}" \
|
|
481
|
+
--meta-key "source_issue=${SOURCE_ISSUE:-none}" \
|
|
482
|
+
--meta-key "semantic_id=${SEMANTIC_ID}" \
|
|
483
|
+
--meta-key "content_hash=${CONTENT_HASH}" \
|
|
406
484
|
"${DERIVED_META[@]}" \
|
|
407
485
|
|| echo "Warning: Failed to upload $(basename "$FILE")"
|
|
408
486
|
}
|
|
@@ -738,7 +816,7 @@ jobs:
|
|
|
738
816
|
if: steps.resolve.outputs.skip != 'true' && steps.version.outputs.version != 'skip'
|
|
739
817
|
run: |
|
|
740
818
|
set -euo pipefail
|
|
741
|
-
chmod +x scripts/report-test-
|
|
819
|
+
chmod +x scripts/report-test-execution.sh scripts/report-release-check.sh 2>/dev/null || true
|
|
742
820
|
DERIVED_RELEASE="${{ steps.version.outputs.version }}"
|
|
743
821
|
|
|
744
822
|
# Tier metadata for the portal (the operator filters/groups by
|
|
@@ -746,18 +824,24 @@ jobs:
|
|
|
746
824
|
# push to main → regression; otherwise dispatch/schedule.
|
|
747
825
|
PRIOR_EVENT="${{ github.event.workflow_run.event }}"
|
|
748
826
|
PRIOR_BRANCH="${{ github.event.workflow_run.head_branch }}"
|
|
827
|
+
E2E_ENVIRONMENT=uat
|
|
749
828
|
case "$PRIOR_EVENT" in
|
|
750
|
-
pull_request)
|
|
751
|
-
push)
|
|
752
|
-
|
|
829
|
+
pull_request) TIER=critical; STAGE=2 ;;
|
|
830
|
+
push) TIER=regression; STAGE=5 ;;
|
|
831
|
+
deployment_status) TIER=regression; STAGE=5; E2E_ENVIRONMENT=production ;;
|
|
832
|
+
*) TIER="${PRIOR_EVENT}"; STAGE="" ;;
|
|
753
833
|
esac
|
|
754
834
|
|
|
755
835
|
# Common flags for upload-evidence.sh. Branch + SHA come from
|
|
756
836
|
# the triggering run, not from this workflow_run dispatch.
|
|
757
|
-
FLAGS="--create-release-if-missing --environment
|
|
837
|
+
FLAGS="--create-release-if-missing --environment ${E2E_ENVIRONMENT} \
|
|
758
838
|
--git-sha ${{ github.event.workflow_run.head_sha }} \
|
|
759
839
|
--ci-run-id ${{ github.event.workflow_run.id }} \
|
|
760
|
-
--branch ${PRIOR_BRANCH}
|
|
840
|
+
--branch ${PRIOR_BRANCH} \
|
|
841
|
+
--meta-key source_event=${PRIOR_EVENT} \
|
|
842
|
+
--meta-key source_workflow=E2E_Regression \
|
|
843
|
+
--meta-key source_run_id=${{ github.event.workflow_run.id }} \
|
|
844
|
+
--meta-key source_run_attempt=${{ github.event.workflow_run.run_attempt }}"
|
|
761
845
|
[ -n "$STAGE" ] && FLAGS="${FLAGS} --sdlc-stage ${STAGE}"
|
|
762
846
|
|
|
763
847
|
# In-scope REQs from pending release tickets at the triggering
|
|
@@ -820,36 +904,52 @@ jobs:
|
|
|
820
904
|
DERIVED_RELEASE="${REQS[0]}"
|
|
821
905
|
fi
|
|
822
906
|
|
|
907
|
+
if [ "$PRIOR_EVENT" = "deployment_status" ] && [ "${#REQS[@]}" -eq 0 ]; then
|
|
908
|
+
echo "::error::Deployment-origin E2E evidence requires tagged or in-scope REQ attribution; refusing _compliance-docs fallback."
|
|
909
|
+
exit 1
|
|
910
|
+
fi
|
|
911
|
+
|
|
823
912
|
if [ "${#REQS[@]}" -eq 0 ]; then
|
|
824
913
|
REQS=(_compliance-docs)
|
|
825
914
|
fi
|
|
915
|
+
# A bounded Playwright process exits the workflow with `failure`
|
|
916
|
+
# even when its machine-readable execution result is specifically
|
|
917
|
+
# `timed_out`. Preserve that distinction for the portal lifecycle.
|
|
918
|
+
EXECUTION_OUTCOME=""
|
|
919
|
+
if [ -f e2e-artifacts/e2e-regression-metadata.json ]; then
|
|
920
|
+
EXECUTION_OUTCOME="$(jq -r '.outcome // empty' e2e-artifacts/e2e-regression-metadata.json 2>/dev/null || true)"
|
|
921
|
+
case "$EXECUTION_OUTCOME" in
|
|
922
|
+
passed|failed|timed_out) ;;
|
|
923
|
+
*) EXECUTION_OUTCOME="" ;;
|
|
924
|
+
esac
|
|
925
|
+
fi
|
|
826
926
|
echo "Uploading E2E ${TIER}-tier evidence to: ${REQS[*]} (release: ${DERIVED_RELEASE})"
|
|
827
927
|
|
|
828
928
|
UPLOAD_FAILURES=0
|
|
829
929
|
for REQ in "${REQS[@]}"; do
|
|
830
930
|
REQ_FAILURES=0
|
|
831
|
-
LINEAGE_FLAGS=(--test-
|
|
832
|
-
|
|
833
|
-
bash scripts/report-test-
|
|
931
|
+
LINEAGE_FLAGS=(--test-execution "${{ github.event.workflow_run.id }}")
|
|
932
|
+
EXECUTION_OUT="$(mktemp)"
|
|
933
|
+
bash scripts/report-test-execution.sh start \
|
|
834
934
|
--project-slug {{PROJECT_SLUG}} \
|
|
835
935
|
--release "${DERIVED_RELEASE}" \
|
|
836
936
|
--sdlc-stage "${STAGE:-2}" \
|
|
837
|
-
--environment
|
|
838
|
-
--
|
|
937
|
+
--environment "${E2E_ENVIRONMENT}" \
|
|
938
|
+
--suite-kind e2e \
|
|
839
939
|
--provider github_actions \
|
|
840
940
|
--external-run-id "${{ github.event.workflow_run.id }}" \
|
|
841
941
|
--external-run-attempt "${{ github.event.workflow_run.run_attempt }}" \
|
|
842
|
-
--idempotency-key "github:${{ github.repository }}:e2e-regression.${REQ}.e2e:${{ github.event.workflow_run.id }}:${{ github.event.workflow_run.run_attempt }}:${STAGE:-2}:${DERIVED_RELEASE}" \
|
|
942
|
+
--idempotency-key "github:${{ github.repository }}:e2e-regression.${REQ}.e2e:${{ github.event.workflow_run.id }}:${{ github.event.workflow_run.run_attempt }}:${STAGE:-2}:${E2E_ENVIRONMENT}:${DERIVED_RELEASE}" \
|
|
843
943
|
--commit-sha "${{ github.event.workflow_run.head_sha }}" \
|
|
844
944
|
--branch "${PRIOR_BRANCH}" \
|
|
845
945
|
--workflow-name "E2E Regression" \
|
|
846
946
|
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
|
|
847
947
|
--started-at "${{ github.event.workflow_run.run_started_at }}" \
|
|
848
|
-
--output-file "$
|
|
849
|
-
. "$
|
|
850
|
-
rm -f "$
|
|
851
|
-
if [ "${
|
|
852
|
-
LINEAGE_FLAGS+=(--evidence-scope
|
|
948
|
+
--output-file "$EXECUTION_OUT"
|
|
949
|
+
. "$EXECUTION_OUT"
|
|
950
|
+
rm -f "$EXECUTION_OUT"
|
|
951
|
+
if [ "${execution_supported:-false}" = "true" ] && [ -n "${execution_record_id:-}" ]; then
|
|
952
|
+
LINEAGE_FLAGS+=(--evidence-scope execution --test-execution-record-id "${execution_record_id}")
|
|
853
953
|
fi
|
|
854
954
|
# 1. JSON reporter output → e2e_result. The single canonical
|
|
855
955
|
# machine-readable artefact for the run.
|
|
@@ -868,7 +968,24 @@ jobs:
|
|
|
868
968
|
REQ_FAILURES=$((REQ_FAILURES + 1))
|
|
869
969
|
fi
|
|
870
970
|
fi
|
|
871
|
-
# 2.
|
|
971
|
+
# 2. The bounded execution metadata makes a timeout distinct
|
|
972
|
+
# from an ordinary failed workflow on the portal.
|
|
973
|
+
if [ -f e2e-artifacts/e2e-regression-metadata.json ]; then
|
|
974
|
+
if bash scripts/upload-evidence.sh \
|
|
975
|
+
{{PROJECT_SLUG}} "$REQ" test_report \
|
|
976
|
+
e2e-artifacts/e2e-regression-metadata.json \
|
|
977
|
+
--category test_report ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
978
|
+
"${LINEAGE_FLAGS[@]}" \
|
|
979
|
+
--meta-key "tier=${TIER}" --meta-key "execution_outcome=${EXECUTION_OUTCOME:-unknown}"
|
|
980
|
+
then
|
|
981
|
+
:
|
|
982
|
+
else
|
|
983
|
+
echo "::warning::E2E execution metadata upload failed for ${REQ}"
|
|
984
|
+
UPLOAD_FAILURES=$((UPLOAD_FAILURES + 1))
|
|
985
|
+
REQ_FAILURES=$((REQ_FAILURES + 1))
|
|
986
|
+
fi
|
|
987
|
+
fi
|
|
988
|
+
# 3. Playwright HTML report zip → e2e_report. Operator-
|
|
872
989
|
# facing artefact (the formatted run summary). Skipped if
|
|
873
990
|
# the report directory is missing (e.g. an upstream
|
|
874
991
|
# infrastructure failure aborted before reporting).
|
|
@@ -908,23 +1025,29 @@ jobs:
|
|
|
908
1025
|
action_required) REQ_OUTCOME=action_required ;;
|
|
909
1026
|
*) REQ_OUTCOME=unknown ;;
|
|
910
1027
|
esac
|
|
911
|
-
|
|
1028
|
+
if [ "$EXECUTION_OUTCOME" = "timed_out" ]; then
|
|
1029
|
+
REQ_OUTCOME=timed_out
|
|
1030
|
+
OUTCOME_REASON="bounded Playwright execution timed out (metadata); workflow_run conclusion: ${{ github.event.workflow_run.conclusion }}"
|
|
1031
|
+
else
|
|
1032
|
+
OUTCOME_REASON="authoritative workflow_run conclusion: ${{ github.event.workflow_run.conclusion }}"
|
|
1033
|
+
fi
|
|
1034
|
+
bash scripts/report-test-execution.sh complete \
|
|
912
1035
|
--project-slug {{PROJECT_SLUG}} \
|
|
913
1036
|
--release "${DERIVED_RELEASE}" \
|
|
914
1037
|
--sdlc-stage "${STAGE:-2}" \
|
|
915
|
-
--environment
|
|
916
|
-
--
|
|
1038
|
+
--environment "${E2E_ENVIRONMENT}" \
|
|
1039
|
+
--suite-kind e2e \
|
|
917
1040
|
--provider github_actions \
|
|
918
1041
|
--external-run-id "${{ github.event.workflow_run.id }}" \
|
|
919
1042
|
--external-run-attempt "${{ github.event.workflow_run.run_attempt }}" \
|
|
920
|
-
--idempotency-key "github:${{ github.repository }}:e2e-regression.${REQ}.e2e:${{ github.event.workflow_run.id }}:${{ github.event.workflow_run.run_attempt }}:${STAGE:-2}:${DERIVED_RELEASE}" \
|
|
1043
|
+
--idempotency-key "github:${{ github.repository }}:e2e-regression.${REQ}.e2e:${{ github.event.workflow_run.id }}:${{ github.event.workflow_run.run_attempt }}:${STAGE:-2}:${E2E_ENVIRONMENT}:${DERIVED_RELEASE}" \
|
|
921
1044
|
--commit-sha "${{ github.event.workflow_run.head_sha }}" \
|
|
922
1045
|
--branch "${PRIOR_BRANCH}" \
|
|
923
1046
|
--workflow-name "E2E Regression" \
|
|
924
1047
|
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \
|
|
925
1048
|
--started-at "${{ github.event.workflow_run.run_started_at }}" \
|
|
926
1049
|
--completed-at "${{ github.event.workflow_run.updated_at }}" \
|
|
927
|
-
--outcome-reason "
|
|
1050
|
+
--outcome-reason "$OUTCOME_REASON" \
|
|
928
1051
|
--outcome "${REQ_OUTCOME}"
|
|
929
1052
|
case "$REQ_OUTCOME" in
|
|
930
1053
|
passed) CHECK_STATUS=successful ;;
|
|
@@ -932,8 +1055,9 @@ jobs:
|
|
|
932
1055
|
esac
|
|
933
1056
|
CHECK_DETAILS=$(jq -cn \
|
|
934
1057
|
--arg conclusion "${{ github.event.workflow_run.conclusion }}" \
|
|
1058
|
+
--arg executionOutcome "${EXECUTION_OUTCOME:-unknown}" \
|
|
935
1059
|
--argjson artifactUploadFailures "$REQ_FAILURES" \
|
|
936
|
-
'{executionSource:"workflow_run.conclusion",conclusion:$conclusion,artifactUploadFailures:$artifactUploadFailures}')
|
|
1060
|
+
'{executionSource:"workflow_run.conclusion",conclusion:$conclusion,executionOutcome:$executionOutcome,artifactUploadFailures:$artifactUploadFailures}')
|
|
937
1061
|
bash scripts/report-release-check.sh \
|
|
938
1062
|
--project-slug {{PROJECT_SLUG}} --release "${DERIVED_RELEASE}" \
|
|
939
1063
|
--check-key "e2e-regression:${{ github.event.workflow_run.id }}:${{ github.event.workflow_run.run_attempt }}:${REQ}" \
|
|
@@ -977,8 +1101,8 @@ jobs:
|
|
|
977
1101
|
# evidence). Classification rationale is included in every filed
|
|
978
1102
|
# issue body so a human can verify and reclassify.
|
|
979
1103
|
#
|
|
980
|
-
#
|
|
981
|
-
# report traces back to the specific test
|
|
1104
|
+
# testExecutionId (CI run ID) cross-references #209 so the incident
|
|
1105
|
+
# report traces back to the specific test execution that found the defect.
|
|
982
1106
|
|
|
983
1107
|
if [ ! -f e2e-artifacts/e2e-regression-results.json ]; then
|
|
984
1108
|
echo "No e2e-regression-results.json found — cannot triage. Skipping incident filing."
|
|
@@ -1165,7 +1289,7 @@ jobs:
|
|
|
1165
1289
|
**Classification:** ${CLASSIFICATION}
|
|
1166
1290
|
**Classification rationale:** ${RATIONALE}
|
|
1167
1291
|
|
|
1168
|
-
**
|
|
1292
|
+
**testExecutionId:** ${CI_RUN_ID}
|
|
1169
1293
|
**Workflow run:** ${WORKFLOW_URL}
|
|
1170
1294
|
**Git SHA:** ${GIT_SHA}
|
|
1171
1295
|
|
|
@@ -1210,7 +1334,7 @@ jobs:
|
|
|
1210
1334
|
**Classification:** ${CLASSIFICATION}
|
|
1211
1335
|
**Classification rationale:** ${RATIONALE}
|
|
1212
1336
|
|
|
1213
|
-
**
|
|
1337
|
+
**testExecutionId:** ${CI_RUN_ID}
|
|
1214
1338
|
**Workflow run:** ${WORKFLOW_URL}
|
|
1215
1339
|
**Git SHA:** ${GIT_SHA}
|
|
1216
1340
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
# Generated by `devaudit install` / `devaudit update` from sdlc-config.json.
|
|
4
4
|
# Runs the E2E specs tagged with the branch's REQ on PRs to the integration
|
|
5
|
-
# branch, so regression-tier bugs surface during the feature
|
|
5
|
+
# branch, so regression-tier bugs surface during the feature execution instead of
|
|
6
6
|
# at the release gate. Uploads the report as stage-2, origin=feature evidence
|
|
7
7
|
# so it does NOT co-mingle with the stage-3/5 regression runs and is NOT
|
|
8
8
|
# counted as release/UAT evidence by the portal.
|
|
@@ -109,8 +109,8 @@ jobs:
|
|
|
109
109
|
echo "No new e2e specs detected — all captures will tag origin=regression."
|
|
110
110
|
fi
|
|
111
111
|
|
|
112
|
-
- name: Register feature release and start feature E2E
|
|
113
|
-
id:
|
|
112
|
+
- name: Register feature release and start feature E2E execution
|
|
113
|
+
id: exec
|
|
114
114
|
if: always()
|
|
115
115
|
run: |
|
|
116
116
|
REQ_ID="${{ needs.detect-req.outputs.req_id }}"
|
|
@@ -121,24 +121,24 @@ jobs:
|
|
|
121
121
|
fi
|
|
122
122
|
BASE="${CONFIG_URL:-$DEVAUDIT_BASE_URL_VAR}"
|
|
123
123
|
if [ -z "$BASE" ] || [ -z "$DEVAUDIT_API_KEY" ]; then
|
|
124
|
-
echo "::warning::DevAudit not configured — skipping feature E2E
|
|
125
|
-
echo "
|
|
124
|
+
echo "::warning::DevAudit not configured — skipping feature E2E test execution lifecycle."
|
|
125
|
+
echo "execution_supported=false" >> "$GITHUB_OUTPUT"
|
|
126
126
|
exit 0
|
|
127
127
|
fi
|
|
128
128
|
export DEVAUDIT_BASE_URL="${BASE%/}"
|
|
129
|
-
chmod +x scripts/upload-evidence.sh scripts/report-test-
|
|
129
|
+
chmod +x scripts/upload-evidence.sh scripts/report-test-execution.sh 2>/dev/null || true
|
|
130
130
|
bash scripts/upload-evidence.sh \
|
|
131
131
|
{{PROJECT_SLUG}} _compliance-docs release_registration README.md \
|
|
132
132
|
--release "$REQ_ID" --create-release-if-missing \
|
|
133
133
|
--environment uat --category planning \
|
|
134
134
|
--git-sha "${{ github.event.pull_request.head.sha }}" \
|
|
135
135
|
--branch "${{ github.head_ref }}" || true
|
|
136
|
-
bash scripts/report-test-
|
|
136
|
+
bash scripts/report-test-execution.sh start \
|
|
137
137
|
--project-slug {{PROJECT_SLUG}} \
|
|
138
138
|
--release "$REQ_ID" \
|
|
139
139
|
--sdlc-stage 2 \
|
|
140
140
|
--environment uat \
|
|
141
|
-
--
|
|
141
|
+
--suite-kind e2e \
|
|
142
142
|
--provider github_actions \
|
|
143
143
|
--external-run-id "${{ github.run_id }}" \
|
|
144
144
|
--external-run-attempt "${{ github.run_attempt }}" \
|
|
@@ -168,9 +168,9 @@ jobs:
|
|
|
168
168
|
zip -qr playwright-report.zip playwright-report/ 2>/dev/null || true
|
|
169
169
|
fi
|
|
170
170
|
if [ -f playwright-report.zip ]; then
|
|
171
|
-
LINEAGE_FLAGS=(--test-
|
|
172
|
-
if [ "${{ steps.
|
|
173
|
-
LINEAGE_FLAGS+=(--evidence-scope
|
|
171
|
+
LINEAGE_FLAGS=(--test-execution "${{ github.run_id }}")
|
|
172
|
+
if [ "${{ steps.exec.outputs.execution_supported }}" = "true" ] && [ -n "${{ steps.exec.outputs.execution_record_id }}" ]; then
|
|
173
|
+
LINEAGE_FLAGS+=(--evidence-scope execution --test-execution-record-id "${{ steps.exec.outputs.execution_record_id }}")
|
|
174
174
|
fi
|
|
175
175
|
bash scripts/upload-evidence.sh \
|
|
176
176
|
{{PROJECT_SLUG}} "$REQ_ID" feature_e2e_report playwright-report.zip \
|
|
@@ -183,10 +183,10 @@ jobs:
|
|
|
183
183
|
|| echo "::warning::feature E2E report upload failed"
|
|
184
184
|
fi
|
|
185
185
|
|
|
186
|
-
- name: Complete feature E2E
|
|
186
|
+
- name: Complete feature E2E execution
|
|
187
187
|
if: always()
|
|
188
188
|
run: |
|
|
189
|
-
if [ "${{ steps.
|
|
189
|
+
if [ "${{ steps.exec.outputs.execution_supported }}" != "true" ] || [ -z "${{ steps.exec.outputs.execution_record_id }}" ]; then
|
|
190
190
|
exit 0
|
|
191
191
|
fi
|
|
192
192
|
CONFIG_URL=""
|
|
@@ -203,13 +203,13 @@ jobs:
|
|
|
203
203
|
*) OUTCOME=failed ;;
|
|
204
204
|
esac
|
|
205
205
|
export DEVAUDIT_BASE_URL="${BASE%/}"
|
|
206
|
-
chmod +x scripts/report-test-
|
|
207
|
-
bash scripts/report-test-
|
|
206
|
+
chmod +x scripts/report-test-execution.sh 2>/dev/null || true
|
|
207
|
+
bash scripts/report-test-execution.sh complete \
|
|
208
208
|
--project-slug {{PROJECT_SLUG}} \
|
|
209
209
|
--release "${{ needs.detect-req.outputs.req_id }}" \
|
|
210
210
|
--sdlc-stage 2 \
|
|
211
211
|
--environment uat \
|
|
212
|
-
--
|
|
212
|
+
--suite-kind e2e \
|
|
213
213
|
--provider github_actions \
|
|
214
214
|
--external-run-id "${{ github.run_id }}" \
|
|
215
215
|
--external-run-attempt "${{ github.run_attempt }}" \
|
|
@@ -218,5 +218,5 @@ jobs:
|
|
|
218
218
|
--branch "${{ github.head_ref }}" \
|
|
219
219
|
--workflow-name "Feature In-Scope E2E" \
|
|
220
220
|
--workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
221
|
-
--started-at "${{ steps.
|
|
221
|
+
--started-at "${{ steps.exec.outputs.execution_started_at }}" \
|
|
222
222
|
--outcome "$OUTCOME"
|
|
@@ -145,6 +145,11 @@ jobs:
|
|
|
145
145
|
ISSUE_LABELS: ${{ toJSON(github.event.issue.labels.*.name) }}
|
|
146
146
|
run: |
|
|
147
147
|
mkdir -p compliance/governance
|
|
148
|
+
SOURCE_RELEASE=$(printf '%s\n' "$ISSUE_BODY" | grep -oE 'REQ-[0-9]+|v[0-9]{4}\.[0-9]{2}\.[0-9]{2}([.][0-9]+)?' | head -1 || true)
|
|
149
|
+
if [ -z "$SOURCE_RELEASE" ]; then
|
|
150
|
+
echo "::error::Baseline-only incident export requires the issue body to reference its owning REQ-XXX or housekeeping release. Reopen or route through Path B with human frontmatter review."
|
|
151
|
+
exit 1
|
|
152
|
+
fi
|
|
148
153
|
ISSUE_JSON=$(gh issue view "$ISSUE_NUMBER" \
|
|
149
154
|
--json title,body,labels,author,createdAt,closedAt,comments,url,assignees,number,state)
|
|
150
155
|
|
|
@@ -235,6 +240,11 @@ jobs:
|
|
|
235
240
|
"---",
|
|
236
241
|
("title: " + (.title | @json)),
|
|
237
242
|
("incident_id: \"INC-" + (.createdAt[:10] | gsub("-";"")) + "-" + (.number|tostring) + "\""),
|
|
243
|
+
"incident_kind: \"incident\"",
|
|
244
|
+
("source_release: \"'"${SOURCE_RELEASE}"'\""),
|
|
245
|
+
("source_issue: \"" + (.number|tostring) + "\""),
|
|
246
|
+
("source_issue_url: " + (.url | @json)),
|
|
247
|
+
("semantic_id: \"INC-" + (.createdAt[:10] | gsub("-";"")) + "-" + (.number|tostring) + "\""),
|
|
238
248
|
("severity: \"'"${SEVERITY}"'\""),
|
|
239
249
|
("detected_at: " + (.createdAt | @json)),
|
|
240
250
|
("resolved_at: " + (.closedAt | @json)),
|
|
@@ -242,8 +252,6 @@ jobs:
|
|
|
242
252
|
("reported_to_supervisory_authority: \"n/a\""),
|
|
243
253
|
("notification_window_72h: \"n/a\""),
|
|
244
254
|
("last_reviewed_at: " + (.closedAt[:10] | @json)),
|
|
245
|
-
("source_issue: " + (.url | @json)),
|
|
246
|
-
("source_issue_number: " + (.number|tostring)),
|
|
247
255
|
"---",
|
|
248
256
|
"",
|
|
249
257
|
"> ℹ️ Auto-exported by Incident Export workflow on issue close (Path A — baseline-only).",
|
|
@@ -349,8 +357,11 @@ jobs:
|
|
|
349
357
|
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
350
358
|
OUT: ${{ steps.paths.outputs.out }}
|
|
351
359
|
SEVERITY: ${{ steps.route.outputs.severity }}
|
|
360
|
+
ISSUE_BODY: ${{ github.event.issue.body }}
|
|
352
361
|
run: |
|
|
353
362
|
mkdir -p compliance/governance
|
|
363
|
+
SOURCE_RELEASE=$(printf '%s\n' "$ISSUE_BODY" | grep -oE 'REQ-[0-9]+|v[0-9]{4}\.[0-9]{2}\.[0-9]{2}([.][0-9]+)?' | head -1 || true)
|
|
364
|
+
[ -n "$SOURCE_RELEASE" ] || SOURCE_RELEASE="REPLACE — owning REQ-XXX or vYYYY.MM.DD.N"
|
|
354
365
|
ISSUE_JSON=$(gh issue view "$ISSUE_NUMBER" \
|
|
355
366
|
--json title,body,labels,author,createdAt,closedAt,comments,url,assignees,number,state)
|
|
356
367
|
{
|
|
@@ -358,6 +369,11 @@ jobs:
|
|
|
358
369
|
"---",
|
|
359
370
|
("title: " + (.title | @json)),
|
|
360
371
|
("incident_id: \"INC-" + (.createdAt[:10] | gsub("-";"")) + "-" + (.number|tostring) + "\""),
|
|
372
|
+
"incident_kind: \"incident\"",
|
|
373
|
+
("source_release: \"'"${SOURCE_RELEASE}"'\""),
|
|
374
|
+
("source_issue: \"" + (.number|tostring) + "\""),
|
|
375
|
+
("source_issue_url: " + (.url | @json)),
|
|
376
|
+
("semantic_id: \"INC-" + (.createdAt[:10] | gsub("-";"")) + "-" + (.number|tostring) + "\""),
|
|
361
377
|
("severity: \"REPLACE — low | medium | high | critical\""),
|
|
362
378
|
("detected_at: " + (.createdAt | @json)),
|
|
363
379
|
("resolved_at: " + (.closedAt | @json)),
|
|
@@ -365,8 +381,6 @@ jobs:
|
|
|
365
381
|
("reported_to_supervisory_authority: \"REPLACE — true | false | n/a\""),
|
|
366
382
|
("notification_window_72h: \"REPLACE — within | outside | n/a\""),
|
|
367
383
|
("last_reviewed_at: " + (.closedAt[:10] | @json)),
|
|
368
|
-
("source_issue: " + (.url | @json)),
|
|
369
|
-
("source_issue_number: " + (.number|tostring)),
|
|
370
384
|
"---",
|
|
371
385
|
"",
|
|
372
386
|
"> ℹ️ Auto-exported by Incident Export workflow on issue close.",
|