@metasession.co/devaudit-cli 0.1.28 → 0.1.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metasession.co/devaudit-cli",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "DevAudit CLI — installs, syncs, and operates the Metasession SDLC across consumer projects.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@clack/prompts": "^0.8.2",
36
- "@metasession.co/devaudit-plugin-sdk": "^0.1.28",
36
+ "@metasession.co/devaudit-plugin-sdk": "^0.1.29",
37
37
  "commander": "^12.1.0",
38
38
  "consola": "^3.2.3",
39
39
  "env-paths": "^3.0.0",
@@ -26,6 +26,12 @@
26
26
  # build / test / compliance / revert) for
27
27
  # the release row. Unknown values are
28
28
  # silently dropped server-side.
29
+ # --gate-status <status> `passed` / `failed` / `skipped`. Lets the
30
+ # portal distinguish a gate that ran-and-
31
+ # failed from one that never ran. Forwarded
32
+ # as `gateStatus`; unknown values are
33
+ # silently dropped server-side.
34
+ # DevAudit-Installer#96.
29
35
  #
30
36
  # Required environment variables:
31
37
  # DEVAUDIT_BASE_URL e.g. https://meta-comply-production.up.railway.app
@@ -65,6 +71,7 @@ ENVIRONMENT=""
65
71
  EVIDENCE_CATEGORY=""
66
72
  RELEASE_TITLE=""
67
73
  CHANGE_TYPE=""
74
+ GATE_STATUS=""
68
75
 
69
76
  while [ "$#" -gt 0 ]; do
70
77
  case "$1" in
@@ -80,6 +87,10 @@ while [ "$#" -gt 0 ]; do
80
87
  # unknown change-type values are dropped server-side, not 400'd.
81
88
  --release-title) RELEASE_TITLE="$2"; shift 2 ;;
82
89
  --change-type) CHANGE_TYPE="$2"; shift 2 ;;
90
+ # passed/failed/skipped — surfaces failed gates on the portal so
91
+ # ran-and-failed != never-ran. Unknown values dropped server-side.
92
+ # DevAudit-Installer#96.
93
+ --gate-status) GATE_STATUS="$2"; shift 2 ;;
83
94
  *) echo "Unknown option: $1"; exit 1 ;;
84
95
  esac
85
96
  done
@@ -181,6 +192,7 @@ for FILE in "${FILES[@]}"; do
181
192
  [ -n "$EVIDENCE_CATEGORY" ] && CURL_ARGS+=(-F "evidenceCategory=${EVIDENCE_CATEGORY}")
182
193
  [ -n "$RELEASE_TITLE" ] && CURL_ARGS+=(-F "releaseTitle=${RELEASE_TITLE}")
183
194
  [ -n "$CHANGE_TYPE" ] && CURL_ARGS+=(-F "changeType=${CHANGE_TYPE}")
195
+ [ -n "$GATE_STATUS" ] && CURL_ARGS+=(-F "gateStatus=${GATE_STATUS}")
184
196
 
185
197
  ATTEMPT=1
186
198
  BACKOFF=$INITIAL_BACKOFF_SECONDS
@@ -13,6 +13,9 @@
13
13
  # 4. Pending release ticket on disk: exactly one
14
14
  # compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md
15
15
  # -> REQ-XXX
16
+ # 4-bis. RTM.md IN PROGRESS row: exactly one tracked REQ marked
17
+ # IN PROGRESS in compliance/RTM.md
18
+ # -> REQ-XXX
16
19
  # 5. Fallback: bare date -> v2026.05.17
17
20
  #
18
21
  # Step 4 (DevAudit-Installer#92) handles `chore:` / `docs:` / `ci:`
@@ -23,6 +26,12 @@
23
26
  # bare date — when exactly one ticket is open, attribute to it.
24
27
  # Multiple open tickets stays ambiguous → bare-date fallback.
25
28
  #
29
+ # Step 4-bis (DevAudit-Installer#95) is the zero-ceremony equivalent:
30
+ # RTM.md is the file the operator already maintains as the source of
31
+ # truth for release state. When step 4 finds no ticket and exactly one
32
+ # RTM row is IN PROGRESS, attribute to it. RTM_PATH defaults to
33
+ # compliance/RTM.md and is overridable via env.
34
+ #
26
35
  # The id is taken from a bracketed [REQ-XXX] tag (subject or body) or the
27
36
  # `Ref:` line — NOT from unbracketed prose (e.g. "target close: REQ-002" must
28
37
  # not win over "Ref: REQ-001"). Step 3 exists because a "Merge pull request"
@@ -83,5 +92,32 @@ if [ -d compliance/pending-releases ]; then
83
92
  fi
84
93
  fi
85
94
 
95
+ # 4-bis. RTM.md IN PROGRESS row: when exactly one REQ row in
96
+ # compliance/RTM.md (or $RTM_PATH) is marked IN PROGRESS, attribute the
97
+ # in-flight release to it. Reads the file the operator already
98
+ # maintains so chore/docs/ci sync commits don't need a manually-dropped
99
+ # pending-tickets file. Same exactly-one guard as step 4 — zero or
100
+ # multiple IN PROGRESS rows → ambiguous, fall through.
101
+ # DevAudit-Installer#95.
102
+ RTM_PATH="${RTM_PATH:-compliance/RTM.md}"
103
+ if [ -f "$RTM_PATH" ]; then
104
+ # Match REQ rows whose status column starts with `IN PROGRESS`.
105
+ # `\|[[:space:]]+IN PROGRESS` requires a pipe followed by whitespace,
106
+ # so legend rows (`| \`IN PROGRESS\``) and prose mentions don't match.
107
+ # Variable padding between REQ-ID and Status (Issue/Risk/Evidence
108
+ # columns) is fine — only the leading REQ-XXX and the status-cell
109
+ # marker matter.
110
+ IN_PROGRESS_REQS=$(grep -E '\|[[:space:]]+IN PROGRESS' "$RTM_PATH" 2>/dev/null \
111
+ | grep -oE '^\|[[:space:]]*REQ-[0-9]+' \
112
+ | grep -oE 'REQ-[0-9]+' | sort -u || true)
113
+ if [ -n "$IN_PROGRESS_REQS" ]; then
114
+ IN_PROGRESS_COUNT=$(echo "$IN_PROGRESS_REQS" | grep -c .)
115
+ if [ "$IN_PROGRESS_COUNT" = "1" ]; then
116
+ echo "$IN_PROGRESS_REQS"
117
+ exit 0
118
+ fi
119
+ fi
120
+ fi
121
+
86
122
  # 5. Fallback: bare date in UTC
87
123
  echo "v$(date -u +%Y.%m.%d)"
@@ -154,6 +154,89 @@ cat > compliance/pending-releases/RELEASE-TICKET-REQ-051.md <<'TICKET'
154
154
  TICKET
155
155
  assert_eq "subject [REQ-099] beats pending REQ-051 -> REQ-099" "REQ-099" "$(run_helper)"
156
156
 
157
+ # Case 13 (DevAudit-Installer#95): step-4-bis. No subject/body tag,
158
+ # no pending ticket, but RTM.md has exactly one IN PROGRESS row.
159
+ # Attribute to that REQ. Tests the zero-ceremony fallback that
160
+ # survives chore/docs/ci sync commits when no operator state file
161
+ # has been dropped.
162
+ make_fixture "$WORK/c13" "chore: devaudit update to 0.1.29"
163
+ mkdir -p compliance
164
+ cat > compliance/RTM.md <<'RTM'
165
+ # RTM
166
+ | REQ-ID | Issue | Risk | Evidence | Status | Approver | Date |
167
+ | ------- | ----- | ---- | ---------------------------- | ------------------- | -------- | ---------- |
168
+ | REQ-100 | #10 | LOW | compliance/evidence/REQ-100/ | APPROVED - DEPLOYED | dev | 2026-05-30 |
169
+ | REQ-101 | #11 | MED | compliance/evidence/REQ-101/ | IN PROGRESS | dev | 2026-06-01 |
170
+ RTM
171
+ assert_eq "RTM single IN PROGRESS row -> REQ-101" "REQ-101" "$(run_helper)"
172
+
173
+ # Case 14: step-4-bis ambiguity guard. Two IN PROGRESS rows → falls
174
+ # through to the bare date rather than guessing.
175
+ make_fixture "$WORK/c14" "chore: devaudit update to 0.1.29"
176
+ mkdir -p compliance
177
+ cat > compliance/RTM.md <<'RTM'
178
+ | REQ-ID | Status |
179
+ | ------- | -------------- |
180
+ | REQ-101 | IN PROGRESS |
181
+ | REQ-102 | IN PROGRESS |
182
+ RTM
183
+ assert_eq "RTM two IN PROGRESS rows -> bare date $TODAY" "$TODAY" "$(run_helper)"
184
+
185
+ # Case 15: step-4-bis must ignore legend rows that mention IN PROGRESS
186
+ # inside backticks (the wawagardenbar-app RTM convention) and prose
187
+ # mentions in description columns.
188
+ make_fixture "$WORK/c15" "chore: devaudit update to 0.1.29"
189
+ mkdir -p compliance
190
+ cat > compliance/RTM.md <<'RTM'
191
+ # RTM
192
+ ## Conventions
193
+ | Value | Meaning |
194
+ | ------------------ | ----------------------------- |
195
+ | `IN PROGRESS` | Active development underway |
196
+
197
+ | REQ-ID | Status |
198
+ | ------- | ----------------------------------------------------------------- |
199
+ | REQ-200 | RELEASED (was IN PROGRESS during Q3, then deployed) |
200
+ RTM
201
+ assert_eq "RTM legend + prose mentions -> bare date $TODAY" "$TODAY" "$(run_helper)"
202
+
203
+ # Case 16: step-4-bis with the real META-JOBS-shaped RTM row (long
204
+ # parenthetical commentary in the status cell). The status cell still
205
+ # starts with `IN PROGRESS` after the pipe.
206
+ make_fixture "$WORK/c16" "chore: devaudit update to 0.1.29"
207
+ mkdir -p compliance
208
+ cat > compliance/RTM.md <<'RTM'
209
+ | REQ-ID | Issue | Risk | Evidence | Status | Approver | Date |
210
+ | ------- | ----- | ----------- | ---------------------------- | --------------------------------------------------------------------- | ---------- | ---------- |
211
+ | REQ-056 | #117 | MEDIUM-HIGH | compliance/evidence/REQ-056/ | IN PROGRESS (WhatsApp inbound-message router; many details follow...) | ostendo-io | 2026-06-01 |
212
+ RTM
213
+ assert_eq "RTM long parenthetical status -> REQ-056" "REQ-056" "$(run_helper)"
214
+
215
+ # Case 17: step-4-bis must NOT win over a pending ticket on disk.
216
+ # Step 4 returns first.
217
+ make_fixture "$WORK/c17" "chore: devaudit update to 0.1.29"
218
+ mkdir -p compliance/pending-releases compliance
219
+ cat > compliance/pending-releases/RELEASE-TICKET-REQ-301.md <<'TICKET'
220
+ # Release Ticket: REQ-301
221
+ TICKET
222
+ cat > compliance/RTM.md <<'RTM'
223
+ | REQ-ID | Status |
224
+ | ------- | ----------- |
225
+ | REQ-302 | IN PROGRESS |
226
+ RTM
227
+ assert_eq "pending ticket REQ-301 beats RTM IN PROGRESS REQ-302" "REQ-301" "$(run_helper)"
228
+
229
+ # Case 18: step-4-bis respects RTM_PATH env override.
230
+ make_fixture "$WORK/c18" "chore: devaudit update to 0.1.29"
231
+ mkdir -p docs
232
+ cat > docs/custom-RTM.md <<'RTM'
233
+ | REQ-ID | Status |
234
+ | ------- | ----------- |
235
+ | REQ-400 | IN PROGRESS |
236
+ RTM
237
+ GOT=$(RTM_PATH=docs/custom-RTM.md run_helper)
238
+ assert_eq "RTM_PATH=docs/custom-RTM.md -> REQ-400" "REQ-400" "$GOT"
239
+
157
240
  echo ""
158
241
  echo "=== Summary: $PASS pass / $FAIL fail ==="
159
242
 
@@ -22,7 +22,10 @@ echo "Comparing: $BASE_BRANCH...HEAD"
22
22
  echo ""
23
23
 
24
24
  # Conventional Commit regex: type(optional-scope): description
25
- CC_REGEX='^(feat|fix|docs|test|refactor|chore|compliance|security|perf|ci|build|revert)(\([a-zA-Z0-9_-]+\))?!?: .+'
25
+ # Scope accepts anything except `)` so multi-scope subjects like
26
+ # `feat(auth,profile):` and `fix(rewards/expiry):` validate. The closing-paren
27
+ # guard prevents pathological inputs. DevAudit-Installer#93.
28
+ CC_REGEX='^(feat|fix|docs|test|refactor|chore|compliance|security|perf|ci|build|revert)(\([^)]+\))?!?: .+'
26
29
 
27
30
  COMMITS=$(git log "$BASE_BRANCH"..HEAD --format='%H' || true)
28
31
 
@@ -76,11 +76,13 @@ jobs:
76
76
  # ── Gate 1: TypeScript ──
77
77
 
78
78
  - name: TypeScript Check
79
+ id: typescript
79
80
  run: npx tsc --noEmit
80
81
 
81
82
  # ── Gate 2: SAST (Semgrep) ──
82
83
 
83
84
  - name: SAST Scan
85
+ id: sast
84
86
  run: |
85
87
  # --output writes the JSON report to the file directly; stderr
86
88
  # (progress/metrics/version notices) goes to /dev/null. Using
@@ -106,6 +108,7 @@ jobs:
106
108
  # ── Gate 3: Dependency Audit ──
107
109
 
108
110
  - name: Dependency Audit
111
+ id: dep-audit
109
112
  run: |
110
113
  # stderr → /dev/null so warnings can't corrupt the JSON (DevAudit #48)
111
114
  npm audit --json > dependency-audit.json 2>/dev/null || true
@@ -144,10 +147,34 @@ jobs:
144
147
  # ── Gate 5: Build ──
145
148
 
146
149
  - name: Build Check
150
+ id: build
147
151
  run: npm run build
148
152
  env:
149
153
  {{BUILD_ENV}}
150
154
 
155
+ # ── Summarise per-gate outcomes ──
156
+ #
157
+ # Runs unconditionally so a failed earlier gate doesn't strand the
158
+ # outcome of the gates that did run. Step outcomes are one of
159
+ # `success` / `failure` / `skipped` / `cancelled`. The upload-evidence
160
+ # job maps these to `passed` / `failed` / `skipped` and sends the
161
+ # result as `gateStatus=` on each per-gate upload, so the portal can
162
+ # render a failed gate as failed (with the run artefact) rather than
163
+ # showing it as missing. DevAudit-Installer#96.
164
+
165
+ - name: Summarise gate outcomes
166
+ if: always()
167
+ run: |
168
+ cat > gate-outcomes.json <<EOF
169
+ {
170
+ "typescript": "${{ steps.typescript.outcome }}",
171
+ "sast": "${{ steps.sast.outcome }}",
172
+ "dependency_audit": "${{ steps.dep-audit.outcome }}",
173
+ "build": "${{ steps.build.outcome }}"
174
+ }
175
+ EOF
176
+ cat gate-outcomes.json
177
+
151
178
  # ── Upload artifacts ──
152
179
 
153
180
  - uses: actions/upload-artifact@v4
@@ -162,6 +189,7 @@ jobs:
162
189
  e2e-auth-results.json
163
190
  playwright-report/
164
191
  coverage/coverage-summary.json
192
+ gate-outcomes.json
165
193
  compliance/evidence/*/screenshots/*.png
166
194
  retention-days: 90
167
195
 
@@ -278,7 +306,11 @@ jobs:
278
306
  name: Upload Evidence
279
307
  runs-on: {{RUNNER}}
280
308
  needs: [quality-gates, register-release]
281
- if: ${{ !failure() && !cancelled() && vars.DEVAUDIT_BASE_URL != '' }}
309
+ # `always()` instead of `!failure()` so failed gates still upload their
310
+ # evidence — `status=failed` is itself the audit trail. `!cancelled()`
311
+ # still guards against partial state on operator-cancel.
312
+ # DevAudit-Installer#96.
313
+ if: ${{ always() && !cancelled() && vars.DEVAUDIT_BASE_URL != '' && needs.register-release.result == 'success' }}
282
314
  env:
283
315
  DEVAUDIT_BASE_URL: ${{ vars.DEVAUDIT_BASE_URL }}
284
316
  DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
@@ -315,6 +347,32 @@ jobs:
315
347
  fi
316
348
  }
317
349
 
350
+ # Map step outcomes from gate-outcomes.json (written by the
351
+ # `Summarise gate outcomes` step) to gateStatus values the
352
+ # uploader forwards to the portal. Failed gates upload as
353
+ # gateStatus=failed so the portal can distinguish a gate that
354
+ # ran-and-failed from one that never ran. DevAudit-Installer#96.
355
+ gate_status() {
356
+ case "$1" in
357
+ success) echo passed ;;
358
+ failure) echo failed ;;
359
+ *) echo skipped ;;
360
+ esac
361
+ }
362
+ STATUS_TYPESCRIPT=skipped
363
+ STATUS_SAST=skipped
364
+ STATUS_DEPAUDIT=skipped
365
+ STATUS_BUILD=skipped
366
+ if [ -f ci-evidence/gate-outcomes.json ]; then
367
+ STATUS_TYPESCRIPT=$(gate_status "$(jq -r '.typescript // "skipped"' ci-evidence/gate-outcomes.json)")
368
+ STATUS_SAST=$(gate_status "$(jq -r '.sast // "skipped"' ci-evidence/gate-outcomes.json)")
369
+ STATUS_DEPAUDIT=$(gate_status "$(jq -r '.dependency_audit // "skipped"' ci-evidence/gate-outcomes.json)")
370
+ STATUS_BUILD=$(gate_status "$(jq -r '.build // "skipped"' ci-evidence/gate-outcomes.json)")
371
+ upload gate-outcomes.json \
372
+ {{PROJECT_SLUG}} _compliance-docs compliance_document ci-evidence/gate-outcomes.json \
373
+ --category ci_pipeline ${FLAGS}
374
+ fi
375
+
318
376
  # Re-generate gate evidence as fallback if artifact download failed
319
377
  mkdir -p ci-evidence
320
378
  if [ ! -f ci-evidence/sast-results.json ]; then
@@ -337,7 +395,7 @@ jobs:
337
395
  if [ -f ci-evidence/sast-results.json ]; then
338
396
  upload sast-results.json \
339
397
  {{PROJECT_SLUG}} _compliance-docs sast_report ci-evidence/sast-results.json \
340
- --category security_scan ${FLAGS}
398
+ --category security_scan --gate-status "$STATUS_SAST" ${FLAGS}
341
399
  fi
342
400
 
343
401
  # Upload dependency audit — precise evidence_type=dependency_audit
@@ -345,7 +403,7 @@ jobs:
345
403
  if [ -f ci-evidence/dependency-audit.json ]; then
346
404
  upload dependency-audit.json \
347
405
  {{PROJECT_SLUG}} _compliance-docs dependency_audit ci-evidence/dependency-audit.json \
348
- --category security_scan ${FLAGS}
406
+ --category security_scan --gate-status "$STATUS_DEPAUDIT" ${FLAGS}
349
407
  fi
350
408
 
351
409
  # Upload E2E test results (ci_pipeline category)
@@ -61,19 +61,23 @@ jobs:
61
61
  # ── Gate 1: Lint + format (ruff) ──
62
62
 
63
63
  - name: Ruff lint
64
+ id: ruff-lint
64
65
  run: ruff check {{SOURCE_DIRS}}
65
66
 
66
67
  - name: Ruff format check
68
+ id: ruff-format
67
69
  run: ruff format --check {{SOURCE_DIRS}}
68
70
 
69
71
  # ── Gate 2: Type check (mypy --strict) ──
70
72
 
71
73
  - name: Type Check (mypy)
74
+ id: mypy
72
75
  run: mypy {{SOURCE_DIRS}}
73
76
 
74
77
  # ── Gate 3: SAST (Semgrep) ──
75
78
 
76
79
  - name: SAST Scan
80
+ id: sast
77
81
  run: |
78
82
  mkdir -p ci-evidence
79
83
  semgrep scan --config auto {{SOURCE_DIRS}} \
@@ -95,6 +99,7 @@ jobs:
95
99
  # ── Gate 4: Dependency Audit (pip-audit) ──
96
100
 
97
101
  - name: Dependency Audit
102
+ id: dep-audit
98
103
  run: |
99
104
  mkdir -p ci-evidence
100
105
  pip-audit --format=json > ci-evidence/dependency-audit.json 2>&1 || true
@@ -124,6 +129,7 @@ jobs:
124
129
  {{DATABASE_URI_STEP}}
125
130
 
126
131
  - name: Tests
132
+ id: tests
127
133
  run: |
128
134
  mkdir -p ci-evidence
129
135
  pytest --junit-xml=ci-evidence/junit.xml --tb=short
@@ -131,10 +137,31 @@ jobs:
131
137
  # ── Gate 6: Build ──
132
138
 
133
139
  - name: Build Check
140
+ id: build
134
141
  run: python -m build --sdist --wheel
135
142
  env:
136
143
  {{BUILD_ENV}}
137
144
 
145
+ # ── Summarise per-gate outcomes ──
146
+ # Runs unconditionally so a failed earlier gate doesn't strand the
147
+ # outcome of the gates that did run. DevAudit-Installer#96.
148
+ - name: Summarise gate outcomes
149
+ if: always()
150
+ run: |
151
+ mkdir -p ci-evidence
152
+ cat > ci-evidence/gate-outcomes.json <<EOF
153
+ {
154
+ "ruff_lint": "${{ steps.ruff-lint.outcome }}",
155
+ "ruff_format": "${{ steps.ruff-format.outcome }}",
156
+ "mypy": "${{ steps.mypy.outcome }}",
157
+ "sast": "${{ steps.sast.outcome }}",
158
+ "dependency_audit": "${{ steps.dep-audit.outcome }}",
159
+ "tests": "${{ steps.tests.outcome }}",
160
+ "build": "${{ steps.build.outcome }}"
161
+ }
162
+ EOF
163
+ cat ci-evidence/gate-outcomes.json
164
+
138
165
  # ── Upload artifacts ──
139
166
 
140
167
  # actions/upload-artifact@v4 doesn't honour the job's `working-directory`;
@@ -150,6 +177,7 @@ jobs:
150
177
  {{WORKING_DIR_PREFIX}}ci-evidence/sast-results.json
151
178
  {{WORKING_DIR_PREFIX}}ci-evidence/dependency-audit.json
152
179
  {{WORKING_DIR_PREFIX}}ci-evidence/junit.xml
180
+ {{WORKING_DIR_PREFIX}}ci-evidence/gate-outcomes.json
153
181
  {{WORKING_DIR_PREFIX}}dist/
154
182
  retention-days: 90
155
183
 
@@ -256,7 +284,11 @@ jobs:
256
284
  name: Upload Evidence
257
285
  runs-on: {{RUNNER}}
258
286
  needs: [quality-gates, register-release]
259
- if: ${{ !failure() && !cancelled() && vars.DEVAUDIT_BASE_URL != '' }}
287
+ # `always()` instead of `!failure()` so failed gates still upload their
288
+ # evidence — `status=failed` is itself the audit trail. `!cancelled()`
289
+ # still guards against partial state on operator-cancel.
290
+ # DevAudit-Installer#96.
291
+ if: ${{ always() && !cancelled() && vars.DEVAUDIT_BASE_URL != '' && needs.register-release.result == 'success' }}
260
292
  env:
261
293
  DEVAUDIT_BASE_URL: ${{ vars.DEVAUDIT_BASE_URL }}
262
294
  DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
@@ -293,25 +325,50 @@ jobs:
293
325
  fi
294
326
  }
295
327
 
328
+ # Map step outcomes from gate-outcomes.json (written by the
329
+ # `Summarise gate outcomes` step) to gateStatus values the
330
+ # uploader forwards to the portal. Failed gates upload as
331
+ # gateStatus=failed so the portal can distinguish a gate that
332
+ # ran-and-failed from one that never ran. DevAudit-Installer#96.
333
+ gate_status() {
334
+ case "$1" in
335
+ success) echo passed ;;
336
+ failure) echo failed ;;
337
+ *) echo skipped ;;
338
+ esac
339
+ }
340
+ STATUS_SAST=skipped
341
+ STATUS_DEPAUDIT=skipped
342
+ STATUS_TESTS=skipped
343
+ OUTCOMES_FILE="{{WORKING_DIR_PREFIX}}ci-evidence/gate-outcomes.json"
344
+ if [ -f "$OUTCOMES_FILE" ]; then
345
+ STATUS_SAST=$(gate_status "$(jq -r '.sast // "skipped"' "$OUTCOMES_FILE")")
346
+ STATUS_DEPAUDIT=$(gate_status "$(jq -r '.dependency_audit // "skipped"' "$OUTCOMES_FILE")")
347
+ STATUS_TESTS=$(gate_status "$(jq -r '.tests // "skipped"' "$OUTCOMES_FILE")")
348
+ upload gate-outcomes.json \
349
+ {{PROJECT_SLUG}} _compliance-docs compliance_document "$OUTCOMES_FILE" \
350
+ --category ci_pipeline ${FLAGS}
351
+ fi
352
+
296
353
  mkdir -p {{WORKING_DIR_PREFIX}}ci-evidence
297
354
 
298
355
  if [ -f {{WORKING_DIR_PREFIX}}ci-evidence/sast-results.json ]; then
299
356
  upload sast-results.json \
300
357
  {{PROJECT_SLUG}} _compliance-docs audit_log {{WORKING_DIR_PREFIX}}ci-evidence/sast-results.json \
301
- --category security_scan ${FLAGS}
358
+ --category security_scan --gate-status "$STATUS_SAST" ${FLAGS}
302
359
  fi
303
360
 
304
361
  if [ -f {{WORKING_DIR_PREFIX}}ci-evidence/dependency-audit.json ]; then
305
362
  upload dependency-audit.json \
306
363
  {{PROJECT_SLUG}} _compliance-docs audit_log {{WORKING_DIR_PREFIX}}ci-evidence/dependency-audit.json \
307
- --category security_scan ${FLAGS}
364
+ --category security_scan --gate-status "$STATUS_DEPAUDIT" ${FLAGS}
308
365
  fi
309
366
 
310
367
  # pytest junit.xml is the Python equivalent of e2e-results.json — same `e2e_result` evidence type
311
368
  if [ -f {{WORKING_DIR_PREFIX}}ci-evidence/junit.xml ]; then
312
369
  upload junit.xml \
313
370
  {{PROJECT_SLUG}} _compliance-docs e2e_result {{WORKING_DIR_PREFIX}}ci-evidence/junit.xml \
314
- --category ci_pipeline ${FLAGS}
371
+ --category ci_pipeline --gate-status "$STATUS_TESTS" ${FLAGS}
315
372
  fi
316
373
 
317
374
  if [ -f "compliance/test-summary-report.md" ]; then