@metasession.co/devaudit-cli 0.1.1 → 0.1.3
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/README.md +13 -10
- package/dist/index.js +17 -5
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
- package/scripts/upload-evidence.sh +225 -0
- package/sdlc/CLAUDE.md +73 -0
- package/sdlc/HOST_ADAPTER.md +127 -0
- package/sdlc/SKILLS.md +137 -0
- package/sdlc/STACK_ADAPTER.md +130 -0
- package/sdlc/ai-rules/INSTRUCTIONS-SDLC.md +172 -0
- package/sdlc/ai-rules/README.md +103 -0
- package/sdlc/ai-rules/SDLC_RULES.md +584 -0
- package/sdlc/ai-rules/claude/CLAUDE.md +192 -0
- package/sdlc/ai-rules/cursor/.cursorrules +167 -0
- package/sdlc/ai-rules/windsurf/.windsurfrules +167 -0
- package/sdlc/article.md +219 -0
- package/sdlc/files/_common/0-project-setup.md +410 -0
- package/sdlc/files/_common/1-plan-requirement.md +381 -0
- package/sdlc/files/_common/2-implement-and-test.md +276 -0
- package/sdlc/files/_common/3-compile-evidence.md +603 -0
- package/sdlc/files/_common/4-submit-for-review.md +362 -0
- package/sdlc/files/_common/5-deploy-main.md +251 -0
- package/sdlc/files/_common/Periodic_Security_Review_Schedule.md +169 -0
- package/sdlc/files/_common/README_TEMPLATE.md +441 -0
- package/sdlc/files/_common/Test_Architecture.md +461 -0
- package/sdlc/files/_common/Test_Plan_TEMPLATE.md +311 -0
- package/sdlc/files/_common/Test_Policy.md +277 -0
- package/sdlc/files/_common/Test_Strategy.md +359 -0
- package/sdlc/files/_common/github/ISSUE_TEMPLATE/bug.yml +75 -0
- package/sdlc/files/_common/github/ISSUE_TEMPLATE/config.yml +11 -0
- package/sdlc/files/_common/github/ISSUE_TEMPLATE/requirement.yml +75 -0
- package/sdlc/files/_common/github/ISSUE_TEMPLATE/task.yml +48 -0
- package/sdlc/files/_common/github/pull_request_template.md +69 -0
- package/sdlc/files/_common/implementing-an-sdlc-issue.md +413 -0
- package/sdlc/files/_common/scripts/derive-release-version.sh +40 -0
- package/sdlc/files/_common/scripts/derive-release-version.test.sh +98 -0
- package/sdlc/files/_common/scripts/submit-for-uat-review.sh +162 -0
- package/sdlc/files/_common/scripts/validate-commits.sh +83 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +202 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.test.sh +202 -0
- package/sdlc/files/_common/skills/_schema/skill.schema.json +36 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +254 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/references/bootstrap.md +244 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/references/evidence.ts +40 -0
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +189 -0
- package/sdlc/files/_common/skills/sdlc-implementer/references/call-graph.md +64 -0
- package/sdlc/files/_common/skills/sdlc-implementer/references/change-request-loop.md +192 -0
- package/sdlc/files/_common/skills/sdlc-implementer/references/compliance-constraints.md +81 -0
- package/sdlc/files/ci/check-release-approval.yml.template +201 -0
- package/sdlc/files/ci/ci-status-fallback.yml.template +41 -0
- package/sdlc/files/ci/ci.yml.template +390 -0
- package/sdlc/files/ci/compliance-evidence.yml.template +161 -0
- package/sdlc/files/ci/compliance-validation.yml.template +34 -0
- package/sdlc/files/ci/post-deploy-prod.yml.template +159 -0
- package/sdlc/files/ci/python/ci.yml.template +335 -0
- package/sdlc/files/hosts/_schema/adapter.schema.json +103 -0
- package/sdlc/files/hosts/railway/adapter.json +32 -0
- package/sdlc/files/sdlc-config.example.json +74 -0
- package/sdlc/files/stacks/_schema/adapter.schema.json +151 -0
- package/sdlc/files/stacks/node/adapter.json +54 -0
- package/sdlc/files/stacks/node/hooks/.prettierrc.json +9 -0
- package/sdlc/files/stacks/node/hooks/commit-msg +7 -0
- package/sdlc/files/stacks/node/hooks/commitlint.config.mjs +64 -0
- package/sdlc/files/stacks/node/hooks/lint-staged.config.mjs +16 -0
- package/sdlc/files/stacks/node/hooks/pre-commit +13 -0
- package/sdlc/files/stacks/node/hooks/pre-push +15 -0
- package/sdlc/files/stacks/node/scripts/check-requirement-jsdoc.sh +54 -0
- package/sdlc/files/stacks/python/adapter.json +36 -0
- package/sdlc/files/stacks/python/hooks/.pre-commit-config.yaml +51 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Compliance Evidence Upload — triggers on compliance-only changes
|
|
2
|
+
#
|
|
3
|
+
# Generated by `devaudit install` / `devaudit update` from sdlc-config.json.
|
|
4
|
+
# Do not edit manually — re-run the CLI (`devaudit update`) to regenerate.
|
|
5
|
+
#
|
|
6
|
+
# Uploads compliance docs to DevAudit without running quality gates.
|
|
7
|
+
# Complements ci.yml which skips compliance-only pushes via paths-ignore.
|
|
8
|
+
# In sdlc-v1.22.0+ this workflow is the load-bearing path for Stage 3
|
|
9
|
+
# Step 9 (push-early): every commit on develop that touches compliance/
|
|
10
|
+
# fires this and uploads to DevAudit immediately so destination breakage
|
|
11
|
+
# (dead alias, missing token, schema drift) surfaces in seconds, not at
|
|
12
|
+
# the end of Stage 3.
|
|
13
|
+
#
|
|
14
|
+
# Auth: project-scoped API key (uploader role). Issue from
|
|
15
|
+
# DevAudit → Project Settings → API Keys. Set as repo Secret
|
|
16
|
+
# DEVAUDIT_API_KEY. Base URL is read from sdlc-config.json
|
|
17
|
+
# devaudit.base_url; falls back to repo Variable DEVAUDIT_BASE_URL
|
|
18
|
+
# (deprecated, removed in v1.23.0).
|
|
19
|
+
|
|
20
|
+
name: Compliance Evidence Upload
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
push:
|
|
24
|
+
branches: [develop]
|
|
25
|
+
paths:
|
|
26
|
+
- 'compliance/**'
|
|
27
|
+
|
|
28
|
+
concurrency:
|
|
29
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
30
|
+
cancel-in-progress: true
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
upload-compliance-evidence:
|
|
34
|
+
name: Upload Compliance Evidence
|
|
35
|
+
runs-on: {{RUNNER}}
|
|
36
|
+
env:
|
|
37
|
+
DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
|
|
38
|
+
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- name: Resolve DevAudit base URL
|
|
43
|
+
id: resolve
|
|
44
|
+
run: |
|
|
45
|
+
# Prefer sdlc-config.json (visible in PR review) over repo Variable.
|
|
46
|
+
CONFIG_URL=""
|
|
47
|
+
if [ -f sdlc-config.json ]; then
|
|
48
|
+
CONFIG_URL=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
|
|
49
|
+
fi
|
|
50
|
+
if [ -n "$CONFIG_URL" ]; then
|
|
51
|
+
BASE="$CONFIG_URL"
|
|
52
|
+
echo "Using devaudit.base_url from sdlc-config.json: $BASE"
|
|
53
|
+
elif [ -n "$DEVAUDIT_BASE_URL_VAR" ]; then
|
|
54
|
+
BASE="$DEVAUDIT_BASE_URL_VAR"
|
|
55
|
+
echo "::warning::Using repo Variable DEVAUDIT_BASE_URL (deprecated in v1.23.0). Move base_url to sdlc-config.json devaudit.base_url for PR-visible config."
|
|
56
|
+
else
|
|
57
|
+
echo "::warning::No DevAudit base URL configured — skipping evidence upload. Set devaudit.base_url in sdlc-config.json."
|
|
58
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
if [ -z "${DEVAUDIT_API_KEY}" ]; then
|
|
62
|
+
echo "::warning::DEVAUDIT_API_KEY not set — skipping evidence upload."
|
|
63
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
# Pre-flight: confirm destination is reachable. Catches dead aliases / wrong hosts early.
|
|
67
|
+
CODE=$(curl -s -o /dev/null -w "%{http_code}" -m 10 -I "${BASE%/}/" || echo "000")
|
|
68
|
+
case "$CODE" in
|
|
69
|
+
2*|3*)
|
|
70
|
+
echo "DevAudit reachable at ${BASE} (HTTP ${CODE})"
|
|
71
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
72
|
+
echo "BASE=${BASE%/}" >> "$GITHUB_ENV"
|
|
73
|
+
# Export for upload-evidence.sh, which reads $DEVAUDIT_BASE_URL directly.
|
|
74
|
+
echo "DEVAUDIT_BASE_URL=${BASE%/}" >> "$GITHUB_ENV"
|
|
75
|
+
;;
|
|
76
|
+
*)
|
|
77
|
+
echo "::error::DevAudit base URL ${BASE} returned HTTP ${CODE}. Likely a stale alias or wrong host. Check sdlc-config.json devaudit.base_url."
|
|
78
|
+
exit 1
|
|
79
|
+
;;
|
|
80
|
+
esac
|
|
81
|
+
|
|
82
|
+
- name: Determine release version
|
|
83
|
+
id: version
|
|
84
|
+
if: steps.resolve.outputs.skip != 'true'
|
|
85
|
+
run: |
|
|
86
|
+
# Release identity is the REQ tag on the latest commit
|
|
87
|
+
# ([REQ-XXX] in subject, Ref: REQ-XXX in body), with a bare-date
|
|
88
|
+
# fallback for housekeeping commits. Both this workflow and
|
|
89
|
+
# ci.yml call the same helper so a single feature converges on
|
|
90
|
+
# one release record regardless of whether the push touched
|
|
91
|
+
# code or docs. See DevAudit #310.
|
|
92
|
+
chmod +x scripts/derive-release-version.sh 2>/dev/null || true
|
|
93
|
+
VERSION=$(./scripts/derive-release-version.sh)
|
|
94
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
95
|
+
echo "Release version: ${VERSION}"
|
|
96
|
+
|
|
97
|
+
- name: Upload compliance documents
|
|
98
|
+
if: steps.resolve.outputs.skip != 'true'
|
|
99
|
+
run: |
|
|
100
|
+
chmod +x scripts/upload-evidence.sh 2>/dev/null || true
|
|
101
|
+
FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
|
|
102
|
+
FLAGS="${FLAGS} --release ${{ steps.version.outputs.version }} --create-release-if-missing"
|
|
103
|
+
FLAGS="${FLAGS} --environment uat"
|
|
104
|
+
|
|
105
|
+
# Upload compliance docs (planning category)
|
|
106
|
+
for DOC in compliance/RTM.md compliance/test-plan.md compliance/test-cases.md compliance/test-summary-report.md; do
|
|
107
|
+
if [ -f "$DOC" ]; then
|
|
108
|
+
echo "Uploading: $(basename "$DOC")"
|
|
109
|
+
bash scripts/upload-evidence.sh \
|
|
110
|
+
{{PROJECT_SLUG}} _compliance-docs compliance_document "$DOC" \
|
|
111
|
+
--category planning ${FLAGS} || echo "Warning: Failed to upload $(basename "$DOC")"
|
|
112
|
+
fi
|
|
113
|
+
done
|
|
114
|
+
|
|
115
|
+
# Upload release tickets (pending only)
|
|
116
|
+
if [ -d "compliance/pending-releases" ]; then
|
|
117
|
+
for TICKET in compliance/pending-releases/*.md; do
|
|
118
|
+
[ -f "$TICKET" ] || continue
|
|
119
|
+
echo "Uploading: $(basename "$TICKET")"
|
|
120
|
+
bash scripts/upload-evidence.sh \
|
|
121
|
+
{{PROJECT_SLUG}} _compliance-docs compliance_document "$TICKET" \
|
|
122
|
+
--category release_artifact ${FLAGS} || echo "Warning: Failed to upload $(basename "$TICKET")"
|
|
123
|
+
done
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# Upload per-requirement evidence — scoped to requirements with a
|
|
127
|
+
# pending release ticket. Without this scoping every historical
|
|
128
|
+
# compliance/evidence/REQ-*/ folder would be re-uploaded on every
|
|
129
|
+
# run, re-populating the release-requirement matrix with the full
|
|
130
|
+
# project catalogue (DevAudit #135, sibling of #133).
|
|
131
|
+
IN_SCOPE_REQS=()
|
|
132
|
+
if [ -d compliance/pending-releases ]; then
|
|
133
|
+
for TICKET in compliance/pending-releases/RELEASE-TICKET-REQ-*.md; do
|
|
134
|
+
[ -f "$TICKET" ] || continue
|
|
135
|
+
REQ_ID=$(basename "$TICKET" .md | sed 's/^RELEASE-TICKET-//')
|
|
136
|
+
IN_SCOPE_REQS+=("$REQ_ID")
|
|
137
|
+
done
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
if [ ${#IN_SCOPE_REQS[@]} -eq 0 ]; then
|
|
141
|
+
echo "No pending release tickets found — skipping per-requirement evidence upload"
|
|
142
|
+
else
|
|
143
|
+
echo "In-scope requirements for this release: ${IN_SCOPE_REQS[*]}"
|
|
144
|
+
for REQ_ID in "${IN_SCOPE_REQS[@]}"; do
|
|
145
|
+
REQ_DIR="compliance/evidence/${REQ_ID}/"
|
|
146
|
+
if [ ! -d "$REQ_DIR" ]; then
|
|
147
|
+
echo "Warning: pending ticket for ${REQ_ID} but no ${REQ_DIR} on disk"
|
|
148
|
+
continue
|
|
149
|
+
fi
|
|
150
|
+
for ARTIFACT in "$REQ_DIR"*.md; do
|
|
151
|
+
[ -f "$ARTIFACT" ] || continue
|
|
152
|
+
echo "Uploading: ${REQ_ID}/$(basename "$ARTIFACT")"
|
|
153
|
+
bash scripts/upload-evidence.sh \
|
|
154
|
+
{{PROJECT_SLUG}} "${REQ_ID}" compliance_document "$ARTIFACT" \
|
|
155
|
+
--category planning ${FLAGS} || echo "Warning: Failed to upload $(basename "$ARTIFACT")"
|
|
156
|
+
done
|
|
157
|
+
done
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
- name: Summary
|
|
161
|
+
run: echo "Compliance evidence uploaded for ${{ steps.version.outputs.version }}"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Compliance Validation — runs on PRs to main only
|
|
2
|
+
#
|
|
3
|
+
# Generated by `devaudit install` / `devaudit update` from sdlc-config.json.
|
|
4
|
+
# Do not edit manually — re-run the CLI (`devaudit update`) to regenerate.
|
|
5
|
+
#
|
|
6
|
+
# Validates that compliance artifacts (test-scope, test-plan, RTM entries,
|
|
7
|
+
# release tickets) are complete before merging to main. These artifacts are
|
|
8
|
+
# built up progressively during development, so this check only makes sense
|
|
9
|
+
# when the work is ready to merge — not on every push to develop.
|
|
10
|
+
|
|
11
|
+
name: Compliance Validation
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
pull_request:
|
|
15
|
+
branches: [main]
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
compliance-validation:
|
|
23
|
+
name: Compliance Validation
|
|
24
|
+
runs-on: {{RUNNER}}
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
with:
|
|
28
|
+
fetch-depth: 0
|
|
29
|
+
|
|
30
|
+
- name: Validate compliance artifacts
|
|
31
|
+
run: bash scripts/validate-compliance-artifacts.sh origin/main
|
|
32
|
+
|
|
33
|
+
- name: Validate commit conventions
|
|
34
|
+
run: bash scripts/validate-commits.sh origin/main
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Post-deploy production verification (read-only)
|
|
2
|
+
#
|
|
3
|
+
# Generated by `devaudit install` / `devaudit update` from sdlc-config.json.
|
|
4
|
+
# Do not edit manually — re-run the CLI (`devaudit update`) to regenerate.
|
|
5
|
+
#
|
|
6
|
+
# Production verification is READ-ONLY.
|
|
7
|
+
# No E2E tests, no database operations, no API mutations.
|
|
8
|
+
#
|
|
9
|
+
# In sdlc-v1.22.0+ the terminal release status is configurable via
|
|
10
|
+
# sdlc-config.json `production_review.terminal_status`:
|
|
11
|
+
# - "prod_review" (default, Option A) — stop at prod_review; human in the
|
|
12
|
+
# portal clicks "Approve Production" then "Mark as Released" for an
|
|
13
|
+
# explicit audit trail. Closes #138.
|
|
14
|
+
# - "released" (Option B) — preserves v1.21.x auto-release behaviour.
|
|
15
|
+
|
|
16
|
+
name: Post-Deploy Production Evidence
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
branches: [main]
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
production-evidence:
|
|
24
|
+
name: Production Evidence
|
|
25
|
+
runs-on: {{RUNNER}}
|
|
26
|
+
env:
|
|
27
|
+
DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
|
|
28
|
+
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
29
|
+
PROD_URL: ${{ secrets.{{PRODUCTION_URL_SECRET}} }}
|
|
30
|
+
PROJECT_SLUG: {{PROJECT_SLUG}}
|
|
31
|
+
GIT_SHA: ${{ github.sha }}
|
|
32
|
+
CI_RUN: ${{ github.run_id }}
|
|
33
|
+
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
|
|
37
|
+
- name: Resolve DevAudit base URL and post-deploy terminal status
|
|
38
|
+
run: |
|
|
39
|
+
# Prefer sdlc-config.json (visible in PR review) over repo Variable.
|
|
40
|
+
CONFIG_URL=""
|
|
41
|
+
TERMINAL_STATUS="prod_review"
|
|
42
|
+
if [ -f sdlc-config.json ]; then
|
|
43
|
+
CONFIG_URL=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
|
|
44
|
+
CONFIG_TERMINAL=$(jq -r '.production_review.terminal_status // empty' sdlc-config.json 2>/dev/null || true)
|
|
45
|
+
if [ -n "$CONFIG_TERMINAL" ]; then
|
|
46
|
+
TERMINAL_STATUS="$CONFIG_TERMINAL"
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
if [ -n "$CONFIG_URL" ]; then
|
|
50
|
+
BASE="$CONFIG_URL"
|
|
51
|
+
echo "Using devaudit.base_url from sdlc-config.json: $BASE"
|
|
52
|
+
elif [ -n "$DEVAUDIT_BASE_URL_VAR" ]; then
|
|
53
|
+
BASE="$DEVAUDIT_BASE_URL_VAR"
|
|
54
|
+
echo "::warning::Using repo Variable DEVAUDIT_BASE_URL (deprecated in v1.23.0). Move base_url to sdlc-config.json devaudit.base_url."
|
|
55
|
+
else
|
|
56
|
+
echo "::error::No DevAudit base URL configured. Set devaudit.base_url in sdlc-config.json."
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
if [ -z "${DEVAUDIT_API_KEY}" ]; then
|
|
60
|
+
echo "::error::DEVAUDIT_API_KEY secret must be set."
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
case "$TERMINAL_STATUS" in
|
|
64
|
+
prod_review|released) ;;
|
|
65
|
+
*)
|
|
66
|
+
echo "::error::Invalid production_review.terminal_status '${TERMINAL_STATUS}'. Must be 'prod_review' or 'released'."
|
|
67
|
+
exit 1
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
echo "Post-deploy terminal status: ${TERMINAL_STATUS}"
|
|
71
|
+
echo "BASE=${BASE%/}" >> "$GITHUB_ENV"
|
|
72
|
+
# Export for upload-evidence.sh, which reads $DEVAUDIT_BASE_URL directly.
|
|
73
|
+
echo "DEVAUDIT_BASE_URL=${BASE%/}" >> "$GITHUB_ENV"
|
|
74
|
+
echo "TERMINAL_STATUS=${TERMINAL_STATUS}" >> "$GITHUB_ENV"
|
|
75
|
+
|
|
76
|
+
- name: Resolve current release
|
|
77
|
+
id: release
|
|
78
|
+
run: |
|
|
79
|
+
DATE_PREFIX="v$(date +%Y.%m.%d)"
|
|
80
|
+
RESP=$(curl -s -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
81
|
+
"${BASE}/api/ci/releases/resolve?projectSlug=${PROJECT_SLUG}&versionPrefix=${DATE_PREFIX}")
|
|
82
|
+
VERSION=$(echo "$RESP" | jq -r '.latest.version // empty')
|
|
83
|
+
if [ -z "$VERSION" ]; then
|
|
84
|
+
VERSION="${DATE_PREFIX}"
|
|
85
|
+
fi
|
|
86
|
+
RELEASE_ID=$(echo "$RESP" | jq -r '.latest.id // empty')
|
|
87
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
88
|
+
echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
|
|
89
|
+
echo "Release version: ${VERSION}"
|
|
90
|
+
|
|
91
|
+
- name: Wait for production deployment
|
|
92
|
+
run: |
|
|
93
|
+
for i in $(seq 1 30); do
|
|
94
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${PROD_URL}/" || echo "000")
|
|
95
|
+
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 400 ]; then
|
|
96
|
+
echo "Production is up (HTTP ${HTTP_CODE})"
|
|
97
|
+
break
|
|
98
|
+
fi
|
|
99
|
+
echo "Attempt ${i}/30: HTTP ${HTTP_CODE} — waiting 10s..."
|
|
100
|
+
sleep 10
|
|
101
|
+
done
|
|
102
|
+
|
|
103
|
+
- name: Production smoke tests (read-only)
|
|
104
|
+
run: |
|
|
105
|
+
echo "=== Production Smoke Tests ==="
|
|
106
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${PROD_URL}/")
|
|
107
|
+
echo "Health check: HTTP ${HTTP_CODE}"
|
|
108
|
+
if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 400 ]; then
|
|
109
|
+
echo "::error::Production health check failed"
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
curl -s -I "${PROD_URL}/" | grep -iE 'x-frame-options|x-content-type|strict-transport|content-security' || true
|
|
113
|
+
echo "=== Smoke Tests Passed ==="
|
|
114
|
+
cat > prod-smoke-results.json << RESULTS_EOF
|
|
115
|
+
{
|
|
116
|
+
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
117
|
+
"production_url": "${PROD_URL}",
|
|
118
|
+
"git_sha": "${GIT_SHA}",
|
|
119
|
+
"tests": [
|
|
120
|
+
{"name": "health_check", "status": "passed"},
|
|
121
|
+
{"name": "security_headers", "status": "checked"}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
RESULTS_EOF
|
|
125
|
+
|
|
126
|
+
- name: Upload production evidence
|
|
127
|
+
run: |
|
|
128
|
+
chmod +x scripts/upload-evidence.sh 2>/dev/null || true
|
|
129
|
+
VERSION="${{ steps.release.outputs.version }}"
|
|
130
|
+
FLAGS="--release ${VERSION} --create-release-if-missing --environment production --category test_report"
|
|
131
|
+
FLAGS="${FLAGS} --git-sha ${GIT_SHA} --ci-run-id ${CI_RUN} --branch main"
|
|
132
|
+
if [ -f prod-smoke-results.json ]; then
|
|
133
|
+
bash scripts/upload-evidence.sh \
|
|
134
|
+
"${PROJECT_SLUG}" "_compliance-docs" "test_report" prod-smoke-results.json \
|
|
135
|
+
${FLAGS} || echo "Warning: Failed to upload smoke results"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
- name: Advance release status (post-deploy)
|
|
139
|
+
run: |
|
|
140
|
+
RELEASE_ID="${{ steps.release.outputs.release_id }}"
|
|
141
|
+
if [ -z "$RELEASE_ID" ]; then
|
|
142
|
+
echo "::warning::No release_id resolved — skipping status patch"
|
|
143
|
+
exit 0
|
|
144
|
+
fi
|
|
145
|
+
curl -s -o /dev/null -w "Release status patch: HTTP %{http_code}\n" \
|
|
146
|
+
-X PATCH "${BASE}/api/ci/releases/${RELEASE_ID}" \
|
|
147
|
+
-H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
148
|
+
-H "Content-Type: application/json" \
|
|
149
|
+
-d "{\"status\":\"${TERMINAL_STATUS}\"}"
|
|
150
|
+
case "$TERMINAL_STATUS" in
|
|
151
|
+
prod_review)
|
|
152
|
+
echo "Release ${{ steps.release.outputs.version }} → prod_review."
|
|
153
|
+
echo "Next: a human in the portal clicks 'Approve Production' (→ prod_approved), then 'Mark as Released' (→ released)."
|
|
154
|
+
echo "Audit trail captures both events with reviewer identity per compliance/risk-register.md."
|
|
155
|
+
;;
|
|
156
|
+
released)
|
|
157
|
+
echo "Release ${{ steps.release.outputs.version }} → released (Option B: auto-release with no post-deploy human gate)."
|
|
158
|
+
;;
|
|
159
|
+
esac
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# CI Pipeline — all gates on every code push to develop (Python stack)
|
|
2
|
+
#
|
|
3
|
+
# Generated by `devaudit install` / `devaudit update` from sdlc-config.json + stacks/python/adapter.json.
|
|
4
|
+
# Do not edit manually — re-run the CLI (`devaudit update`) to regenerate.
|
|
5
|
+
#
|
|
6
|
+
# Single consolidated job. Order: install → ruff → mypy → semgrep → pip-audit → pytest → build.
|
|
7
|
+
#
|
|
8
|
+
# PRs to main inherit commit status via branch protection.
|
|
9
|
+
# Compliance validation runs separately on PRs (compliance-validation.yml).
|
|
10
|
+
|
|
11
|
+
name: CI Pipeline
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
push:
|
|
16
|
+
branches: [develop]
|
|
17
|
+
paths-ignore:
|
|
18
|
+
{{PATHS_IGNORE}} - 'sdlc-config.json'
|
|
19
|
+
|
|
20
|
+
concurrency:
|
|
21
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
22
|
+
cancel-in-progress: true
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
quality-gates:
|
|
26
|
+
name: Quality Gates
|
|
27
|
+
runs-on: {{RUNNER}}
|
|
28
|
+
|
|
29
|
+
# `working-directory` lets monorepo / subdir Python projects (e.g. META-AGENT
|
|
30
|
+
# with mission-control-api/pyproject.toml) run gates from the right place
|
|
31
|
+
# without prefixing every command. Defaults to '.' (repo root) when
|
|
32
|
+
# sdlc-config.json doesn't set working_directory.
|
|
33
|
+
defaults:
|
|
34
|
+
run:
|
|
35
|
+
working-directory: {{WORKING_DIRECTORY}}
|
|
36
|
+
|
|
37
|
+
services:
|
|
38
|
+
{{DATABASE_SERVICE}}:
|
|
39
|
+
image: {{DATABASE_IMAGE}}
|
|
40
|
+
ports:
|
|
41
|
+
- {{DATABASE_PORT}}
|
|
42
|
+
|
|
43
|
+
env:
|
|
44
|
+
{{DATABASE_ENV}}
|
|
45
|
+
{{APP_ENV}}
|
|
46
|
+
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
|
|
50
|
+
- uses: actions/setup-python@v5
|
|
51
|
+
with:
|
|
52
|
+
python-version: '{{PYTHON_VERSION}}'
|
|
53
|
+
cache: pip
|
|
54
|
+
|
|
55
|
+
- name: Install dependencies
|
|
56
|
+
run: |
|
|
57
|
+
python -m pip install --upgrade pip
|
|
58
|
+
pip install -e ".[dev]"
|
|
59
|
+
pip install semgrep pip-audit build
|
|
60
|
+
|
|
61
|
+
# ── Gate 1: Lint + format (ruff) ──
|
|
62
|
+
|
|
63
|
+
- name: Ruff lint
|
|
64
|
+
run: ruff check {{SOURCE_DIRS}}
|
|
65
|
+
|
|
66
|
+
- name: Ruff format check
|
|
67
|
+
run: ruff format --check {{SOURCE_DIRS}}
|
|
68
|
+
|
|
69
|
+
# ── Gate 2: Type check (mypy --strict) ──
|
|
70
|
+
|
|
71
|
+
- name: Type Check (mypy)
|
|
72
|
+
run: mypy {{SOURCE_DIRS}}
|
|
73
|
+
|
|
74
|
+
# ── Gate 3: SAST (Semgrep) ──
|
|
75
|
+
|
|
76
|
+
- name: SAST Scan
|
|
77
|
+
run: |
|
|
78
|
+
mkdir -p ci-evidence
|
|
79
|
+
semgrep scan --config auto {{SOURCE_DIRS}} \
|
|
80
|
+
--severity ERROR --severity WARNING \
|
|
81
|
+
--json > ci-evidence/sast-results.json 2>&1 || true
|
|
82
|
+
FINDINGS=$(python3 -c "
|
|
83
|
+
import json
|
|
84
|
+
with open('ci-evidence/sast-results.json') as f:
|
|
85
|
+
data = json.load(f)
|
|
86
|
+
print(len(data.get('results', [])))
|
|
87
|
+
" 2>/dev/null || echo "0")
|
|
88
|
+
echo "SAST findings: $FINDINGS"
|
|
89
|
+
BASELINE={{SAST_BASELINE}}
|
|
90
|
+
if [ "$FINDINGS" -gt "$BASELINE" ]; then
|
|
91
|
+
echo "::error::New SAST findings ($FINDINGS > baseline $BASELINE)."
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# ── Gate 4: Dependency Audit (pip-audit) ──
|
|
96
|
+
|
|
97
|
+
- name: Dependency Audit
|
|
98
|
+
run: |
|
|
99
|
+
mkdir -p ci-evidence
|
|
100
|
+
pip-audit --format=json > ci-evidence/dependency-audit.json 2>&1 || true
|
|
101
|
+
ACCEPTED="{{ACCEPTED_DEP_RISKS}}"
|
|
102
|
+
UNACCEPTED=$(python3 -c "
|
|
103
|
+
import json
|
|
104
|
+
with open('ci-evidence/dependency-audit.json') as f:
|
|
105
|
+
data = json.load(f)
|
|
106
|
+
accepted = set('${ACCEPTED}'.split()) if '${ACCEPTED}' else set()
|
|
107
|
+
deps = data.get('dependencies', []) if isinstance(data, dict) else data
|
|
108
|
+
issues = []
|
|
109
|
+
for dep in deps:
|
|
110
|
+
name = dep.get('name', '') if isinstance(dep, dict) else ''
|
|
111
|
+
vulns = dep.get('vulns', []) if isinstance(dep, dict) else []
|
|
112
|
+
if vulns and name and name not in accepted:
|
|
113
|
+
issues.append(name)
|
|
114
|
+
print(len(issues))
|
|
115
|
+
" 2>/dev/null || echo "unknown")
|
|
116
|
+
echo "Unaccepted vulnerable packages: $UNACCEPTED"
|
|
117
|
+
if [ "$UNACCEPTED" != "0" ] && [ "$UNACCEPTED" != "unknown" ]; then
|
|
118
|
+
echo "::error::$UNACCEPTED unaccepted vulnerable package(s)."
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# ── Gate 5: Tests (pytest) ──
|
|
123
|
+
|
|
124
|
+
{{DATABASE_URI_STEP}}
|
|
125
|
+
|
|
126
|
+
- name: Tests
|
|
127
|
+
run: |
|
|
128
|
+
mkdir -p ci-evidence
|
|
129
|
+
pytest --junit-xml=ci-evidence/junit.xml --tb=short
|
|
130
|
+
|
|
131
|
+
# ── Gate 6: Build ──
|
|
132
|
+
|
|
133
|
+
- name: Build Check
|
|
134
|
+
run: python -m build --sdist --wheel
|
|
135
|
+
env:
|
|
136
|
+
{{BUILD_ENV}}
|
|
137
|
+
|
|
138
|
+
# ── Upload artifacts ──
|
|
139
|
+
|
|
140
|
+
# actions/upload-artifact@v4 doesn't honour the job's `working-directory`;
|
|
141
|
+
# paths are workspace-relative. Prefix with WORKING_DIR_PREFIX so artifacts
|
|
142
|
+
# uploaded from a subdir project (e.g. mission-control-api/) include the
|
|
143
|
+
# subdir in their stored path, matching where the gate steps wrote them.
|
|
144
|
+
- uses: actions/upload-artifact@v4
|
|
145
|
+
if: always()
|
|
146
|
+
continue-on-error: true
|
|
147
|
+
with:
|
|
148
|
+
name: ci-results
|
|
149
|
+
path: |
|
|
150
|
+
{{WORKING_DIR_PREFIX}}ci-evidence/sast-results.json
|
|
151
|
+
{{WORKING_DIR_PREFIX}}ci-evidence/dependency-audit.json
|
|
152
|
+
{{WORKING_DIR_PREFIX}}ci-evidence/junit.xml
|
|
153
|
+
{{WORKING_DIR_PREFIX}}dist/
|
|
154
|
+
retention-days: 90
|
|
155
|
+
|
|
156
|
+
register-release:
|
|
157
|
+
name: Register Release
|
|
158
|
+
runs-on: {{RUNNER}}
|
|
159
|
+
if: ${{ vars.DEVAUDIT_BASE_URL != '' }}
|
|
160
|
+
outputs:
|
|
161
|
+
version: ${{ steps.version.outputs.version }}
|
|
162
|
+
env:
|
|
163
|
+
DEVAUDIT_BASE_URL: ${{ vars.DEVAUDIT_BASE_URL }}
|
|
164
|
+
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
165
|
+
steps:
|
|
166
|
+
- uses: actions/checkout@v4
|
|
167
|
+
|
|
168
|
+
- name: Validate DevAudit env
|
|
169
|
+
run: |
|
|
170
|
+
if [ -z "${DEVAUDIT_BASE_URL}" ] || [ -z "${DEVAUDIT_API_KEY}" ]; then
|
|
171
|
+
echo "::error::DEVAUDIT_BASE_URL (variable) and DEVAUDIT_API_KEY (secret) must both be set."
|
|
172
|
+
exit 1
|
|
173
|
+
fi
|
|
174
|
+
echo "BASE=${DEVAUDIT_BASE_URL%/}" >> "$GITHUB_ENV"
|
|
175
|
+
|
|
176
|
+
- name: Determine release version
|
|
177
|
+
id: version
|
|
178
|
+
run: |
|
|
179
|
+
DATE_PREFIX="v$(date +%Y.%m.%d)"
|
|
180
|
+
RESP=$(curl -s -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
181
|
+
"${BASE}/api/ci/releases/resolve?projectSlug={{PROJECT_SLUG}}&versionPrefix=${DATE_PREFIX}")
|
|
182
|
+
VERSION=$(echo "$RESP" | jq -r '.nextSequenceVersion // empty')
|
|
183
|
+
if [ -z "$VERSION" ]; then
|
|
184
|
+
VERSION="${DATE_PREFIX}"
|
|
185
|
+
fi
|
|
186
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
187
|
+
echo "Release version: ${VERSION}"
|
|
188
|
+
|
|
189
|
+
- name: Ensure release exists
|
|
190
|
+
run: |
|
|
191
|
+
chmod +x scripts/upload-evidence.sh 2>/dev/null || true
|
|
192
|
+
bash scripts/upload-evidence.sh \
|
|
193
|
+
{{PROJECT_SLUG}} _compliance-docs compliance_document README.md \
|
|
194
|
+
--release ${{ steps.version.outputs.version }} --create-release-if-missing \
|
|
195
|
+
--environment uat --category planning \
|
|
196
|
+
--git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
|
|
197
|
+
|
|
198
|
+
- name: Sync known requirements from RTM
|
|
199
|
+
run: |
|
|
200
|
+
if [ -f "compliance/RTM.md" ]; then
|
|
201
|
+
REQS=$(grep -oP 'REQ-\d+' compliance/RTM.md | sort -t- -k2 -n -u)
|
|
202
|
+
if [ -n "$REQS" ]; then
|
|
203
|
+
JSON_ARRAY=$(echo "$REQS" | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
|
204
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
205
|
+
-X PATCH "${BASE}/api/ci/projects/{{PROJECT_SLUG}}/known-requirements" \
|
|
206
|
+
-H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
207
|
+
-H "Content-Type: application/json" \
|
|
208
|
+
-d "{\"requirements\": ${JSON_ARRAY}}")
|
|
209
|
+
echo "known_requirements sync: HTTP ${HTTP_CODE}"
|
|
210
|
+
echo "Synced $(echo "$REQS" | wc -w) requirements from RTM.md"
|
|
211
|
+
fi
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
upload-evidence:
|
|
215
|
+
name: Upload Evidence
|
|
216
|
+
runs-on: {{RUNNER}}
|
|
217
|
+
needs: [quality-gates, register-release]
|
|
218
|
+
if: ${{ !failure() && !cancelled() && vars.DEVAUDIT_BASE_URL != '' }}
|
|
219
|
+
env:
|
|
220
|
+
DEVAUDIT_BASE_URL: ${{ vars.DEVAUDIT_BASE_URL }}
|
|
221
|
+
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
222
|
+
steps:
|
|
223
|
+
- uses: actions/checkout@v4
|
|
224
|
+
|
|
225
|
+
# Download to workspace root: upload-artifact@v4 preserves the file's
|
|
226
|
+
# workspace-relative path (e.g. mission-control-api/ci-evidence/sast.json
|
|
227
|
+
# for a subdir project). Downloading with path: '.' restores files to
|
|
228
|
+
# those exact paths so the upload-evidence.sh references below resolve
|
|
229
|
+
# without nesting.
|
|
230
|
+
- name: Download CI gate artifacts
|
|
231
|
+
uses: actions/download-artifact@v4
|
|
232
|
+
continue-on-error: true
|
|
233
|
+
with:
|
|
234
|
+
name: ci-results
|
|
235
|
+
path: .
|
|
236
|
+
|
|
237
|
+
- name: Generate and upload gate evidence
|
|
238
|
+
if: env.DEVAUDIT_BASE_URL != ''
|
|
239
|
+
run: |
|
|
240
|
+
chmod +x scripts/upload-evidence.sh 2>/dev/null || true
|
|
241
|
+
FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
|
|
242
|
+
FLAGS="${FLAGS} --release ${{ needs.register-release.outputs.version }} --create-release-if-missing"
|
|
243
|
+
FLAGS="${FLAGS} --environment uat"
|
|
244
|
+
|
|
245
|
+
UPLOAD_FAILURES=0
|
|
246
|
+
upload() {
|
|
247
|
+
local label="$1"; shift
|
|
248
|
+
echo "Uploading: ${label}"
|
|
249
|
+
if ! bash scripts/upload-evidence.sh "$@"; then
|
|
250
|
+
echo "::error::Failed to upload ${label}"
|
|
251
|
+
UPLOAD_FAILURES=$((UPLOAD_FAILURES + 1))
|
|
252
|
+
fi
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
mkdir -p {{WORKING_DIR_PREFIX}}ci-evidence
|
|
256
|
+
|
|
257
|
+
if [ -f {{WORKING_DIR_PREFIX}}ci-evidence/sast-results.json ]; then
|
|
258
|
+
upload sast-results.json \
|
|
259
|
+
{{PROJECT_SLUG}} _compliance-docs audit_log {{WORKING_DIR_PREFIX}}ci-evidence/sast-results.json \
|
|
260
|
+
--category security_scan ${FLAGS}
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
if [ -f {{WORKING_DIR_PREFIX}}ci-evidence/dependency-audit.json ]; then
|
|
264
|
+
upload dependency-audit.json \
|
|
265
|
+
{{PROJECT_SLUG}} _compliance-docs audit_log {{WORKING_DIR_PREFIX}}ci-evidence/dependency-audit.json \
|
|
266
|
+
--category security_scan ${FLAGS}
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
# pytest junit.xml is the Python equivalent of e2e-results.json — same `e2e_result` evidence type
|
|
270
|
+
if [ -f {{WORKING_DIR_PREFIX}}ci-evidence/junit.xml ]; then
|
|
271
|
+
upload junit.xml \
|
|
272
|
+
{{PROJECT_SLUG}} _compliance-docs e2e_result {{WORKING_DIR_PREFIX}}ci-evidence/junit.xml \
|
|
273
|
+
--category ci_pipeline ${FLAGS}
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
if [ -f "compliance/test-summary-report.md" ]; then
|
|
277
|
+
upload test-summary-report.md \
|
|
278
|
+
{{PROJECT_SLUG}} _compliance-docs compliance_document compliance/test-summary-report.md \
|
|
279
|
+
--category test_report ${FLAGS}
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
for DOC in compliance/RTM.md compliance/test-plan.md compliance/test-cases.md; do
|
|
283
|
+
if [ -f "$DOC" ]; then
|
|
284
|
+
upload "$(basename "$DOC")" \
|
|
285
|
+
{{PROJECT_SLUG}} _compliance-docs compliance_document "$DOC" \
|
|
286
|
+
--category planning ${FLAGS}
|
|
287
|
+
fi
|
|
288
|
+
done
|
|
289
|
+
|
|
290
|
+
for DIR in compliance/pending-releases; do
|
|
291
|
+
if [ -d "$DIR" ]; then
|
|
292
|
+
for TICKET in "$DIR"/*.md; do
|
|
293
|
+
[ -f "$TICKET" ] || continue
|
|
294
|
+
upload "$(basename "$TICKET")" \
|
|
295
|
+
{{PROJECT_SLUG}} _compliance-docs compliance_document "$TICKET" \
|
|
296
|
+
--category release_artifact ${FLAGS}
|
|
297
|
+
done
|
|
298
|
+
fi
|
|
299
|
+
done
|
|
300
|
+
|
|
301
|
+
IN_SCOPE_REQS=()
|
|
302
|
+
if [ -d compliance/pending-releases ]; then
|
|
303
|
+
for TICKET in compliance/pending-releases/RELEASE-TICKET-REQ-*.md; do
|
|
304
|
+
[ -f "$TICKET" ] || continue
|
|
305
|
+
REQ_ID=$(basename "$TICKET" .md | sed 's/^RELEASE-TICKET-//')
|
|
306
|
+
IN_SCOPE_REQS+=("$REQ_ID")
|
|
307
|
+
done
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
if [ ${#IN_SCOPE_REQS[@]} -eq 0 ]; then
|
|
311
|
+
echo "No pending release tickets found — skipping per-requirement evidence upload"
|
|
312
|
+
else
|
|
313
|
+
echo "In-scope requirements for this release: ${IN_SCOPE_REQS[*]}"
|
|
314
|
+
for REQ_ID in "${IN_SCOPE_REQS[@]}"; do
|
|
315
|
+
REQ_DIR="compliance/evidence/${REQ_ID}/"
|
|
316
|
+
if [ ! -d "$REQ_DIR" ]; then
|
|
317
|
+
echo "Warning: pending ticket for ${REQ_ID} but no ${REQ_DIR} on disk"
|
|
318
|
+
continue
|
|
319
|
+
fi
|
|
320
|
+
for ARTIFACT in "$REQ_DIR"*.md; do
|
|
321
|
+
[ -f "$ARTIFACT" ] || continue
|
|
322
|
+
upload "${REQ_ID}/$(basename "$ARTIFACT")" \
|
|
323
|
+
{{PROJECT_SLUG}} "${REQ_ID}" compliance_document "$ARTIFACT" \
|
|
324
|
+
--category planning ${FLAGS}
|
|
325
|
+
done
|
|
326
|
+
done
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
if [ "$UPLOAD_FAILURES" -gt 0 ]; then
|
|
330
|
+
echo "::error::${UPLOAD_FAILURES} evidence upload(s) failed — release is missing gate evidence and cannot pass UAT review"
|
|
331
|
+
exit 1
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
- name: Summary
|
|
335
|
+
run: echo "Evidence uploaded for ${{ needs.register-release.outputs.version }} (UAT)"
|