@metasession.co/devaudit-cli 0.1.69 → 0.1.71
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 +12 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/upload-evidence.sh +10 -1
- package/sdlc/CLAUDE.md +25 -2
- package/sdlc/SKILLS.md +1 -1
- package/sdlc/ai-rules/INSTRUCTIONS-SDLC.md +21 -5
- package/sdlc/files/_common/3-compile-evidence.md +63 -2
- package/sdlc/files/_common/4-submit-for-review.md +2 -1
- package/sdlc/files/_common/5-deploy-main.md +1 -0
- package/sdlc/files/_common/Test_Plan_TEMPLATE.md +1 -1
- package/sdlc/files/_common/Test_Strategy.md +1 -1
- package/sdlc/files/_common/governance/nil-incident-report.md.template +38 -0
- package/sdlc/files/_common/scripts/derive-release-version.sh +7 -0
- package/sdlc/files/_common/scripts/generate-bundled-changes.sh +88 -0
- package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +183 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +13 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +12 -2
- package/sdlc/files/_common/skills/requirements-aligner/SKILL.md +6 -1
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +138 -25
- package/sdlc/files/_common/skills/sdlc-implementer/references/call-graph.md +1 -1
- package/sdlc/files/_common/skills/sdlc-implementer/references/change-request-loop.md +27 -2
- package/sdlc/files/ci/ci.yml.template +112 -13
- package/sdlc/files/ci/compliance-evidence.yml.template +353 -53
- package/sdlc/files/ci/feature-e2e.yml.template +3 -2
- package/sdlc/files/ci/incident-export.yml.template +306 -8
- package/sdlc/files/ci/label-retention.yml.template +144 -0
- package/sdlc/files/ci/post-deploy-prod.yml.template +80 -3
- package/sdlc/files/ci/python/ci.yml.template +60 -16
- package/sdlc/files/sdlc-config.example.json +1 -1
- package/sdlc/.claude/settings.local.json +0 -11
|
@@ -238,6 +238,7 @@ jobs:
|
|
|
238
238
|
# release. Note: upload-evidence.sh keeps the LAST --release seen.
|
|
239
239
|
FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
|
|
240
240
|
FLAGS="${FLAGS} --create-release-if-missing --environment uat --sdlc-stage 3"
|
|
241
|
+
FLAGS="${FLAGS} --test-cycle ${{ github.run_id }}"
|
|
241
242
|
DERIVED_RELEASE="${{ steps.version.outputs.version }}"
|
|
242
243
|
|
|
243
244
|
# Derive change_type for the bare-date (housekeeping) DERIVED_RELEASE:
|
|
@@ -249,18 +250,28 @@ jobs:
|
|
|
249
250
|
DERIVED_META=()
|
|
250
251
|
[ -n "$DERIVED_CT" ] && DERIVED_META+=(--change-type "$DERIVED_CT")
|
|
251
252
|
|
|
252
|
-
# Upload planning docs
|
|
253
|
-
#
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
253
|
+
# Upload project-level planning docs with specific evidence_type
|
|
254
|
+
# so the portal's framework-coverage predicates attribute them
|
|
255
|
+
# correctly (DevAudit-Installer#205). Previously all three uploaded
|
|
256
|
+
# as compliance_document — the catch-all that satisfies no specific
|
|
257
|
+
# clause predicate.
|
|
258
|
+
#
|
|
259
|
+
# - RTM.md → rtm (ISO 29119 §3.3 traceability)
|
|
260
|
+
# - test-plan.md → test_plan (ISO 29119 §3.4 test plan)
|
|
261
|
+
# - test-cases.md → test_cases (ISO 29119 §3.4 test cases)
|
|
262
|
+
upload_project_doc() {
|
|
263
|
+
local DOC="$1" EVTYPE="$2"
|
|
264
|
+
if [ ! -f "$DOC" ]; then return 0; fi
|
|
265
|
+
echo "Uploading: $(basename "$DOC") (${EVTYPE})"
|
|
266
|
+
bash scripts/upload-evidence.sh \
|
|
267
|
+
{{PROJECT_SLUG}} _compliance-docs "$EVTYPE" "$DOC" \
|
|
268
|
+
--category planning ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
269
|
+
"${DERIVED_META[@]}" \
|
|
270
|
+
|| echo "Warning: Failed to upload $(basename "$DOC")"
|
|
271
|
+
}
|
|
272
|
+
upload_project_doc compliance/RTM.md rtm
|
|
273
|
+
upload_project_doc compliance/test-plan.md test_plan
|
|
274
|
+
upload_project_doc compliance/test-cases.md test_cases
|
|
264
275
|
|
|
265
276
|
# Project-level Test Summary Report — a hand-authored baseline
|
|
266
277
|
# describing the project's testing posture. As of v0.1.32 this is
|
|
@@ -280,6 +291,13 @@ jobs:
|
|
|
280
291
|
|| echo "Warning: Failed to upload test-summary-report.md"
|
|
281
292
|
fi
|
|
282
293
|
|
|
294
|
+
# NOTE: test-summary-report.md stays as compliance_document because
|
|
295
|
+
# it is a project-level evergreen baseline, NOT per-release test
|
|
296
|
+
# evidence. It does not satisfy any specific clause predicate —
|
|
297
|
+
# the per-REQ test-execution-summary.md (test_report) does that.
|
|
298
|
+
# This is the one intentional compliance_document use for project-
|
|
299
|
+
# level docs (DevAudit-Installer#205).
|
|
300
|
+
|
|
283
301
|
# Tier 3 per-event governance docs (devaudit#370 Phase 3a, narrowed
|
|
284
302
|
# in v0.1.39). Only periodic-review and incident-report are CI-uploaded
|
|
285
303
|
# — both are auto-generated by other workflows (periodic-review by the
|
|
@@ -313,9 +331,68 @@ jobs:
|
|
|
313
331
|
# by upload-evidence.sh's central stub guard — so the stub
|
|
314
332
|
# can never flip ISO29119.3.5.4 to COVERED on its own
|
|
315
333
|
# (devaudit#133). `*.md` does not match `*.md.template`.
|
|
334
|
+
#
|
|
335
|
+
# DevAudit-Installer#210 §8a: also glob `nil-incident-report*.md`
|
|
336
|
+
# — per-release "no incidents" attestations generated by
|
|
337
|
+
# sdlc-implementer Phase 3 when no incident-labelled issues
|
|
338
|
+
# were closed. Upload as `incident_report` evidence so the
|
|
339
|
+
# portal flips ISO29119.3.5.4 to COVERED for clean releases.
|
|
340
|
+
#
|
|
341
|
+
# DevAudit-Installer#210 §5: completeness gate — skip upload
|
|
342
|
+
# if the file contains REPLACE markers in §4 (Root cause),
|
|
343
|
+
# §5 (Impact), or §9 (Sign-off). Incomplete incident reports
|
|
344
|
+
# must not flip ISO29119.3.5.4 to COVERED on the portal.
|
|
345
|
+
upload_incident_report() {
|
|
346
|
+
local FILE="$1"
|
|
347
|
+
if [ ! -f "$FILE" ]; then return 0; fi
|
|
348
|
+
# Completeness gate — check for REPLACE markers in structured sections.
|
|
349
|
+
# §4 Root cause, §5 Impact, §9 Sign-off (populated reports).
|
|
350
|
+
# For nil reports, only check the Sign-off section.
|
|
351
|
+
if grep -q '## 4\. Root cause' "$FILE" 2>/dev/null; then
|
|
352
|
+
SECTION=$(sed -n '/^## 4\. Root cause/,/^## [0-9]/p' "$FILE")
|
|
353
|
+
if echo "$SECTION" | grep -q 'REPLACE'; then
|
|
354
|
+
echo "::warning::$(basename "$FILE"): §4 (Root cause) contains REPLACE markers — skipping upload. Complete the section and push to develop."
|
|
355
|
+
return 0
|
|
356
|
+
fi
|
|
357
|
+
fi
|
|
358
|
+
if grep -q '## 5\. Impact' "$FILE" 2>/dev/null; then
|
|
359
|
+
SECTION=$(sed -n '/^## 5\. Impact/,/^## [0-9]/p' "$FILE")
|
|
360
|
+
if echo "$SECTION" | grep -q 'REPLACE'; then
|
|
361
|
+
echo "::warning::$(basename "$FILE"): §5 (Impact) contains REPLACE markers — skipping upload. Complete the section and push to develop."
|
|
362
|
+
return 0
|
|
363
|
+
fi
|
|
364
|
+
fi
|
|
365
|
+
if grep -q '## 9\. Sign-off' "$FILE" 2>/dev/null; then
|
|
366
|
+
SECTION=$(sed -n '/^## 9\. Sign-off/,/^## /p' "$FILE")
|
|
367
|
+
if echo "$SECTION" | grep -q 'REPLACE'; then
|
|
368
|
+
echo "::warning::$(basename "$FILE"): §9 (Sign-off) contains REPLACE markers — skipping upload. Complete the sign-off and push to develop."
|
|
369
|
+
return 0
|
|
370
|
+
fi
|
|
371
|
+
fi
|
|
372
|
+
# Nil reports have a simpler sign-off section — check it too.
|
|
373
|
+
if grep -q '## Sign-off' "$FILE" 2>/dev/null && ! grep -q '## 9\. Sign-off' "$FILE" 2>/dev/null; then
|
|
374
|
+
SECTION=$(sed -n '/^## Sign-off/,/^## /p' "$FILE")
|
|
375
|
+
if echo "$SECTION" | grep -q 'REPLACE'; then
|
|
376
|
+
echo "::warning::$(basename "$FILE"): Sign-off section contains REPLACE markers — skipping upload. Complete the sign-off and push to develop."
|
|
377
|
+
return 0
|
|
378
|
+
fi
|
|
379
|
+
fi
|
|
380
|
+
echo "Uploading governance: $(basename "$FILE") (type=incident_report)"
|
|
381
|
+
bash scripts/upload-evidence.sh \
|
|
382
|
+
{{PROJECT_SLUG}} _compliance-docs incident_report "$FILE" \
|
|
383
|
+
--category planning ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
384
|
+
"${DERIVED_META[@]}" \
|
|
385
|
+
|| echo "Warning: Failed to upload $(basename "$FILE")"
|
|
386
|
+
}
|
|
316
387
|
shopt -s nullglob
|
|
317
388
|
for f in compliance/incident-report*.md compliance/governance/incident-report*.md; do
|
|
318
|
-
|
|
389
|
+
upload_incident_report "$f"
|
|
390
|
+
done
|
|
391
|
+
# DevAudit-Installer#210 §8a: nil incident reports (per-release
|
|
392
|
+
# "no incidents" attestations). Glob alongside populated reports
|
|
393
|
+
# and upload as incident_report evidence.
|
|
394
|
+
for f in compliance/nil-incident-report*.md compliance/governance/nil-incident-report*.md; do
|
|
395
|
+
upload_incident_report "$f"
|
|
319
396
|
done
|
|
320
397
|
shopt -u nullglob
|
|
321
398
|
|
|
@@ -339,7 +416,7 @@ jobs:
|
|
|
339
416
|
echo "Uploading: audit-log.json (audit_log — 90-day window)"
|
|
340
417
|
bash scripts/upload-evidence.sh \
|
|
341
418
|
{{PROJECT_SLUG}} _compliance-docs audit_log "$AUDIT_LOG_FILE" \
|
|
342
|
-
--category
|
|
419
|
+
--category audit_log ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
343
420
|
"${DERIVED_META[@]}" \
|
|
344
421
|
|| echo "Warning: Failed to upload audit-log.json"
|
|
345
422
|
else
|
|
@@ -393,7 +470,7 @@ jobs:
|
|
|
393
470
|
esac
|
|
394
471
|
echo "Uploading: $(basename "$TICKET") -> release ${TICKET_RELEASE}"
|
|
395
472
|
eval "bash scripts/upload-evidence.sh \
|
|
396
|
-
{{PROJECT_SLUG}} \"${TICKET_OWNER}\"
|
|
473
|
+
{{PROJECT_SLUG}} \"${TICKET_OWNER}\" release_ticket \"$TICKET\" \
|
|
397
474
|
--category release_artifact ${FLAGS} --release \"${TICKET_RELEASE}\" \
|
|
398
475
|
${TICKET_META_ARGS}" \
|
|
399
476
|
|| echo "Warning: Failed to upload $(basename "$TICKET")"
|
|
@@ -435,44 +512,54 @@ jobs:
|
|
|
435
512
|
for ARTIFACT in "$REQ_DIR"*.md; do
|
|
436
513
|
[ -f "$ARTIFACT" ] || continue
|
|
437
514
|
# Per-REQ basename → (evidence_type, evidence_category) routing.
|
|
438
|
-
#
|
|
439
|
-
#
|
|
440
|
-
#
|
|
441
|
-
#
|
|
515
|
+
# Every SDLC-expected artifact has an explicit case with a
|
|
516
|
+
# dedicated evidence_type so the portal's framework-coverage
|
|
517
|
+
# predicates attribute them to the correct clause. The `*)`
|
|
518
|
+
# catch-all was removed (DevAudit-Installer#205) — unrecognized
|
|
519
|
+
# filenames are now skipped with a warning instead of being
|
|
520
|
+
# silently uploaded as compliance_document/planning, which
|
|
521
|
+
# caused wrong artifacts to satisfy wrong framework clauses.
|
|
522
|
+
#
|
|
523
|
+
# Evidence type → framework clause mapping:
|
|
524
|
+
#
|
|
525
|
+
# - test-execution-summary.md → test_report
|
|
526
|
+
# ISO 29119-3 §3.5.6 Test Completion Report.
|
|
527
|
+
# DevAudit-Installer#101.
|
|
528
|
+
#
|
|
529
|
+
# - srs-alignment.md → srs_alignment
|
|
530
|
+
# Output of requirements-aligner skill at Stage 3.
|
|
531
|
+
# DevAudit-Installer#119.
|
|
532
|
+
#
|
|
533
|
+
# - architecture-decision.md → architecture_decision
|
|
534
|
+
# ISO 27001 A.8.25 (Secure SDLC).
|
|
535
|
+
# DevAudit-Installer#120.
|
|
536
|
+
#
|
|
537
|
+
# - risk-assessment.md → risk_assessment
|
|
538
|
+
# SOC 2 CC3.2 (Risk identification).
|
|
539
|
+
# DevAudit-Installer#121.
|
|
442
540
|
#
|
|
443
|
-
# - test-
|
|
444
|
-
#
|
|
445
|
-
# Report per release cycle. Satisfies the portal's
|
|
446
|
-
# Test Reports gate with per-release evidence
|
|
447
|
-
# instead of the project-level evergreen TSR (which
|
|
448
|
-
# from v0.1.32 downgrades to compliance_document).
|
|
449
|
-
# DevAudit-Installer#101.
|
|
541
|
+
# - test-scope.md → test_scope
|
|
542
|
+
# ISO 29119 §3.3 (Test scope).
|
|
450
543
|
#
|
|
451
|
-
# -
|
|
452
|
-
#
|
|
453
|
-
# skill at Stage 3. Orphan-by-design at v1 per
|
|
454
|
-
# META-COMPLY framework-registry-auditor review;
|
|
455
|
-
# surfaces in Documents tab + audit-pack export.
|
|
456
|
-
# DevAudit-Installer#119.
|
|
544
|
+
# - test-plan.md → test_plan
|
|
545
|
+
# ISO 29119 §3.4 (Test plan, per-REQ).
|
|
457
546
|
#
|
|
458
|
-
# -
|
|
459
|
-
#
|
|
460
|
-
# skill at Stage 3. Closes ISO 27001 A.8.25 (Secure
|
|
461
|
-
# development life cycle) via the dedicated type
|
|
462
|
-
# predicate. DevAudit-Installer#120.
|
|
547
|
+
# - implementation-plan.md → implementation_plan
|
|
548
|
+
# ISO 27001 A.8.25 (Secure SDLC planning).
|
|
463
549
|
#
|
|
464
|
-
# -
|
|
465
|
-
#
|
|
466
|
-
# skill at Stage 3. Closes SOC 2 CC3.2 (Risk
|
|
467
|
-
# identification and assessment) via the dedicated
|
|
468
|
-
# type predicate. DevAudit-Installer#121.
|
|
550
|
+
# - security-summary.md → security_summary
|
|
551
|
+
# ISO 27001 A.8.28 / SOC2 CC6.1.
|
|
469
552
|
#
|
|
470
|
-
#
|
|
471
|
-
#
|
|
472
|
-
#
|
|
473
|
-
#
|
|
474
|
-
#
|
|
475
|
-
#
|
|
553
|
+
# - ai-use-note.md → ai_use_note
|
|
554
|
+
# EUAIA Art. 13 (Transparency).
|
|
555
|
+
#
|
|
556
|
+
# - ai-prompts.md → ai_prompt_log
|
|
557
|
+
# EUAIA Art. 12 (Record-keeping).
|
|
558
|
+
#
|
|
559
|
+
# - ai-agent-handoff.md → ai_agent_handoff
|
|
560
|
+
# EUAIA Art. 14 (Human oversight).
|
|
561
|
+
#
|
|
562
|
+
# DevAudit-Installer#146, #205.
|
|
476
563
|
BASENAME=$(basename "$ARTIFACT")
|
|
477
564
|
case "$BASENAME" in
|
|
478
565
|
test-execution-summary.md|test-summary-report.md)
|
|
@@ -483,8 +570,23 @@ jobs:
|
|
|
483
570
|
EVTYPE=architecture_decision; EVCAT=planning ;;
|
|
484
571
|
risk-assessment.md)
|
|
485
572
|
EVTYPE=risk_assessment; EVCAT=planning ;;
|
|
573
|
+
test-scope.md)
|
|
574
|
+
EVTYPE=test_scope; EVCAT=planning ;;
|
|
575
|
+
test-plan.md)
|
|
576
|
+
EVTYPE=test_plan; EVCAT=planning ;;
|
|
577
|
+
implementation-plan.md)
|
|
578
|
+
EVTYPE=implementation_plan; EVCAT=planning ;;
|
|
579
|
+
security-summary.md)
|
|
580
|
+
EVTYPE=security_summary; EVCAT=security ;;
|
|
581
|
+
ai-use-note.md)
|
|
582
|
+
EVTYPE=ai_use_note; EVCAT=ai_governance ;;
|
|
583
|
+
ai-prompts.md)
|
|
584
|
+
EVTYPE=ai_prompt_log; EVCAT=ai_governance ;;
|
|
585
|
+
ai-agent-handoff.md)
|
|
586
|
+
EVTYPE=ai_agent_handoff; EVCAT=ai_governance ;;
|
|
486
587
|
*)
|
|
487
|
-
|
|
588
|
+
echo "::warning::Unrecognized artifact ${REQ_ID}/${BASENAME} — skipping upload. Add explicit routing in compliance-evidence.yml if this is a new evidence type. (DevAudit-Installer#205)"
|
|
589
|
+
continue ;;
|
|
488
590
|
esac
|
|
489
591
|
echo "Uploading: ${REQ_ID}/${BASENAME} (${EVTYPE})"
|
|
490
592
|
eval "bash scripts/upload-evidence.sh \
|
|
@@ -605,6 +707,7 @@ jobs:
|
|
|
605
707
|
--ci-run-id ${{ github.event.workflow_run.id }} \
|
|
606
708
|
--branch ${PRIOR_BRANCH}"
|
|
607
709
|
[ -n "$STAGE" ] && FLAGS="${FLAGS} --sdlc-stage ${STAGE}"
|
|
710
|
+
FLAGS="${FLAGS} --test-cycle ${{ github.event.workflow_run.id }}"
|
|
608
711
|
|
|
609
712
|
# In-scope REQs from pending release tickets at the triggering
|
|
610
713
|
# SHA. Fall back to `_compliance-docs` if no tickets present so
|
|
@@ -636,7 +739,7 @@ jobs:
|
|
|
636
739
|
if bash scripts/upload-evidence.sh \
|
|
637
740
|
{{PROJECT_SLUG}} "$REQ" e2e_result \
|
|
638
741
|
e2e-artifacts/e2e-regression-results.json \
|
|
639
|
-
--category
|
|
742
|
+
--category e2e_result ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
640
743
|
--meta-key "tier=${TIER}"
|
|
641
744
|
then
|
|
642
745
|
:
|
|
@@ -645,10 +748,13 @@ jobs:
|
|
|
645
748
|
UPLOAD_FAILURES=$((UPLOAD_FAILURES + 1))
|
|
646
749
|
fi
|
|
647
750
|
fi
|
|
648
|
-
# 2. Playwright HTML report
|
|
751
|
+
# 2. Playwright HTML report zip → e2e_report. Operator-
|
|
649
752
|
# facing artefact (the formatted run summary). Skipped if
|
|
650
753
|
# the report directory is missing (e.g. an upstream
|
|
651
754
|
# infrastructure failure aborted before reporting).
|
|
755
|
+
# Uses evidence_type=e2e_report (NOT test_report) so the
|
|
756
|
+
# portal can distinguish run-context bundles from per-REQ
|
|
757
|
+
# test completion reports (DevAudit-Installer#207).
|
|
652
758
|
if [ -d e2e-artifacts/playwright-report ]; then
|
|
653
759
|
(cd e2e-artifacts && zip -qr playwright-report.zip playwright-report/) 2>/dev/null || true
|
|
654
760
|
fi
|
|
@@ -661,9 +767,9 @@ jobs:
|
|
|
661
767
|
# Once the portal's presigned R2 URL flow is live (#189
|
|
662
768
|
# Option C), this can be reverted to a hard fail.
|
|
663
769
|
if bash scripts/upload-evidence.sh \
|
|
664
|
-
{{PROJECT_SLUG}} "$REQ"
|
|
770
|
+
{{PROJECT_SLUG}} "$REQ" e2e_report \
|
|
665
771
|
e2e-artifacts/playwright-report.zip \
|
|
666
|
-
--category
|
|
772
|
+
--category e2e_report ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
667
773
|
--meta-key "tier=${TIER}"
|
|
668
774
|
then
|
|
669
775
|
:
|
|
@@ -686,3 +792,197 @@ jobs:
|
|
|
686
792
|
exit 1
|
|
687
793
|
fi
|
|
688
794
|
echo "E2E ${TIER}-tier evidence uploaded for ${#REQS[@]} requirement(s)"
|
|
795
|
+
|
|
796
|
+
- name: Triage and file incidents on E2E Regression failure
|
|
797
|
+
if: steps.resolve.outputs.skip != 'true' && github.event.workflow_run.conclusion == 'failure'
|
|
798
|
+
env:
|
|
799
|
+
GH_TOKEN: ${{ secrets.DEVAUDIT_USER_TOKEN || github.token }}
|
|
800
|
+
CI_RUN_ID: ${{ github.event.workflow_run.id }}
|
|
801
|
+
WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}
|
|
802
|
+
GIT_SHA: ${{ github.event.workflow_run.head_sha }}
|
|
803
|
+
run: |
|
|
804
|
+
# DevAudit-Installer#210 §9a — triage E2E regression failures
|
|
805
|
+
# and file incident issues for application defects + unintended
|
|
806
|
+
# visual diffs. Flakes, test bugs, and seed-data gaps are NOT
|
|
807
|
+
# filed with the incident label (they don't produce incident_report
|
|
808
|
+
# evidence). Classification rationale is included in every filed
|
|
809
|
+
# issue body so a human can verify and reclassify.
|
|
810
|
+
#
|
|
811
|
+
# testCycleId (CI run ID) cross-references #209 so the incident
|
|
812
|
+
# report traces back to the specific test cycle that found the defect.
|
|
813
|
+
|
|
814
|
+
if [ ! -f e2e-artifacts/e2e-regression-results.json ]; then
|
|
815
|
+
echo "No e2e-regression-results.json found — cannot triage. Skipping incident filing."
|
|
816
|
+
exit 0
|
|
817
|
+
fi
|
|
818
|
+
|
|
819
|
+
# Ensure the incident + test-bug labels exist (idempotent).
|
|
820
|
+
gh label list --json name --jq '.[].name' | grep -qx incident || \
|
|
821
|
+
gh label create incident --color 'B60205' \
|
|
822
|
+
--description 'Operational, test, or compliance incident; close to auto-archive as portal evidence'
|
|
823
|
+
gh label list --json name --jq '.[].name' | grep -qx test-bug || \
|
|
824
|
+
gh label create test-bug --color 'FBCA04' \
|
|
825
|
+
--description 'Test bug or seed-data gap — not an application defect'
|
|
826
|
+
|
|
827
|
+
FLAKE_COUNT=0
|
|
828
|
+
TEST_BUG_COUNT=0
|
|
829
|
+
SEED_GAP_COUNT=0
|
|
830
|
+
VISUAL_INTENDED_COUNT=0
|
|
831
|
+
VISUAL_UNINTENDED_COUNT=0
|
|
832
|
+
APP_DEFECT_COUNT=0
|
|
833
|
+
|
|
834
|
+
# Parse the Playwright JSON reporter output and classify each failing test.
|
|
835
|
+
# Using jq to extract failing tests.
|
|
836
|
+
FAILING_TESTS=$(jq -r '
|
|
837
|
+
[.suites[]? | .specs[]? | select(.status == "failed" or .status == "timedOut" or .status == "interrupted" or .status == "flaky")]
|
|
838
|
+
| .[]
|
|
839
|
+
| [.title, .status, (.tests[0].results[-1].error.message // "no error message")] | @tsv
|
|
840
|
+
' e2e-artifacts/e2e-regression-results.json 2>/dev/null || true)
|
|
841
|
+
|
|
842
|
+
if [ -z "$FAILING_TESTS" ]; then
|
|
843
|
+
echo "No failing tests found in JSON report — nothing to triage."
|
|
844
|
+
exit 0
|
|
845
|
+
fi
|
|
846
|
+
|
|
847
|
+
while IFS=$'\t' read -r TEST_NAME TEST_STATUS ERROR_MSG; do
|
|
848
|
+
[ -z "$TEST_NAME" ] && continue
|
|
849
|
+
|
|
850
|
+
# --- Classification ---
|
|
851
|
+
CLASSIFICATION=""
|
|
852
|
+
RATIONALE=""
|
|
853
|
+
FILE_LABEL=""
|
|
854
|
+
|
|
855
|
+
# Check for flake (Playwright status "flaky" = passed on retry).
|
|
856
|
+
if [ "$TEST_STATUS" = "flaky" ]; then
|
|
857
|
+
CLASSIFICATION="flake"
|
|
858
|
+
RATIONALE="Test has status 'flaky' — passed on retry. No incident filed."
|
|
859
|
+
FLAKE_COUNT=$((FLAKE_COUNT + 1))
|
|
860
|
+
echo " [FLAKE] $TEST_NAME — $RATIONALE"
|
|
861
|
+
continue
|
|
862
|
+
fi
|
|
863
|
+
|
|
864
|
+
# Check for test bug patterns.
|
|
865
|
+
if echo "$ERROR_MSG" | grep -qiE 'TimeoutError|locator\.fill.*strict mode|Page\.waitForSelector|TestTimeoutError'; then
|
|
866
|
+
CLASSIFICATION="test-bug"
|
|
867
|
+
RATIONALE="Error message matches selector/timeout patterns (TimeoutError, strict mode violation, waitForSelector). Likely a test bug, not an application defect."
|
|
868
|
+
FILE_LABEL="test-bug"
|
|
869
|
+
TEST_BUG_COUNT=$((TEST_BUG_COUNT + 1))
|
|
870
|
+
# Check for seed-data gap patterns.
|
|
871
|
+
elif echo "$ERROR_MSG" | grep -qiE 'toHaveCount\(0\)|toBeVisible.*empty|toHaveLength\(0\)'; then
|
|
872
|
+
CLASSIFICATION="seed-data-gap"
|
|
873
|
+
RATIONALE="Error message matches assertion on empty state. Likely a seed-data gap — the assertion is correct but the fixture doesn't satisfy it."
|
|
874
|
+
FILE_LABEL="test-bug"
|
|
875
|
+
SEED_GAP_COUNT=$((SEED_GAP_COUNT + 1))
|
|
876
|
+
# Check for visual diff (intended vs unintended).
|
|
877
|
+
elif echo "$TEST_NAME" | grep -qiE 'visual|screenshot|snapshot'; then
|
|
878
|
+
# Check if recent commits touch the affected area.
|
|
879
|
+
RECENT_FILES=$(git diff --name-only HEAD~1..HEAD 2>/dev/null || true)
|
|
880
|
+
if echo "$RECENT_FILES" | grep -qiE 'visual|screenshot|snapshot|component|ui'; then
|
|
881
|
+
CLASSIFICATION="visual-diff-intended"
|
|
882
|
+
RATIONALE="Spec is in a visual-regression directory and the diff is in an area recent commits touch. Likely intended — human should update the baseline."
|
|
883
|
+
VISUAL_INTENDED_COUNT=$((VISUAL_INTENDED_COUNT + 1))
|
|
884
|
+
echo " [VISUAL-INTENDED] $TEST_NAME — $RATIONALE"
|
|
885
|
+
continue
|
|
886
|
+
else
|
|
887
|
+
CLASSIFICATION="visual-diff-unintended"
|
|
888
|
+
RATIONALE="Spec is in a visual-regression directory but the diff is in an area NOT touched by recent commits. Regression — something broke visually without a deliberate change."
|
|
889
|
+
FILE_LABEL="incident"
|
|
890
|
+
VISUAL_UNINTENDED_COUNT=$((VISUAL_UNINTENDED_COUNT + 1))
|
|
891
|
+
fi
|
|
892
|
+
else
|
|
893
|
+
# Default: application defect (conservative).
|
|
894
|
+
CLASSIFICATION="application-defect"
|
|
895
|
+
RATIONALE="Error is an assertion failure, application error, or unrecognised pattern. Defaulting to application defect (conservative)."
|
|
896
|
+
FILE_LABEL="incident"
|
|
897
|
+
APP_DEFECT_COUNT=$((APP_DEFECT_COUNT + 1))
|
|
898
|
+
fi
|
|
899
|
+
|
|
900
|
+
# --- File issue ---
|
|
901
|
+
if [ "$FILE_LABEL" = "incident" ]; then
|
|
902
|
+
# Dedup: check for existing open incident issue for the same test name.
|
|
903
|
+
EXISTING=$(gh issue list --label incident --state open --search "$TEST_NAME" --json number --jq '.[0].number' || true)
|
|
904
|
+
if [ -n "$EXISTING" ]; then
|
|
905
|
+
echo " [DEDUP] $TEST_NAME — existing incident #$EXISTING already open. Posting comment."
|
|
906
|
+
gh issue comment "$EXISTING" --body "Additional failure detected in workflow run ${WORKFLOW_URL} (SHA ${GIT_SHA}). Classification: ${CLASSIFICATION}. Rationale: ${RATIONALE}"
|
|
907
|
+
continue
|
|
908
|
+
fi
|
|
909
|
+
|
|
910
|
+
echo " [INCIDENT] $TEST_NAME — filing with incident label"
|
|
911
|
+
|
|
912
|
+
ISSUE_BODY="## E2E Regression Failure — ${CLASSIFICATION}
|
|
913
|
+
|
|
914
|
+
**Test name:** ${TEST_NAME}
|
|
915
|
+
**Error message:**
|
|
916
|
+
\`\`\`
|
|
917
|
+
${ERROR_MSG}
|
|
918
|
+
\`\`\`
|
|
919
|
+
|
|
920
|
+
**Classification:** ${CLASSIFICATION}
|
|
921
|
+
**Classification rationale:** ${RATIONALE}
|
|
922
|
+
|
|
923
|
+
**testCycleId:** ${CI_RUN_ID}
|
|
924
|
+
**Workflow run:** ${WORKFLOW_URL}
|
|
925
|
+
**Git SHA:** ${GIT_SHA}
|
|
926
|
+
|
|
927
|
+
This regression was detected and classified by the autonomous E2E Regression CI run. A human should confirm the classification, adjust severity if needed, and close once fixed. The \`incident\` label ensures an \`incident_report\` will be generated on close.
|
|
928
|
+
|
|
929
|
+
### Framework attribution
|
|
930
|
+
|
|
931
|
+
This defect, once closed with the \`incident\` label, will be auto-exported as \`incident_report\` evidence and attribute to:
|
|
932
|
+
|
|
933
|
+
- [x] \`ISO29119.3.5.4\` (baseline — every incident_report)
|
|
934
|
+
- [x] \`SOC2.CC7.2\` — ops impact: a regression in production-adjacent code is an ops concern
|
|
935
|
+
- [ ] \`GDPR.Art-33\` — personal data scope: <REPLACE — yes/no>
|
|
936
|
+
- [ ] \`GDPR.Art-34\` — data-subject notification required: <REPLACE — yes/no>
|
|
937
|
+
- [ ] \`EUAIA.Art-9 / Art-14 / Art-15\` — AI failure: <REPLACE — yes/no, which article(s)>
|
|
938
|
+
|
|
939
|
+
Once closed, the \`incident-export.yml\` workflow exports this issue's body to \`compliance/governance/incident-report-<N>.md\`.
|
|
940
|
+
"
|
|
941
|
+
|
|
942
|
+
gh issue create \
|
|
943
|
+
--title "[REGRESSION] ${TEST_NAME}" \
|
|
944
|
+
--label "incident,${CLASSIFICATION}" \
|
|
945
|
+
--body "$ISSUE_BODY"
|
|
946
|
+
|
|
947
|
+
elif [ "$FILE_LABEL" = "test-bug" ]; then
|
|
948
|
+
echo " [TEST-BUG] $TEST_NAME — filing with test-bug label (no incident label)"
|
|
949
|
+
|
|
950
|
+
ISSUE_BODY="## E2E Regression Failure — ${CLASSIFICATION}
|
|
951
|
+
|
|
952
|
+
**Test name:** ${TEST_NAME}
|
|
953
|
+
**Error message:**
|
|
954
|
+
\`\`\`
|
|
955
|
+
${ERROR_MSG}
|
|
956
|
+
\`\`\`
|
|
957
|
+
|
|
958
|
+
**Classification:** ${CLASSIFICATION}
|
|
959
|
+
**Classification rationale:** ${RATIONALE}
|
|
960
|
+
|
|
961
|
+
**testCycleId:** ${CI_RUN_ID}
|
|
962
|
+
**Workflow run:** ${WORKFLOW_URL}
|
|
963
|
+
**Git SHA:** ${GIT_SHA}
|
|
964
|
+
|
|
965
|
+
This failure appears to be a ${CLASSIFICATION} (not an application defect). A human should confirm and fix the ${CLASSIFICATION}, not the application. Filed with the \`test-bug\` label — this does NOT produce \`incident_report\` evidence.
|
|
966
|
+
"
|
|
967
|
+
|
|
968
|
+
gh issue create \
|
|
969
|
+
--title "[TEST-BUG] ${TEST_NAME}" \
|
|
970
|
+
--label "test-bug" \
|
|
971
|
+
--body "$ISSUE_BODY"
|
|
972
|
+
fi
|
|
973
|
+
|
|
974
|
+
done <<< "$FAILING_TESTS"
|
|
975
|
+
|
|
976
|
+
# --- Post summary comment on the workflow run ---
|
|
977
|
+
SUMMARY="E2E Regression triage summary:
|
|
978
|
+
- Flakes (not filed): ${FLAKE_COUNT}
|
|
979
|
+
- Test bugs (filed without incident label): ${TEST_BUG_COUNT}
|
|
980
|
+
- Seed-data gaps (filed without incident label): ${SEED_GAP_COUNT}
|
|
981
|
+
- Visual diffs intended (not filed): ${VISUAL_INTENDED_COUNT}
|
|
982
|
+
- Visual diffs unintended (filed as incident): ${VISUAL_UNINTENDED_COUNT}
|
|
983
|
+
- Application defects (filed as incident): ${APP_DEFECT_COUNT}"
|
|
984
|
+
|
|
985
|
+
echo "$SUMMARY"
|
|
986
|
+
# Post summary as a workflow run comment (using gh api).
|
|
987
|
+
gh api "${{ github.repository }}/actions/runs/${CI_RUN_ID}/comments" \
|
|
988
|
+
--method POST -f body="$SUMMARY" 2>/dev/null || true
|
|
@@ -128,11 +128,12 @@ jobs:
|
|
|
128
128
|
fi
|
|
129
129
|
if [ -f playwright-report.zip ]; then
|
|
130
130
|
bash scripts/upload-evidence.sh \
|
|
131
|
-
{{PROJECT_SLUG}} "$REQ_ID"
|
|
132
|
-
--category
|
|
131
|
+
{{PROJECT_SLUG}} "$REQ_ID" feature_e2e_report playwright-report.zip \
|
|
132
|
+
--category e2e_report --release "$REQ_ID" --create-release-if-missing \
|
|
133
133
|
--environment uat --sdlc-stage 2 \
|
|
134
134
|
--git-sha "${{ github.event.pull_request.head.sha }}" \
|
|
135
135
|
--ci-run-id "${{ github.run_id }}" --branch "${{ github.head_ref }}" \
|
|
136
|
+
--test-cycle "${{ github.run_id }}" \
|
|
136
137
|
--meta-key "origin=feature" \
|
|
137
138
|
|| echo "::warning::feature E2E report upload failed"
|
|
138
139
|
fi
|