@metasession.co/devaudit-cli 0.1.28 → 0.1.30

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.
@@ -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