@metasession.co/devaudit-cli 0.3.12 → 0.3.13

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 (65) hide show
  1. package/dist/index.js +53 -4
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/scripts/upload-evidence.sh +55 -1
  5. package/sdlc/files/_common/0-project-setup.md +10 -4
  6. package/sdlc/files/_common/Periodic_Security_Review_Schedule.md +5 -5
  7. package/sdlc/files/_common/README_TEMPLATE.md +8 -8
  8. package/sdlc/files/_common/Test_Architecture.md +15 -15
  9. package/sdlc/files/_common/Test_Plan_TEMPLATE.md +13 -13
  10. package/sdlc/files/_common/Test_Policy.md +2 -2
  11. package/sdlc/files/_common/Test_Strategy.md +10 -10
  12. package/sdlc/files/_common/governance/ai-disclosure.md.template +3 -3
  13. package/sdlc/files/_common/governance/dpia.md.template +3 -3
  14. package/sdlc/files/_common/governance/incident-report.md.template +3 -3
  15. package/sdlc/files/_common/governance/nil-incident-report.md.template +1 -1
  16. package/sdlc/files/_common/governance/periodic-review.md.template +3 -3
  17. package/sdlc/files/_common/governance/risk-register.md.template +1 -1
  18. package/sdlc/files/_common/governance/ropa.md.template +2 -2
  19. package/sdlc/files/_common/joining-an-existing-project.md +4 -4
  20. package/sdlc/files/_common/scripts/check-host-deployment.sh +107 -0
  21. package/sdlc/files/_common/scripts/check-host-deployment.test.sh +119 -0
  22. package/sdlc/files/_common/scripts/check-release-pr-scope.sh +95 -0
  23. package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +137 -0
  24. package/sdlc/files/_common/scripts/close-out-release.sh +175 -54
  25. package/sdlc/files/_common/scripts/close-out-release.test.sh +90 -0
  26. package/sdlc/files/_common/scripts/extract-release-metadata.sh +27 -0
  27. package/sdlc/files/_common/scripts/extract-release-metadata.test.sh +13 -1
  28. package/sdlc/files/_common/scripts/generate-bundled-changes.sh +302 -64
  29. package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +128 -82
  30. package/sdlc/files/_common/scripts/generate-housekeeping-release-ticket.sh +18 -9
  31. package/sdlc/files/_common/scripts/generate-housekeeping-release-ticket.test.sh +66 -0
  32. package/sdlc/files/_common/scripts/generate-security-summary.sh +14 -5
  33. package/sdlc/files/_common/scripts/generate-security-summary.test.sh +57 -0
  34. package/sdlc/files/_common/scripts/prepare-release-pr.sh +44 -4
  35. package/sdlc/files/_common/scripts/render-test-cycles.sh +227 -0
  36. package/sdlc/files/_common/scripts/render-test-cycles.test.sh +107 -0
  37. package/sdlc/files/_common/scripts/report-test-cycle.sh +344 -0
  38. package/sdlc/files/_common/scripts/submit-bundle-manifest.sh +100 -0
  39. package/sdlc/files/_common/scripts/submit-bundle-manifest.test.sh +138 -0
  40. package/sdlc/files/_common/scripts/upload-evidence.sh +24 -0
  41. package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +16 -0
  42. package/sdlc/files/_common/scripts/validate-compliance-artifacts.test.sh +14 -0
  43. package/sdlc/files/_common/scripts/validate-test-summary.test.sh +4 -4
  44. package/sdlc/files/_common/skills/adr-author/SKILL.md +2 -2
  45. package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +2 -2
  46. package/sdlc/files/_common/skills/e2e-test-engineer/references/bootstrap.md +5 -5
  47. package/sdlc/files/_common/skills/governance-doc-author/references/incident-classification.md +1 -1
  48. package/sdlc/files/_common/skills/requirements-aligner/SKILL.md +4 -4
  49. package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +21 -2
  50. package/sdlc/files/_common/skills/sdlc-implementer/references/change-request-loop.md +1 -1
  51. package/sdlc/files/_common/skills/sdlc-implementer/references/compliance-constraints.md +7 -14
  52. package/sdlc/files/ci/ci.yml.template +82 -28
  53. package/sdlc/files/ci/close-out-release.yml.template +7 -4
  54. package/sdlc/files/ci/compliance-evidence.yml.template +204 -20
  55. package/sdlc/files/ci/feature-e2e.yml.template +84 -1
  56. package/sdlc/files/ci/post-deploy-prod.yml.template +59 -4
  57. package/sdlc/files/ci/python/ci.yml.template +60 -12
  58. package/sdlc/files/ci/quality-gates-provenance.yml.template +123 -28
  59. package/sdlc/package.json +1 -1
  60. package/sdlc/src/bin/devaudit-sdlc.js +44 -2
  61. package/sdlc/src/blueprints/1-plan-requirement.raw.md +6 -1
  62. package/sdlc/src/blueprints/2-implement-and-test.raw.md +1 -1
  63. package/sdlc/src/blueprints/3-compile-evidence.raw.md +34 -20
  64. package/sdlc/src/blueprints/4-submit-for-review.raw.md +2 -2
  65. package/sdlc/src/blueprints/5-deploy-main.raw.md +2 -2
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ # submit-bundle-manifest.sh — resolve a release row, then POST its bundle manifest.
3
+ #
4
+ # Usage:
5
+ # ./scripts/submit-bundle-manifest.sh <project-slug> <release-version> <manifest-path> [--reconcile-existing-ownership]
6
+
7
+ set -euo pipefail
8
+
9
+ PROJECT_SLUG="${1:-}"
10
+ RELEASE_VERSION="${2:-}"
11
+ MANIFEST_PATH="${3:-}"
12
+ shift $(( $# >= 3 ? 3 : $# ))
13
+
14
+ RECONCILE_EXISTING_OWNERSHIP=false
15
+ while [ "$#" -gt 0 ]; do
16
+ case "$1" in
17
+ --reconcile-existing-ownership)
18
+ RECONCILE_EXISTING_OWNERSHIP=true
19
+ shift
20
+ ;;
21
+ *)
22
+ echo "Unknown argument: $1" >&2
23
+ exit 2
24
+ ;;
25
+ esac
26
+ done
27
+
28
+ if [ -z "$PROJECT_SLUG" ] || [ -z "$RELEASE_VERSION" ] || [ -z "$MANIFEST_PATH" ]; then
29
+ echo "Usage: $0 <project-slug> <release-version> <manifest-path> [--reconcile-existing-ownership]" >&2
30
+ exit 1
31
+ fi
32
+
33
+ : "${DEVAUDIT_BASE_URL:?DEVAUDIT_BASE_URL must be set}"
34
+ : "${DEVAUDIT_API_KEY:?DEVAUDIT_API_KEY must be set}"
35
+
36
+ if [ ! -f "$MANIFEST_PATH" ]; then
37
+ echo "Error: manifest file not found: $MANIFEST_PATH" >&2
38
+ exit 1
39
+ fi
40
+
41
+ if ! command -v jq >/dev/null 2>&1; then
42
+ echo "Error: jq is required." >&2
43
+ exit 1
44
+ fi
45
+
46
+ BASE_URL="${DEVAUDIT_BASE_URL%/}"
47
+
48
+ SCHEMA_VERSION="$(jq -r '.schemaVersion // empty' "$MANIFEST_PATH")"
49
+ if [ "$SCHEMA_VERSION" != "1" ]; then
50
+ echo "Error: manifest schemaVersion must be 1." >&2
51
+ exit 1
52
+ fi
53
+
54
+ MEMBER_COUNT="$(jq -r '(.members // []) | length' "$MANIFEST_PATH")"
55
+ WORK_ITEM_COUNT="$(jq -r '(.nonReleaseWorkItems // []) | length' "$MANIFEST_PATH")"
56
+ if [ "$MEMBER_COUNT" = "0" ] && [ "$WORK_ITEM_COUNT" = "0" ]; then
57
+ echo "Bundle manifest has no members or non-release work items; skipping submission."
58
+ exit 0
59
+ fi
60
+
61
+ RESOLVE_URL="${BASE_URL}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${RELEASE_VERSION}"
62
+ RESOLVE_RESPONSE="$(curl -sS -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" "$RESOLVE_URL")"
63
+ RELEASE_ID="$(printf '%s' "$RESOLVE_RESPONSE" | jq -r '.latest.id // empty')"
64
+ RESOLVED_VERSION="$(printf '%s' "$RESOLVE_RESPONSE" | jq -r '.latest.version // empty')"
65
+
66
+ if [ -z "$RELEASE_ID" ]; then
67
+ echo "Error: could not resolve release ${RELEASE_VERSION} for project ${PROJECT_SLUG}." >&2
68
+ exit 1
69
+ fi
70
+ if [ "$RESOLVED_VERSION" != "$RELEASE_VERSION" ]; then
71
+ echo "Error: resolve endpoint returned ${RESOLVED_VERSION} instead of exact release ${RELEASE_VERSION}." >&2
72
+ exit 1
73
+ fi
74
+
75
+ PAYLOAD="$(cat "$MANIFEST_PATH")"
76
+ if [ "$RECONCILE_EXISTING_OWNERSHIP" = "true" ]; then
77
+ PAYLOAD="$(jq '. + { reconcileExistingOwnership: true }' "$MANIFEST_PATH")"
78
+ fi
79
+
80
+ POST_URL="${BASE_URL}/api/ci/releases/${RELEASE_ID}/bundle-manifest"
81
+ HTTP_BODY_FILE="$(mktemp)"
82
+ trap 'rm -f "$HTTP_BODY_FILE"' EXIT
83
+ HTTP_CODE="$(
84
+ curl -sS -o "$HTTP_BODY_FILE" -w "%{http_code}" \
85
+ -X POST \
86
+ -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
87
+ -H "Content-Type: application/json" \
88
+ --data "$PAYLOAD" \
89
+ "$POST_URL"
90
+ )"
91
+
92
+ if [ "$HTTP_CODE" != "201" ]; then
93
+ echo "Error: bundle manifest submission failed with HTTP ${HTTP_CODE}." >&2
94
+ sed 's/^/ /' "$HTTP_BODY_FILE" >&2
95
+ exit 1
96
+ fi
97
+
98
+ echo "Submitted bundle manifest for ${RELEASE_VERSION} to release ${RELEASE_ID}."
99
+ echo " members: ${MEMBER_COUNT}"
100
+ echo " non-release work items: ${WORK_ITEM_COUNT}"
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env bash
2
+ # submit-bundle-manifest.test.sh — Focused tests for bundle manifest submission.
3
+
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ HELPER="$SCRIPT_DIR/submit-bundle-manifest.sh"
8
+ [ -x "$HELPER" ] || chmod +x "$HELPER"
9
+
10
+ PASS=0
11
+ FAIL=0
12
+ WORK="$(mktemp -d)"
13
+ trap 'rm -rf "$WORK"' EXIT
14
+
15
+ assert_contains() {
16
+ local desc="$1" needle="$2" file="$3"
17
+ if grep -Fq "$needle" "$file"; then
18
+ echo " PASS: $desc"
19
+ PASS=$((PASS + 1))
20
+ else
21
+ echo " FAIL: $desc"
22
+ echo " missing: $needle"
23
+ echo " file:"
24
+ sed 's/^/ /' "$file"
25
+ FAIL=$((FAIL + 1))
26
+ fi
27
+ }
28
+
29
+ make_fixture() {
30
+ local dir="$1"
31
+ rm -rf "$dir"
32
+ mkdir -p "$dir/bin"
33
+ cd "$dir"
34
+ cat > bin/curl <<'EOF'
35
+ #!/usr/bin/env bash
36
+ set -euo pipefail
37
+ LOG_FILE="${SUBMIT_TEST_LOG:?}"
38
+ URL=""
39
+ BODY_FILE=""
40
+ WRITE_OUT=""
41
+ METHOD="GET"
42
+ BODY=""
43
+ ARGS=("$@")
44
+ for ((i=0; i<${#ARGS[@]}; i++)); do
45
+ arg="${ARGS[$i]}"
46
+ case "$arg" in
47
+ -X) METHOD="${ARGS[$((i+1))]}" ;;
48
+ -o) BODY_FILE="${ARGS[$((i+1))]}" ;;
49
+ -w) WRITE_OUT="${ARGS[$((i+1))]}" ;;
50
+ --data) BODY="${ARGS[$((i+1))]}" ;;
51
+ http://*|https://*) URL="$arg" ;;
52
+ esac
53
+ done
54
+ echo "METHOD:${METHOD}" >> "$LOG_FILE"
55
+ echo "URL:${URL}" >> "$LOG_FILE"
56
+ if [ -n "$BODY" ]; then
57
+ echo "BODY:${BODY}" >> "$LOG_FILE"
58
+ fi
59
+ if [[ "$URL" == *"/api/ci/releases/resolve"* ]]; then
60
+ printf '{"latest":{"id":"rel_123","version":"REQ-042","status":"draft"}}'
61
+ exit 0
62
+ fi
63
+ if [[ "$URL" == *"/api/ci/releases/rel_123/bundle-manifest" ]]; then
64
+ if [ -n "$BODY_FILE" ]; then
65
+ printf '{"ok":true}' > "$BODY_FILE"
66
+ fi
67
+ if [ -n "$WRITE_OUT" ]; then
68
+ printf '201'
69
+ fi
70
+ exit 0
71
+ fi
72
+ printf '{"error":"unexpected url"}'
73
+ exit 1
74
+ EOF
75
+ chmod +x bin/curl
76
+ }
77
+
78
+ echo "=== submit-bundle-manifest.sh tests ==="
79
+
80
+ D1="$WORK/case1"
81
+ make_fixture "$D1"
82
+ cat > "$D1/manifest.json" <<'EOF'
83
+ {
84
+ "schemaVersion": 1,
85
+ "approvalRelease": { "version": "REQ-042" },
86
+ "coreRelease": { "version": "REQ-042" },
87
+ "members": [
88
+ {
89
+ "version": "REQ-041",
90
+ "role": "predecessor",
91
+ "relationship": "superseded"
92
+ }
93
+ ],
94
+ "nonReleaseWorkItems": [
95
+ {
96
+ "kind": "housekeeping_commit",
97
+ "title": "docs: refresh release notes"
98
+ }
99
+ ],
100
+ "manifestHash": "sha256:test"
101
+ }
102
+ EOF
103
+ LOG_FILE="$D1/submit.log"
104
+ touch "$LOG_FILE"
105
+ export PATH="$D1/bin:$PATH"
106
+ export SUBMIT_TEST_LOG="$LOG_FILE"
107
+ export DEVAUDIT_BASE_URL="https://devaudit.example.test"
108
+ export DEVAUDIT_API_KEY="mc_test_dummy"
109
+
110
+ bash "$HELPER" fixture-project REQ-042 "$D1/manifest.json" > "$D1/stdout.log" 2>&1
111
+ assert_contains "resolve endpoint called" "/api/ci/releases/resolve?projectSlug=fixture-project&versionPrefix=REQ-042" "$LOG_FILE"
112
+ assert_contains "bundle endpoint called" "/api/ci/releases/rel_123/bundle-manifest" "$LOG_FILE"
113
+ assert_contains "member payload submitted" "\"version\": \"REQ-041\"" "$LOG_FILE"
114
+
115
+ D2="$WORK/case2"
116
+ make_fixture "$D2"
117
+ cat > "$D2/manifest.json" <<'EOF'
118
+ {
119
+ "schemaVersion": 1,
120
+ "approvalRelease": { "version": "REQ-042" },
121
+ "coreRelease": { "version": "REQ-042" },
122
+ "members": [],
123
+ "nonReleaseWorkItems": []
124
+ }
125
+ EOF
126
+ LOG_FILE="$D2/submit.log"
127
+ touch "$LOG_FILE"
128
+ export PATH="$D2/bin:$PATH"
129
+ export SUBMIT_TEST_LOG="$LOG_FILE"
130
+
131
+ bash "$HELPER" fixture-project REQ-042 "$D2/manifest.json" > "$D2/stdout.log" 2>&1
132
+ assert_contains "empty manifest skips submission" "Bundle manifest has no members or non-release work items; skipping submission." "$D2/stdout.log"
133
+
134
+ echo ""
135
+ echo "=== submit-bundle-manifest.test.sh: $PASS passed, $FAIL failed ==="
136
+ if [ "$FAIL" -gt 0 ]; then
137
+ exit 1
138
+ fi
@@ -47,6 +47,14 @@
47
47
  # portal group evidence by test cycle per
48
48
  # ISO/IEC/IEEE 29119-3. Optional — older
49
49
  # portals ignore the field (no error).
50
+ # --evidence-scope <scope> Evidence ownership scope: release | stage |
51
+ # cycle | approval. Forwarded as
52
+ # `evidenceScope`; optional and ignored by
53
+ # older portals.
54
+ # --test-cycle-record-id <id> First-class portal cycle UUID. Requires
55
+ # `--evidence-scope cycle`. Forwarded as
56
+ # `testCycleRecordId`; preserved alongside
57
+ # legacy `--test-cycle` during dual-write.
50
58
  #
51
59
  # Required environment variables:
52
60
  # DEVAUDIT_BASE_URL e.g. https://meta-comply-production.up.railway.app
@@ -98,6 +106,8 @@ CHANGE_TYPE=""
98
106
  GATE_STATUS=""
99
107
  SDLC_STAGE=""
100
108
  TEST_CYCLE=""
109
+ EVIDENCE_SCOPE=""
110
+ TEST_CYCLE_RECORD_ID=""
101
111
  SENTINEL_CONTENT=""
102
112
  COMMIT_TIMESTAMP=""
103
113
  # Repeatable `--meta-key key=value` accumulator. Each pair gets merged
@@ -127,6 +137,8 @@ while [ "$#" -gt 0 ]; do
127
137
  --gate-status) GATE_STATUS="$2"; shift 2 ;;
128
138
  --sdlc-stage) SDLC_STAGE="$2"; shift 2 ;;
129
139
  --test-cycle) TEST_CYCLE="$2"; shift 2 ;;
140
+ --evidence-scope) EVIDENCE_SCOPE="$2"; shift 2 ;;
141
+ --test-cycle-record-id) TEST_CYCLE_RECORD_ID="$2"; shift 2 ;;
130
142
  # --meta-key key=value (repeatable). Merged into the metadata JSON
131
143
  # before posting. Validates the `key=value` shape; rejects bare
132
144
  # keys without `=`.
@@ -154,6 +166,14 @@ if [ -n "$SDLC_STAGE" ] && ! [[ "$SDLC_STAGE" =~ ^[1-5]$ ]]; then
154
166
  echo "Error: --sdlc-stage must be an integer 1-5 (got: $SDLC_STAGE)"
155
167
  exit 1
156
168
  fi
169
+ if [ -n "$EVIDENCE_SCOPE" ] && ! [[ "$EVIDENCE_SCOPE" =~ ^(release|stage|cycle|approval)$ ]]; then
170
+ echo "Error: --evidence-scope must be one of: release, stage, cycle, approval"
171
+ exit 1
172
+ fi
173
+ if [ -n "$TEST_CYCLE_RECORD_ID" ] && [ "$EVIDENCE_SCOPE" != "cycle" ]; then
174
+ echo "Error: --test-cycle-record-id requires --evidence-scope cycle"
175
+ exit 1
176
+ fi
157
177
 
158
178
  if [ -z "${DEVAUDIT_BASE_URL:-}" ]; then
159
179
  echo "Error: DEVAUDIT_BASE_URL environment variable is required"
@@ -365,6 +385,8 @@ upload_presigned() {
365
385
  \"changeType\": \"${CHANGE_TYPE}\",
366
386
  \"sdlcStage\": \"${SDLC_STAGE}\",
367
387
  \"testCycleId\": \"${TEST_CYCLE}\",
388
+ \"evidenceScope\": \"${EVIDENCE_SCOPE}\",
389
+ \"testCycleRecordId\": \"${TEST_CYCLE_RECORD_ID}\",
368
390
  \"sentinelContent\": $(jq -Rn --arg v "$SENTINEL_CONTENT" '$v'),
369
391
  \"commitTimestamp\": \"${COMMIT_TIMESTAMP}\"
370
392
  }") || curl_exit=$?
@@ -531,6 +553,8 @@ for FILE in "${FILES[@]}"; do
531
553
  [ -n "$GATE_STATUS" ] && CURL_ARGS+=(-F "gateStatus=${GATE_STATUS}")
532
554
  [ -n "$SDLC_STAGE" ] && CURL_ARGS+=(-F "sdlcStage=${SDLC_STAGE}")
533
555
  [ -n "$TEST_CYCLE" ] && CURL_ARGS+=(-F "testCycleId=${TEST_CYCLE}")
556
+ [ -n "$EVIDENCE_SCOPE" ] && CURL_ARGS+=(-F "evidenceScope=${EVIDENCE_SCOPE}")
557
+ [ -n "$TEST_CYCLE_RECORD_ID" ] && CURL_ARGS+=(-F "testCycleRecordId=${TEST_CYCLE_RECORD_ID}")
534
558
  [ -n "$SENTINEL_CONTENT" ] && CURL_ARGS+=(-F "sentinelContent=${SENTINEL_CONTENT}")
535
559
  [ -n "$COMMIT_TIMESTAMP" ] && CURL_ARGS+=(-F "commitTimestamp=${COMMIT_TIMESTAMP}")
536
560
 
@@ -243,6 +243,20 @@ for REQ in $REQUIREMENTS; do
243
243
  done
244
244
  if [ -n "$BUNDLED_FILE" ]; then
245
245
  echo " OK: Bundled release evidence present: ${BUNDLED_FILE}"
246
+ BUNDLED_MANIFEST=""
247
+ for MANIFEST_PATTERN in \
248
+ "compliance/pending-releases/BUNDLED-CHANGES-${REQ}.json" \
249
+ "compliance/approved-releases/BUNDLED-CHANGES-${REQ}.json" \
250
+ "compliance/superseded-releases/BUNDLED-CHANGES-${REQ}.json"; do
251
+ BUNDLED_MANIFEST=$(find_first_match "$MANIFEST_PATTERN" || true)
252
+ [ -n "$BUNDLED_MANIFEST" ] && break
253
+ done
254
+ if [ -n "$BUNDLED_MANIFEST" ]; then
255
+ echo " OK: Bundled release manifest present: ${BUNDLED_MANIFEST}"
256
+ else
257
+ echo " ERROR: Bundled release evidence exists but BUNDLED-CHANGES-${REQ}.json is missing"
258
+ EXIT_CODE=1
259
+ fi
246
260
  if grep -q '^## Bundled Changes' "$BUNDLED_FILE"; then
247
261
  echo " OK: Bundled release evidence has canonical heading"
248
262
  else
@@ -253,6 +267,8 @@ for REQ in $REQUIREMENTS; do
253
267
  "$BUNDLED_FILE" \
254
268
  "Bundled release evidence" \
255
269
  "Core tracked release" \
270
+ "Bundle manifest" \
271
+ "Manifest hash" \
256
272
  "Absorbed predecessor releases" \
257
273
  "Absorbed non-release work" \
258
274
  "Why bundled here" \
@@ -306,6 +306,7 @@ EOF
306
306
  echo "## Bundled Changes" > compliance/pending-releases/BUNDLED-CHANGES-REQ-344.md
307
307
  git add . && git commit -q --amend --no-edit
308
308
  run_validator
309
+ assert_grep "missing bundled manifest is reported" "ERROR: Bundled release evidence exists but BUNDLED-CHANGES-REQ-344.json is missing" 1
309
310
  assert_grep "missing structured fields on bundled evidence are reported" "ERROR: Bundled release evidence is missing bundled field '\\*\\*Core tracked release:\\*\\*'" 1
310
311
  assert_grep "missing structured fields on ticket are reported" "ERROR: Release ticket is missing bundled field '\\*\\*Core tracked release:\\*\\*'" 1
311
312
  assert_grep "missing bundled section on ticket is reported" "ERROR: Bundled release evidence exists but the release ticket is missing" 1
@@ -387,6 +388,8 @@ cat > compliance/pending-releases/BUNDLED-CHANGES-REQ-345.md <<'EOF'
387
388
  ## Bundled Changes
388
389
 
389
390
  - **Core tracked release:** `REQ-345`
391
+ - **Bundle manifest:** `BUNDLED-CHANGES-REQ-345.json`
392
+ - **Manifest hash:** `sha256:test-bundle-hash`
390
393
  - **Absorbed predecessor releases:** None
391
394
  - **Absorbed non-release work:** housekeeping syncs only
392
395
  - **Why bundled here:** housekeeping consolidation
@@ -399,8 +402,19 @@ cat > compliance/pending-releases/BUNDLED-CHANGES-REQ-345.md <<'EOF'
399
402
 
400
403
  - `abc123` chore: sync templates
401
404
  EOF
405
+ cat > compliance/pending-releases/BUNDLED-CHANGES-REQ-345.json <<'EOF'
406
+ {
407
+ "schemaVersion": 1,
408
+ "approvalRelease": { "version": "REQ-345" },
409
+ "coreRelease": { "version": "REQ-345" },
410
+ "members": [],
411
+ "nonReleaseWorkItems": [],
412
+ "manifestHash": "sha256:test-bundle-hash"
413
+ }
414
+ EOF
402
415
  git add . && git commit -q --amend --no-edit
403
416
  run_validator
417
+ assert_grep "bundled manifest accepted" "OK: Bundled release manifest present: compliance/pending-releases/BUNDLED-CHANGES-REQ-345.json" 1
404
418
  assert_grep "bundled ticket section accepted" "OK: Release ticket documents bundled release context" 1
405
419
  assert_grep "bundled test summary section accepted" "OK: test-execution-summary.md documents bundled release context" 1
406
420
  assert_grep "bundled security summary section accepted" "OK: security-summary.md documents bundled release context" 1
@@ -182,7 +182,7 @@ make_fixture "$D6" "REQ-006" << 'EOF'
182
182
  ## Accepted skips
183
183
 
184
184
  | Spec | Test | REQ/AC | Classification | Resolution attempted | Approved by | Rationale |
185
- |------|------|--------|---------------|---------------------|-------------|----------|
185
+ | ------ | ------ | -------- | --------------- | --------------------- | ------------- | ---------- |
186
186
  | e2e/foo.spec.ts | "AC1: bar dialog" | REQ-006/AC1 | intentional_non_applicability | N/A — API-only change | operator | API-only change, no UI surface |
187
187
 
188
188
  **Final assessment:** All gates passed. E2E skipped — API-only change.
@@ -279,7 +279,7 @@ make_fixture "$D12" "REQ-012" << 'EOF'
279
279
  ## Accepted skips
280
280
 
281
281
  | Spec | Test | REQ/AC | Classification | Resolution attempted | Approved by | Rationale |
282
- |------|------|--------|---------------|---------------------|-------------|----------|
282
+ | ------ | ------ | -------- | --------------- | --------------------- | ------------- | ---------- |
283
283
  | e2e/foo.spec.ts | "AC3: bar dialog" | REQ-012/AC3 | intentional_non_applicability | N/A — API-only change | operator | API-only change, no UI surface |
284
284
 
285
285
  **Final assessment:** All gates passed. E2E skipped — API-only change.
@@ -317,7 +317,7 @@ make_fixture "$D14" "REQ-014" << 'EOF'
317
317
  ## Accepted skips
318
318
 
319
319
  | Spec | Test | REQ/AC | Classification | Resolution attempted | Approved by | Rationale |
320
- |------|------|--------|---------------|---------------------|-------------|----------|
320
+ | ------ | ------ | -------- | --------------- | --------------------- | ------------- | ---------- |
321
321
 
322
322
  **Final assessment:** All gates passed. E2E skipped — API-only change.
323
323
  EOF
@@ -338,7 +338,7 @@ make_fixture "$D15" "REQ-015" << 'EOF'
338
338
  ## Accepted skips
339
339
 
340
340
  | Spec | Test | REQ/AC | Classification | Resolution attempted | Approved by | Rationale |
341
- |------|------|--------|---------------|---------------------|-------------|----------|
341
+ | ------ | ------ | -------- | --------------- | --------------------- | ------------- | ---------- |
342
342
  | e2e/foo.spec.ts | "AC3: bar dialog" | REQ-015/AC3 | intentional_non_applicability | N/A — API-only change | | API-only change, no UI surface |
343
343
 
344
344
  **Final assessment:** All gates passed. E2E skipped — API-only change.
@@ -12,7 +12,7 @@ The skill is **Phase A scope** (per [DevAudit-Installer#120](https://github.com/
12
12
  ## What this skill owns
13
13
 
14
14
  | Artefact | Lives at | Tier |
15
- |---|---|---|
15
+ | --- | --- | --- |
16
16
  | `docs/ADR/ADR-NNN-<slug>.md` (the SoT, project-spanning) | Top-level project docs | 2 (project strategy) |
17
17
  | `compliance/evidence/REQ-XXX/architecture-decision.md` (per-REQ Tier 3 evidence) | Per-REQ evidence directory | 3 (per-REQ) |
18
18
 
@@ -57,7 +57,7 @@ Input: the REQ's `compliance/plans/REQ-XXX/implementation-plan.md` plus the work
57
57
  **Step 2 — Apply the ADR-worthiness decision tree.** The skill judges *architectural significance* via these signals — any one matching ⇒ ADR warranted:
58
58
 
59
59
  | Signal | Examples | Verdict |
60
- |---|---|---|
60
+ | --- | --- | --- |
61
61
  | New third-party runtime dependency | adding `redis`, `bull`, a new ORM, a new auth provider package | ADR |
62
62
  | New external service | introducing Stripe, Twilio, a new SaaS integration | ADR |
63
63
  | New database / cache / queue tier | adding Redis alongside Postgres, swapping MongoDB for Postgres | ADR |
@@ -136,7 +136,7 @@ For each scenario, write a one-line description. Present the full grouped list t
136
136
  When designing each scenario, also pick the tier it'll live in. Three tiers map to MoSCoW priority + gating point (see `Test_Strategy.md` § *E2E gating model*):
137
137
 
138
138
  | Tier | File location | Picks this when… |
139
- |---|---|---|
139
+ | --- | --- | --- |
140
140
  | **smoke** | `e2e/smoke/*.spec.ts` | Cross-cutting sanity that proves the app is up: login, basic nav, one canonical CRUD per main domain. Runs on every push to the integration branch. Keep small — total smoke wall-clock target is ~3–5 min. |
141
141
  | **critical** | `e2e/critical/*.spec.ts` | Must-priority SRS item that breaks a headline flow if it regresses. Examples: payment authorisation, order completion, admin permission editing, RBAC enforcement on financial surfaces. Runs on PR-to-release-branch. Total critical wall-clock target ~10–15 min (includes smoke). |
142
142
  | **regression** | `e2e/<area>/*.spec.ts` | Should/Could-priority SRS item, edge cases, less-load-bearing flows. Runs nightly + post-merge + dispatch. Total full pack can be 30+ min; that's the point of the tier. |
@@ -365,7 +365,7 @@ Every defect filed from Phase 6 becomes `incident_report` evidence when (a) the
365
365
  Classify the defect against this table when filing — the canonical version lives at `governance-doc-author/references/incident-classification.md`, mirrored here for the e2e workflow:
366
366
 
367
367
  | Defect characteristic | Frameworks/clauses attributed |
368
- |---|---|
368
+ | --- | --- |
369
369
  | **Any test failure / defect** (baseline — always) | `ISO29119.3.5.4` Test incident report |
370
370
  | **Ops impact** (downtime, persistent errors, perf regression, data corruption) | + `SOC2.CC7.2` System monitoring and incident response |
371
371
  | **Security vulnerability** (auth bypass, injection, data exposure) | + `SOC2.CC7.2` + relevant ISO 27001 controls |
@@ -20,7 +20,7 @@ Recommend a primary, mention the runner-up, get user confirmation before install
20
20
  ### Web apps — JavaScript / TypeScript
21
21
 
22
22
  | Stack | Primary | Runner-up | Rationale |
23
- |---|---|---|---|
23
+ | --- | --- | --- | --- |
24
24
  | React / Next.js / Remix | **Playwright** | Cypress | Fast, parallel by default, multi-browser, first-party TS, built-in visual regression, excellent trace viewer. |
25
25
  | Vue / Nuxt | **Playwright** | Cypress | Same reasons. |
26
26
  | Angular | **Playwright** | Cypress | Playwright has overtaken Cypress as the Angular community default; Cypress still common in established Angular shops. |
@@ -32,7 +32,7 @@ Recommend a primary, mention the runner-up, get user confirmation before install
32
32
  ### Mobile
33
33
 
34
34
  | Stack | Primary | Runner-up | Rationale |
35
- |---|---|---|---|
35
+ | --- | --- | --- | --- |
36
36
  | React Native | **Detox** | Appium | Native, fast, integrates with RN test infra. |
37
37
  | Native iOS / Android cross-platform | **Appium** | Maestro | Industry standard; Maestro is gaining popularity for simpler flows. |
38
38
  | Mobile web | **Playwright** (mobile emulation) | Appium (for real-device need) | Playwright covers viewport + UA emulation; Appium for real-device touch interactions. |
@@ -40,14 +40,14 @@ Recommend a primary, mention the runner-up, get user confirmation before install
40
40
  ### Desktop
41
41
 
42
42
  | Stack | Primary | Runner-up | Rationale |
43
- |---|---|---|---|
43
+ | --- | --- | --- | --- |
44
44
  | Electron | **Playwright** | WebdriverIO + Electron service | Playwright has first-party Electron support. |
45
45
  | Tauri | **WebdriverIO** + Tauri driver | — | Official path. |
46
46
 
47
47
  ### Backend-rendered / non-JS web apps
48
48
 
49
49
  | Stack | Primary | Runner-up | Rationale |
50
- |---|---|---|---|
50
+ | --- | --- | --- | --- |
51
51
  | Python (Django, Flask, FastAPI with templates) | **pytest-playwright** | Selenium + pytest | Same Playwright engine, idiomatic for Python test suites. |
52
52
  | Ruby on Rails | **Capybara + Cuprite** | Capybara + Selenium | Cuprite uses CDP, faster than Selenium. |
53
53
  | Java (Spring etc.) | **Playwright for Java** | Selenium + JUnit/TestNG | Playwright Java is mature; Selenium still dominant in enterprise Java. |
@@ -67,7 +67,7 @@ Recommend a primary, mention the runner-up, get user confirmation before install
67
67
  Only add visual regression if the user asked for it or the originating issue is visually significant. Default off for greenfield bootstrap unless explicitly requested.
68
68
 
69
69
  | Need | Recommendation | Notes |
70
- |---|---|---|
70
+ | --- | --- | --- |
71
71
  | Default for Playwright | **`toHaveScreenshot()` built-in** | Zero extra deps; baselines stored in repo. Configure `threshold` and `maxDiffPixels`. |
72
72
  | Default for Cypress | **`cypress-image-snapshot`** | Or `cypress-visual-regression`. Both store baselines in repo. |
73
73
  | Default for WebdriverIO | **`@wdio/visual-service`** | Maintained, image-comparison-based. |
@@ -7,7 +7,7 @@ Used by both `e2e-test-engineer` (when filing defects) and `governance-doc-autho
7
7
  Every `incident_report` evidence row closes `ISO29119.3.5.4` (baseline). Additional clauses depend on the incident's scope.
8
8
 
9
9
  | Defect / incident characteristic | Frameworks/clauses attributed |
10
- |---|---|
10
+ | --- | --- |
11
11
  | **Any test failure / defect** (baseline — always) | `ISO29119.3.5.4` Test incident report |
12
12
  | **Ops impact** (downtime, persistent errors, perf regression, data corruption) | + `SOC2.CC7.2` System monitoring and incident response |
13
13
  | **Security vulnerability** (auth bypass, injection, data exposure beyond GDPR scope) | + `SOC2.CC7.2` + relevant ISO 27001 controls |
@@ -12,7 +12,7 @@ The skill is **Phase A scope** (per [DevAudit-Installer#119](https://github.com/
12
12
  ## What this skill owns
13
13
 
14
14
  | Artefact | Lives at | Tier |
15
- |---|---|---|
15
+ | --- | --- | --- |
16
16
  | `docs/SRS.md` (the SoT, project-spanning) | Top-level project docs | 2 (project strategy) |
17
17
  | `compliance/evidence/REQ-XXX/srs-alignment.md` (per-REQ Tier 3 evidence) | Per-REQ evidence directory | 3 (per-REQ) |
18
18
 
@@ -65,7 +65,7 @@ Input: the REQ's `compliance/plans/REQ-XXX/implementation-plan.md` plus the work
65
65
  **Step 3 — Categorise each AC:**
66
66
 
67
67
  | AC ⇒ SRS state | Action |
68
- |---|---|
68
+ | --- | --- |
69
69
  | **Exact match** — AC traces 1:1 to an existing SRS item, no behavioural delta | Record the mapping; no SRS edit needed |
70
70
  | **Match + drift** — existing SRS item covers the AC's surface but the behaviour has shifted (e.g. new field, new edge case, new error path) | Flag the item as *potentially stale*; the plan must mark it for update in this cycle OR justify why the SRS prose still covers it |
71
71
  | **No match** — AC introduces behaviour the SRS doesn't yet describe | Propose new `REQ-AREA-NNN` (next free ID per area — see Step 4) with a Given/When/Then stub the operator edits |
@@ -79,7 +79,7 @@ Input: the REQ's `compliance/plans/REQ-XXX/implementation-plan.md` plus the work
79
79
  ## SRS items proposed/touched
80
80
 
81
81
  | AC | SRS item | Status | Notes |
82
- |---|---|---|---|
82
+ | --- | --- | --- | --- |
83
83
  | AC1 | REQ-ORDER-005 (existing) | unchanged | Trace-only |
84
84
  | AC2 | REQ-INV-010 (new — proposed) | stub | <one-line behaviour description> |
85
85
  | AC3 | REQ-INV-011 (new — proposed) | stub | <one-line> |
@@ -113,7 +113,7 @@ generated_at: <ISO timestamp>
113
113
  ## ACs traced
114
114
 
115
115
  | AC | SRS item | Action this cycle |
116
- |---|---|---|
116
+ | --- | --- | --- |
117
117
  | AC1 | REQ-ORDER-005 | unchanged |
118
118
  | AC2 | REQ-INV-010 | added (new — see Phase 1 stub) |
119
119
  | AC3 | REQ-INV-011 | added (new) |
@@ -385,7 +385,7 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
385
385
  - **`requirements-aligner` fails** (e.g. no `docs/SRS.md`): If `block_on_stage_1` is true, halt — "requirements-aligner could not find SRS.md. Operator action — create `docs/SRS.md` or disable `requirements_aligner.block_on_stage_1` in `sdlc-config.json`." If false, warn and continue with `@srs-deferred` on all ACs.
386
386
  - **`adr-author` fails**: Warn and continue — ADR is advisory by default. Mark the plan's "Architecture decisions" section as "ADR assessment skipped — <error>".
387
387
  - **`risk-register-keeper` fails**: If `block_on_stage_1` is true, halt. If false, warn and continue — mark the plan's "Risk register entries" section as "Risk assessment skipped — <error>".
388
- 9. **Update `compliance/RTM.md`** with the new entry: REQ-XXX, title, risk class, linked issue, linked test cases (placeholder), and a provenance marker `sdlc-implementer@<version>` (devaudit-installer#226). The provenance column is the last column in the RTM row — `validate-commits.sh` in CI checks for its presence when `feat`/`fix`/`refactor`/`perf` commits cite a REQ-XXX. Without the stamp, CI fails with "no sdlc-implementer provenance in RTM.md." If the RTM table doesn't have a provenance column, add one with header `Provenance`.
388
+ 9. **Update `compliance/RTM.md`** with the new entry: REQ-XXX, title, risk class, linked issue, linked test cases (placeholder), and a provenance marker `sdlc-implementer@<version>` (devaudit-installer#226). The provenance column is the last column in the RTM row — `validate-commits.sh` in CI checks for its presence when `feat`/`fix`/`refactor`/`perf` commits cite a REQ-XXX. Without the stamp, CI fails with "no sdlc-implementer provenance in RTM.md." If the RTM table doesn't have a provenance column, add one with header `Provenance`. If any RTM cell text needs literal pipe characters (regexes, enum examples, status notes), escape them as `\|` rather than writing bare `|`, otherwise markdownlint will misparse the row as extra columns.
389
389
  10. **Post plan summary as an issue comment.** Format: TL;DR; Risk class + signals; Acceptance criteria (with SRS-IDs); Architectural decisions (ADR-NNN reference or no-ADR rationale); Risk register entries (RISK-NNN list); Technical approach (one paragraph); Dependencies; Test scope.
390
390
  11. **Checkpoint** — pause for human approval **iff** risk class is HIGH or CRITICAL. LOW and MEDIUM pass through to Phase 2 automatically. The checkpoint can be forced on for all classes via the `--require-plan-approval` flag (or `DEVAUDIT_REQUIRE_PLAN_APPROVAL=1` env var) for orgs that want it always-on.
391
391
  12. **Update SDLC status sticky** before exiting Phase 1: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 1 complete — plan written to compliance/plans/REQ-XXX/implementation-plan.md (risk class <CLASS>)" "Phase 2 — sdlc-implementer auto-continuing"` (or "Operator action — review plan + ping resume" if the HIGH/CRITICAL checkpoint paused).
@@ -511,6 +511,23 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
511
511
 
512
512
  If the validator fails, fix the summary before proceeding. E2E gate results must be one of: `PASS`, `FAIL`, `NOT_NEEDED` (with reason), or `SKIPPED` (with operator-approved rationale). The word "deferred" must never appear in `test-execution-summary.md` — not as a gate state, not in prose, not in final assessment. "Deferred to CI" and "Playwright browsers not installed locally" are environment issues, not gate states. The CI validator (`validate-test-summary.sh`) will reject any summary containing "deferred" or "browsers not installed" on PRs to main.
513
513
 
514
+ 5bb. **Render the Test Cycles table from the portal's cycle read model when available (devaudit-installer#394).** Do not hand-build cycle rows from uploaded artefacts if the portal exposes first-class cycle records. Query the release-journey / cycle API, save the JSON, and render the markdown table with:
515
+
516
+ ```bash
517
+ bash scripts/render-test-cycles.sh /tmp/release-journey.json
518
+ ```
519
+
520
+ First-class rows must preserve:
521
+ - source release
522
+ - SDLC stage
523
+ - cycle ordinal within that source release + stage
524
+ - cycle kind / outcome
525
+ - workflow or run link
526
+ - related evidence
527
+ - incident or remediation reference
528
+
529
+ If the portal does not yet expose first-class cycles, the same helper emits the legacy `testCycleId` grouping fallback and labels it as such. Keep the fallback note in the final `test-execution-summary.md`; it is part of the audit trail during rollout.
530
+
514
531
  5c. **Run the Phase 3 completion guard before any PR/release-review step (devaudit-installer#341).** The tracked-release evidence pack is incomplete until the required Git artefacts exist. Before proceeding past Phase 3, verify:
515
532
 
516
533
  - `compliance/evidence/REQ-XXX/test-scope.md`
@@ -520,6 +537,7 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
520
537
  - `compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md`
521
538
  - `compliance/evidence/REQ-XXX/implementation-plan.md` when the REQ risk class requires it
522
539
  - any already-mandatory AI artefacts required by the risk/usage rules
540
+ - if `compliance/pending-releases/BUNDLED-CHANGES-REQ-XXX.md` exists, regenerate the paired JSON manifest too and keep both aligned with the current approval scope
523
541
  - if `compliance/pending-releases/BUNDLED-CHANGES-REQ-XXX.md` exists, the canonical artefacts must carry the bundle narrative too:
524
542
  - release ticket includes `## Bundled Changes` or `## Absorbed Predecessor Releases`
525
543
  - `test-execution-summary.md` includes `## Bundled Release Context`
@@ -636,7 +654,7 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
636
654
  --base-url <devaudit-base-url>
637
655
  ```
638
656
 
639
- The watcher persists retry state in `.sdlc-pr-watch.json`, polls `gh pr view` + `gh pr checks`, re-runs likely flaky workflows automatically, and re-runs the Release Approval Gate when the portal is already approved but GitHub has not converged yet. Use `--once` when you only need a single classification pass.
657
+ The watcher persists retry state in `.sdlc-pr-watch.json`, polls `gh pr view` + `gh pr checks`, re-runs likely flaky workflows automatically, and re-runs the Release Approval Gate when the portal is already approved but GitHub has not converged yet. For a release PR (`$INTEGRATION_BRANCH -> $RELEASE_BRANCH`), it must not call the PR green until the full required release check set has reached terminal success: `Quality Gates`, `Release Scope Integrity`, `Compliance Validation`, `DevAudit Release Approval`, and `E2E Regression Suite`. Use `--once` when you only need a single classification pass.
640
658
 
641
659
  6. **Hard stop only after blocker classification.** Phase 4 ends here. Do not proceed to merge; the human's next action is reviewing on the portal or clearing the categorized blocker above.
642
660
  7. **Update SDLC status sticky** before halting: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 4 — truthful release PR #<N> prepared against $RELEASE_BRANCH; blockers classified" "Operator action — review PR #<N> + approve UAT release on the portal; sdlc-implementer halts until you ping resume REQ-XXX"`. This is a critical handoff — the sticky must reflect that the agent has stopped + the operator is on the hook.
@@ -675,6 +693,7 @@ Invoked separately by the user after UAT activity on the portal. Trigger: "resum
675
693
  - `gh pr merge <M> --merge` (merge commit; `--squash` and `--rebase` are blocked by branch protection on SDLC repos and would break the audit trail).
676
694
  - **Before watching `post-deploy-prod.yml`**, verify the workflow exists and has triggered: `gh run list --workflow post-deploy-prod.yml --limit 1 --json status,conclusion`. If no run appears within 5 minutes of the merge, halt — "post-deploy-prod.yml did not trigger after merge. Check workflow configuration, secrets, and deployment platform. Operator action — run `gh workflow run post-deploy-prod.yml` manually, then ping `resume REQ-XXX`."
677
695
  - Watch `post-deploy-prod.yml` via `gh run watch` — block until the workflow reaches a terminal state. Wrap in a timeout (30 minutes default, configurable via `sdlc-config.json:post_deploy.timeout_minutes`). If the timeout fires: halt — "post-deploy-prod.yml has not reached a terminal state in N minutes. The deployment may be stuck. Operator action — check hosting platform logs, decide whether to wait or rollback, then ping `resume REQ-XXX`."
696
+ - Treat `post-deploy-prod.yml` success as necessary but not sufficient. The workflow must also confirm the hosting platform's GitHub deployment status for the merged SHA reached terminal `success`. If the host deployment is still `queued` / `in_progress`, remain blocked. If it reaches `failure`, halt — "The host deployment for <sha> failed after merge. Do not approve Production or mark Released. Operator action — inspect hosting logs / rollback / resume once the production state is understood."
678
697
  - Verify production smoke evidence uploaded (`--environment production`) at `https://devaudit.metasession.co/projects/<slug>/releases/<version>`.
679
698
  - **Read `production_review.terminal_status` from `sdlc-config.json`** (default: `prod_review`). Branch:
680
699
  - If `released` (Option B): PATCH directly to `released` — `PATCH /releases/<version>` with `{"status": "released"}`.
@@ -193,7 +193,7 @@ If the portal state is `uat_rejected` (not `uat_changes_requested`), the reviewe
193
193
  The release ticket (`compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md`) `**Status:**` field is updated at each transition in the change-request loop, mirroring the portal's state machine for audit trail completeness:
194
194
 
195
195
  | Transition | Release ticket status |
196
- |---|---|
196
+ | --- | --- |
197
197
  | Entering iteration N | `CHANGES REQUESTED — ITERATION N` |
198
198
  | After evidence re-compile (Step 5) | `TESTED - PENDING SIGN-OFF (ITERATION N)` |
199
199
  | After UAT re-approval (Phase 5 happy path) | `APPROVED — READY FOR MERGE` |