@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.
Files changed (45) hide show
  1. package/dist/index.js +36 -34
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/scripts/upload-evidence.sh +23 -19
  5. package/sdlc/files/_common/governance/incident-report.md.template +6 -1
  6. package/sdlc/files/_common/governance/nil-incident-report.md.template +5 -2
  7. package/sdlc/files/_common/scripts/check-host-deployment.sh +58 -8
  8. package/sdlc/files/_common/scripts/check-host-deployment.test.sh +35 -3
  9. package/sdlc/files/_common/scripts/check-release-pr-scope.sh +8 -1
  10. package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +61 -12
  11. package/sdlc/files/_common/scripts/check-self-hosted-runner.sh +103 -0
  12. package/sdlc/files/_common/scripts/check-self-hosted-runner.test.sh +74 -0
  13. package/sdlc/files/_common/scripts/derive-release-version.sh +7 -9
  14. package/sdlc/files/_common/scripts/derive-release-version.test.sh +18 -18
  15. package/sdlc/files/_common/scripts/generate-bundled-changes.sh +24 -3
  16. package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +9 -5
  17. package/sdlc/files/_common/scripts/reconcile-railway-deployment.sh +37 -0
  18. package/sdlc/files/_common/scripts/reconcile-railway-deployment.test.sh +30 -0
  19. package/sdlc/files/_common/scripts/record-uat-execution.sh +195 -0
  20. package/sdlc/files/_common/scripts/record-uat-execution.test.sh +144 -0
  21. package/sdlc/files/_common/scripts/render-test-executions.sh +146 -0
  22. package/sdlc/files/_common/scripts/{render-test-cycles.test.sh → render-test-executions.test.sh} +12 -18
  23. package/sdlc/files/_common/scripts/{report-test-cycle.sh → report-test-execution.sh} +66 -74
  24. package/sdlc/files/_common/scripts/report-test-execution.test.sh +177 -0
  25. package/sdlc/files/_common/scripts/submit-for-uat-review.sh +21 -0
  26. package/sdlc/files/_common/scripts/upload-evidence.sh +23 -19
  27. package/sdlc/files/_common/scripts/validate-commits.sh +6 -4
  28. package/sdlc/files/_common/scripts/validate-commits.test.sh +15 -0
  29. package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +9 -7
  30. package/sdlc/files/_common/skills/e2e-test-engineer/references/e2e-regression-3-tier.yml +62 -10
  31. package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +19 -16
  32. package/sdlc/files/ci/ci.yml.template +86 -46
  33. package/sdlc/files/ci/compliance-evidence.yml.template +173 -49
  34. package/sdlc/files/ci/feature-e2e.yml.template +17 -17
  35. package/sdlc/files/ci/incident-export.yml.template +18 -4
  36. package/sdlc/files/ci/post-deploy-prod.yml.template +92 -48
  37. package/sdlc/files/ci/python/ci.yml.template +14 -14
  38. package/sdlc/files/ci/quality-gates-provenance.yml.template +3 -0
  39. package/sdlc/files/ci/reconcile-deployment.yml.template +59 -0
  40. package/sdlc/package.json +1 -1
  41. package/sdlc/src/blueprints/3-compile-evidence.raw.md +10 -10
  42. package/sdlc/src/blueprints/4-submit-for-review.raw.md +1 -1
  43. package/sdlc/src/blueprints/5-deploy-main.raw.md +1 -1
  44. package/sdlc/src/blueprints/implementing-an-sdlc-issue.raw.md +3 -3
  45. 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
- # Incident reports: glob `incident-report*.md` so per-incident
349
- # files (e.g. `incident-report-2026-001.md`, written by
350
- # incident-export.yml from labelled GitHub issues) all
351
- # upload as real evidence. The unedited starter
352
- # `incident-report.md` matches the glob too but is skipped
353
- # by upload-evidence.sh's central stub guard so the stub
354
- # can never flip ISO29119.3.5.4 to COVERED on its own
355
- # (devaudit#133). `*.md` does not match `*.md.template`.
356
- #
357
- # DevAudit-Installer#210 §8a: also glob `nil-incident-report*.md`
358
- # — per-release "no incidents" attestations generated by
359
- # sdlc-implementer Phase 3 when no incident-labelled issues
360
- # were closed. Upload as `incident_report` evidence so the
361
- # portal flips ISO29119.3.5.4 to COVERED for clean releases.
362
- #
363
- # DevAudit-Installer#210 §5: completeness gate — skip upload
364
- # if the file contains REPLACE markers in §4 (Root cause),
365
- # §5 (Impact), or §9 (Sign-off). Incomplete incident reports
366
- # must not flip ISO29119.3.5.4 to COVERED on the portal.
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
- echo "Uploading governance: $(basename "$FILE") (type=incident_report)"
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 "${DERIVED_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-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
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) TIER=critical; STAGE=2 ;;
751
- push) TIER=regression; STAGE=5 ;;
752
- *) TIER="${PRIOR_EVENT}"; STAGE="" ;;
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 uat \
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-cycle "${{ github.event.workflow_run.id }}")
832
- CYCLE_OUT="$(mktemp)"
833
- bash scripts/report-test-cycle.sh start \
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 uat \
838
- --cycle-kind e2e \
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 "$CYCLE_OUT"
849
- . "$CYCLE_OUT"
850
- rm -f "$CYCLE_OUT"
851
- if [ "${cycle_supported:-false}" = "true" ] && [ -n "${cycle_record_id:-}" ]; then
852
- LINEAGE_FLAGS+=(--evidence-scope cycle --test-cycle-record-id "${cycle_record_id}")
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. Playwright HTML report zip e2e_report. Operator-
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
- bash scripts/report-test-cycle.sh complete \
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 uat \
916
- --cycle-kind e2e \
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 "authoritative workflow_run conclusion: ${{ github.event.workflow_run.conclusion }}" \
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
- # testCycleId (CI run ID) cross-references #209 so the incident
981
- # report traces back to the specific test cycle that found the defect.
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
- **testCycleId:** ${CI_RUN_ID}
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
- **testCycleId:** ${CI_RUN_ID}
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 cycle instead of
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 cycle
113
- id: cycle
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 cycle lifecycle."
125
- echo "cycle_supported=false" >> "$GITHUB_OUTPUT"
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-cycle.sh 2>/dev/null || true
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-cycle.sh start \
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
- --cycle-kind e2e \
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-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 }}")
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 cycle
186
+ - name: Complete feature E2E execution
187
187
  if: always()
188
188
  run: |
189
- if [ "${{ steps.cycle.outputs.cycle_supported }}" != "true" ] || [ -z "${{ steps.cycle.outputs.cycle_record_id }}" ]; then
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-cycle.sh 2>/dev/null || true
207
- bash scripts/report-test-cycle.sh complete \
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
- --cycle-kind e2e \
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.cycle.outputs.cycle_started_at }}" \
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.",