@metasession.co/devaudit-cli 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/sdlc/files/_common/3-compile-evidence.md +1 -1
- package/sdlc/files/_common/governance/periodic-review.md.template +6 -4
- package/sdlc/files/ci/ci.yml.template +10 -9
- package/sdlc/files/ci/compliance-evidence.yml.template +32 -11
- package/sdlc/files/ci/incident-export.yml.template +173 -0
- package/sdlc/files/ci/periodic-review.yml.template +218 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metasession.co/devaudit-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
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.
|
|
36
|
+
"@metasession.co/devaudit-plugin-sdk": "^0.1.32",
|
|
37
37
|
"commander": "^12.1.0",
|
|
38
38
|
"consola": "^3.2.3",
|
|
39
39
|
"env-paths": "^3.0.0",
|
|
@@ -29,7 +29,7 @@ description: Compile test, security, and AI evidence, update RTM, create release
|
|
|
29
29
|
| `compliance/evidence/REQ-XXX/test-scope.md` | Git | Planning artifact, reviewed in PRs |
|
|
30
30
|
| `compliance/evidence/REQ-XXX/implementation-plan.md` | Git | Design decisions artifact (MEDIUM/HIGH risk), reviewed in PRs |
|
|
31
31
|
| `compliance/evidence/REQ-XXX/test-plan.md` | Git | Test strategy — tests to add/update/remove, mapped to criteria |
|
|
32
|
-
| `compliance/evidence/REQ-XXX/test-execution-summary.md` | Git | Gate results, test changes, coverage against test plan |
|
|
32
|
+
| `compliance/evidence/REQ-XXX/test-execution-summary.md` | Git | Gate results, test changes, coverage against test plan. **ISO 29119-3 §3.5.6 Test Completion Report for THIS release** — uploaded as `evidence_type=test_report` since v0.1.32, satisfying the portal's Test Reports gate with fresh per-release evidence. |
|
|
33
33
|
| `compliance/evidence/REQ-XXX/ai-use-note.md` | Git | Small markdown, needs PR review |
|
|
34
34
|
| `compliance/evidence/REQ-XXX/ai-prompts.md` | Git | Small markdown, needs PR review |
|
|
35
35
|
| `compliance/evidence/REQ-XXX/security-summary.md` | Git | Small markdown, needs PR review |
|
|
@@ -9,10 +9,12 @@ review_cadence_days: 90
|
|
|
9
9
|
|
|
10
10
|
> ⚠️ **STARTER TEMPLATE — REPLACE BEFORE GOING TO PRODUCTION.**
|
|
11
11
|
> This file was auto-installed by `devaudit install` as a starting point.
|
|
12
|
-
>
|
|
13
|
-
> auto-
|
|
14
|
-
>
|
|
15
|
-
>
|
|
12
|
+
> The Periodic Review workflow (`.github/workflows/periodic-review.yml`, shipped in
|
|
13
|
+
> v0.1.31) auto-regenerates this file quarterly with locally-derived metrics and
|
|
14
|
+
> opens a PR. The human attestation sections (Review notes, control-effectiveness
|
|
15
|
+
> judgement, dual-actor sign-off) still need to be filled in before each PR can
|
|
16
|
+
> merge — the auto-generator can't produce defensible attestation on its own.
|
|
17
|
+
> Auditors will reject unedited stubs. See `docs/governance-templates.md` for guidance.
|
|
16
18
|
|
|
17
19
|
# Periodic Review of Internal Controls
|
|
18
20
|
|
|
@@ -430,17 +430,18 @@ jobs:
|
|
|
430
430
|
--category test_report ${FLAGS}
|
|
431
431
|
fi
|
|
432
432
|
|
|
433
|
-
# Upload
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
438
|
-
#
|
|
439
|
-
#
|
|
433
|
+
# Upload project-level Test Summary Report as a baseline
|
|
434
|
+
# `compliance_document`. As of v0.1.32 this is NOT a per-release
|
|
435
|
+
# gate artefact — the Test Reports gate is satisfied by the
|
|
436
|
+
# per-REQ `test-execution-summary.md` uploaded by
|
|
437
|
+
# compliance-evidence.yml (carries fresh release-specific data).
|
|
438
|
+
# The project-level TSR remains useful as Documents-tab baseline
|
|
439
|
+
# describing the project's testing posture but no longer poses
|
|
440
|
+
# as per-release test evidence. See DevAudit-Installer#101.
|
|
440
441
|
if [ -f "compliance/test-summary-report.md" ]; then
|
|
441
442
|
upload test-summary-report.md \
|
|
442
|
-
{{PROJECT_SLUG}} _compliance-docs
|
|
443
|
-
--category
|
|
443
|
+
{{PROJECT_SLUG}} _compliance-docs compliance_document compliance/test-summary-report.md \
|
|
444
|
+
--category planning ${FLAGS}
|
|
444
445
|
fi
|
|
445
446
|
|
|
446
447
|
# Upload per-AC e2e evidence screenshots, scoped to each in-scope
|
|
@@ -138,15 +138,20 @@ jobs:
|
|
|
138
138
|
fi
|
|
139
139
|
done
|
|
140
140
|
|
|
141
|
-
# Test
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
141
|
+
# Project-level Test Summary Report — a hand-authored baseline
|
|
142
|
+
# describing the project's testing posture. As of v0.1.32 this is
|
|
143
|
+
# uploaded as `compliance_document` (NOT `test_report`) — the
|
|
144
|
+
# per-release Test Reports gate is now satisfied by the per-REQ
|
|
145
|
+
# `test-execution-summary.md` uploaded in the in-scope-requirements
|
|
146
|
+
# loop below, which carries fresh release-specific data. The
|
|
147
|
+
# project-level TSR continues to ship as a Documents-tab baseline
|
|
148
|
+
# but no longer poses as per-release test evidence.
|
|
149
|
+
# See DevAudit-Installer#101.
|
|
145
150
|
if [ -f "compliance/test-summary-report.md" ]; then
|
|
146
|
-
echo "Uploading: test-summary-report.md (
|
|
151
|
+
echo "Uploading: test-summary-report.md (compliance_document — baseline)"
|
|
147
152
|
bash scripts/upload-evidence.sh \
|
|
148
|
-
{{PROJECT_SLUG}} _compliance-docs
|
|
149
|
-
--category
|
|
153
|
+
{{PROJECT_SLUG}} _compliance-docs compliance_document compliance/test-summary-report.md \
|
|
154
|
+
--category planning ${FLAGS} --release "${DERIVED_RELEASE}" \
|
|
150
155
|
"${DERIVED_META[@]}" \
|
|
151
156
|
|| echo "Warning: Failed to upload test-summary-report.md"
|
|
152
157
|
fi
|
|
@@ -257,12 +262,28 @@ jobs:
|
|
|
257
262
|
REQ_META_ARGS=$(req_meta_args "$REQ_ID")
|
|
258
263
|
for ARTIFACT in "$REQ_DIR"*.md; do
|
|
259
264
|
[ -f "$ARTIFACT" ] || continue
|
|
260
|
-
|
|
265
|
+
# Per-REQ test-execution-summary.md is the ISO 29119-3 §3.5.6
|
|
266
|
+
# Test Completion Report for THIS release cycle (populated by
|
|
267
|
+
# the e2e-test-engineer skill in Stage 3 — scope, results, AC
|
|
268
|
+
# mapping, defects). Upload as `test_report` so it satisfies
|
|
269
|
+
# the portal's Test Reports gate with per-release evidence
|
|
270
|
+
# instead of the project-level evergreen TSR (which from
|
|
271
|
+
# v0.1.32 downgrades to `compliance_document`). See
|
|
272
|
+
# DevAudit-Installer#101.
|
|
273
|
+
BASENAME=$(basename "$ARTIFACT")
|
|
274
|
+
if [ "$BASENAME" = "test-execution-summary.md" ] || [ "$BASENAME" = "test-summary-report.md" ]; then
|
|
275
|
+
EVTYPE=test_report
|
|
276
|
+
EVCAT=test_report
|
|
277
|
+
else
|
|
278
|
+
EVTYPE=compliance_document
|
|
279
|
+
EVCAT=planning
|
|
280
|
+
fi
|
|
281
|
+
echo "Uploading: ${REQ_ID}/${BASENAME} (${EVTYPE})"
|
|
261
282
|
eval "bash scripts/upload-evidence.sh \
|
|
262
|
-
{{PROJECT_SLUG}} \"${REQ_ID}\"
|
|
263
|
-
--category
|
|
283
|
+
{{PROJECT_SLUG}} \"${REQ_ID}\" ${EVTYPE} \"$ARTIFACT\" \
|
|
284
|
+
--category ${EVCAT} ${FLAGS} --release \"${REQ_ID}\" \
|
|
264
285
|
${REQ_META_ARGS}" \
|
|
265
|
-
|| echo "Warning: Failed to upload $
|
|
286
|
+
|| echo "Warning: Failed to upload ${BASENAME}"
|
|
266
287
|
done
|
|
267
288
|
done
|
|
268
289
|
fi
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Incident Export — auto-export a closed `label:incident` issue to compliance/governance/incident-report-<n>.md
|
|
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
|
+
# Why this exists:
|
|
7
|
+
# - ISO 29119 3.5.4 (Test incident report), SOC 2 CC7.2 (System monitoring
|
|
8
|
+
# and incident response), and GDPR Art. 33 + 34 (breach notification)
|
|
9
|
+
# all want incident records as portal-resident evidence — not lost in
|
|
10
|
+
# GitHub issue threads.
|
|
11
|
+
# - When an issue labelled `incident` is closed, this workflow exports its
|
|
12
|
+
# title + body + comments + timeline into a structured markdown report,
|
|
13
|
+
# opens a PR adding it to compliance/governance/, and lets the next
|
|
14
|
+
# push to develop pick it up via compliance-evidence.yml's
|
|
15
|
+
# upload_governance helper (uploads as `incident_report`).
|
|
16
|
+
#
|
|
17
|
+
# Operator setup (one-time):
|
|
18
|
+
# gh label create incident --color 'B60205' --description 'Operational or test incident; close to auto-archive as portal evidence'
|
|
19
|
+
#
|
|
20
|
+
# DevAudit-Installer#98 WS4.
|
|
21
|
+
|
|
22
|
+
name: Incident Export
|
|
23
|
+
|
|
24
|
+
on:
|
|
25
|
+
issues:
|
|
26
|
+
types: [closed]
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: write
|
|
30
|
+
issues: read
|
|
31
|
+
pull-requests: write
|
|
32
|
+
|
|
33
|
+
jobs:
|
|
34
|
+
export:
|
|
35
|
+
name: Export closed incident issue to compliance/governance/
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
# Only fire when the issue carries the `incident` label. Cheap predicate;
|
|
38
|
+
# skips this job entirely on unlabelled issue closes.
|
|
39
|
+
if: contains(github.event.issue.labels.*.name, 'incident')
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
with:
|
|
43
|
+
fetch-depth: 0
|
|
44
|
+
token: ${{ secrets.DEVAUDIT_USER_TOKEN || github.token }}
|
|
45
|
+
|
|
46
|
+
- name: Compute paths
|
|
47
|
+
id: paths
|
|
48
|
+
env:
|
|
49
|
+
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
50
|
+
run: |
|
|
51
|
+
OUT="compliance/governance/incident-report-${ISSUE_NUMBER}.md"
|
|
52
|
+
BRANCH="chore/incident-export-${ISSUE_NUMBER}"
|
|
53
|
+
echo "out=${OUT}" >> "$GITHUB_OUTPUT"
|
|
54
|
+
echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT"
|
|
55
|
+
|
|
56
|
+
- name: Export issue to markdown
|
|
57
|
+
env:
|
|
58
|
+
GH_TOKEN: ${{ github.token }}
|
|
59
|
+
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
60
|
+
OUT: ${{ steps.paths.outputs.out }}
|
|
61
|
+
run: |
|
|
62
|
+
mkdir -p compliance/governance
|
|
63
|
+
ISSUE_JSON=$(gh issue view "$ISSUE_NUMBER" \
|
|
64
|
+
--json title,body,labels,author,createdAt,closedAt,comments,url,assignees,number,state)
|
|
65
|
+
{
|
|
66
|
+
echo "$ISSUE_JSON" | jq -r '
|
|
67
|
+
"---",
|
|
68
|
+
("title: " + (.title | @json)),
|
|
69
|
+
("incident_id: \"INC-" + (.createdAt[:10] | gsub("-";"")) + "-" + (.number|tostring) + "\""),
|
|
70
|
+
("severity: \"REPLACE — low | medium | high | critical\""),
|
|
71
|
+
("detected_at: " + (.createdAt | @json)),
|
|
72
|
+
("resolved_at: " + (.closedAt | @json)),
|
|
73
|
+
("involves_personal_data: \"REPLACE — true | false\""),
|
|
74
|
+
("reported_to_supervisory_authority: \"REPLACE — true | false | n/a\""),
|
|
75
|
+
("notification_window_72h: \"REPLACE — within | outside | n/a\""),
|
|
76
|
+
("last_reviewed_at: " + (.closedAt[:10] | @json)),
|
|
77
|
+
("source_issue: " + (.url | @json)),
|
|
78
|
+
("source_issue_number: " + (.number|tostring)),
|
|
79
|
+
"---",
|
|
80
|
+
"",
|
|
81
|
+
"> ℹ️ Auto-exported by Incident Export workflow on issue close.",
|
|
82
|
+
"> The narrative below is the original issue body + comments.",
|
|
83
|
+
"> **Operator must replace the REPLACE markers in the frontmatter and",
|
|
84
|
+
"> in the GDPR triage / sign-off sections before this PR merges** —",
|
|
85
|
+
"> a personal-data triage decision is load-bearing; an auto-generated",
|
|
86
|
+
"> answer is not defensible. Auditors will reject auto-generated",
|
|
87
|
+
"> stubs without human attestation.",
|
|
88
|
+
"",
|
|
89
|
+
"# Incident Report — " + .title,
|
|
90
|
+
"",
|
|
91
|
+
"**Framework coverage:**",
|
|
92
|
+
"",
|
|
93
|
+
"- `ISO29119.3.5.4` (Test incident report)",
|
|
94
|
+
"- `SOC2.CC7.2` (System monitoring and incident response)",
|
|
95
|
+
"- `GDPR.Art-33` (Notification of a personal data breach to the supervisory authority — 72h)",
|
|
96
|
+
"- `GDPR.Art-34` (Communication of a personal data breach to the data subject)",
|
|
97
|
+
"",
|
|
98
|
+
"**Source:** [#" + (.number|tostring) + "](" + .url + ") ",
|
|
99
|
+
"**Detected:** " + .createdAt + " ",
|
|
100
|
+
"**Closed:** " + .closedAt + " ",
|
|
101
|
+
"**Reporter:** @" + .author.login + " ",
|
|
102
|
+
"**Assignees:** " + (if .assignees == [] then "_unassigned_" else (.assignees | map("@" + .login) | join(", ")) end) + " ",
|
|
103
|
+
"**Labels:** " + (.labels | map("`" + .name + "`") | join(", ")),
|
|
104
|
+
"",
|
|
105
|
+
"## 1. Personal data scope (GDPR triage) — REPLACE",
|
|
106
|
+
"",
|
|
107
|
+
"| Question | Answer |",
|
|
108
|
+
"| --- | --- |",
|
|
109
|
+
"| Did the incident involve personal data? | REPLACE — Y / N |",
|
|
110
|
+
"| If Y: estimated number of data subjects affected | REPLACE |",
|
|
111
|
+
"| If Y: categories of personal data involved | REPLACE |",
|
|
112
|
+
"| If Y: likely consequences for data subjects | REPLACE |",
|
|
113
|
+
"| Notify supervisory authority (Art. 33)? | REPLACE — required if Y and risk to rights/freedoms |",
|
|
114
|
+
"| Notify data subjects (Art. 34)? | REPLACE — required if high risk to rights/freedoms |",
|
|
115
|
+
"| 72-hour notification window | REPLACE — within / outside / n/a |",
|
|
116
|
+
"",
|
|
117
|
+
"## 2. Narrative (from the GitHub issue)",
|
|
118
|
+
"",
|
|
119
|
+
.body,
|
|
120
|
+
"",
|
|
121
|
+
"## 3. Timeline (from issue comments)"
|
|
122
|
+
'
|
|
123
|
+
echo ""
|
|
124
|
+
# Comments — one section each, ordered by createdAt.
|
|
125
|
+
echo "$ISSUE_JSON" | jq -r '
|
|
126
|
+
.comments | sort_by(.createdAt) | .[] |
|
|
127
|
+
"### " + .createdAt + " — @" + .author.login + "\n\n" + .body + "\n"
|
|
128
|
+
'
|
|
129
|
+
cat <<'TAIL'
|
|
130
|
+
|
|
131
|
+
## 4. Sign-off — REPLACE
|
|
132
|
+
|
|
133
|
+
| Role | Name | Date |
|
|
134
|
+
| ----------------------------------- | ------- | ------- |
|
|
135
|
+
| Incident Commander | REPLACE | REPLACE |
|
|
136
|
+
| Engineering lead | REPLACE | REPLACE |
|
|
137
|
+
| DPO (if personal data involved) | REPLACE | REPLACE |
|
|
138
|
+
| Security lead | REPLACE | REPLACE |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
_Source: auto-exported by `.github/workflows/incident-export.yml` when the originating issue was closed._
|
|
143
|
+
TAIL
|
|
144
|
+
} > "$OUT"
|
|
145
|
+
echo "Wrote $OUT"
|
|
146
|
+
|
|
147
|
+
- name: Open export PR
|
|
148
|
+
env:
|
|
149
|
+
GH_TOKEN: ${{ secrets.DEVAUDIT_USER_TOKEN || github.token }}
|
|
150
|
+
BRANCH: ${{ steps.paths.outputs.branch }}
|
|
151
|
+
OUT: ${{ steps.paths.outputs.out }}
|
|
152
|
+
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
153
|
+
ISSUE_URL: ${{ github.event.issue.html_url }}
|
|
154
|
+
run: |
|
|
155
|
+
git config user.name 'devaudit-bot'
|
|
156
|
+
git config user.email 'devaudit-bot@users.noreply.github.com'
|
|
157
|
+
git fetch origin develop
|
|
158
|
+
git checkout -B "${BRANCH}" origin/develop
|
|
159
|
+
git add "$OUT"
|
|
160
|
+
if git diff --cached --quiet; then
|
|
161
|
+
echo "No change — already exported."
|
|
162
|
+
exit 0
|
|
163
|
+
fi
|
|
164
|
+
git commit -m "chore(compliance): export incident #${ISSUE_NUMBER}" -m "Auto-exported by Incident Export workflow." -m "" -m "Source: ${ISSUE_URL}" -m "" -m "REPLACE markers in the GDPR triage + Sign-off sections require human attestation before this PR can merge."
|
|
165
|
+
git push --force-with-lease origin "${BRANCH}"
|
|
166
|
+
EXISTING=$(gh pr list --head "${BRANCH}" --json number --jq '.[0].number' || true)
|
|
167
|
+
if [ -z "$EXISTING" ]; then
|
|
168
|
+
gh pr create --base develop --head "${BRANCH}" \
|
|
169
|
+
--title "chore(compliance): export incident #${ISSUE_NUMBER}" \
|
|
170
|
+
--body "Auto-exported by the \`Incident Export\` workflow when issue [#${ISSUE_NUMBER}](${ISSUE_URL}) was closed with label \`incident\`.\n\n**Required before merge:**\n- [ ] Replace \`REPLACE — …\` markers in the **GDPR triage** section (personal-data Y/N decision is load-bearing)\n- [ ] Fill in **severity** in the frontmatter\n- [ ] Add reviewer sign-off (IC, Eng lead, DPO if PII, Security lead)\n- [ ] If 72h window applies and we're outside it, document the delay explicitly\n\nSee [\`compliance/governance/incident-report.md\`](compliance/governance/incident-report.md) (the v0.1.30 starter) and [\`DevAudit-Installer/docs/governance-templates.md\`](https://github.com/metasession-dev/DevAudit-Installer/blob/main/docs/governance-templates.md#gdpr--eu-general-data-protection-regulation-2016679) for guidance.\n\nCloses (for this incident) \`ISO29119.3.5.4\` + \`SOC2.CC7.2\`; \`GDPR.Art-33\` + \`Art-34\` close once the triage section is filled in."
|
|
171
|
+
else
|
|
172
|
+
echo "PR #${EXISTING} already open for this incident — branch updated in place."
|
|
173
|
+
fi
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Periodic Review — quarterly auto-generation of compliance/governance/periodic-review.md
|
|
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
|
+
# Why this exists:
|
|
7
|
+
# - SOC 2 CC4.1 (Monitoring of internal controls) and ISO 27001 A.12.1
|
|
8
|
+
# (Operational procedures and responsibilities) require periodic
|
|
9
|
+
# review of controls + procedures. The portal flags `periodic_review`
|
|
10
|
+
# evidence as `expired` after 365 days.
|
|
11
|
+
# - The v0.1.30 starter at compliance/governance/periodic-review.md
|
|
12
|
+
# covers the operator's first attestation. This workflow auto-
|
|
13
|
+
# regenerates it quarterly with locally-derived metrics + a clearly-
|
|
14
|
+
# marked "Review notes (operator fills in)" section, opens a PR so
|
|
15
|
+
# a human reviews + approves before it lands on develop, then
|
|
16
|
+
# compliance-evidence.yml uploads it as `periodic_review` evidence.
|
|
17
|
+
#
|
|
18
|
+
# DevAudit-Installer#98 WS3.
|
|
19
|
+
|
|
20
|
+
name: Periodic Review
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
schedule:
|
|
24
|
+
# Quarterly: 1st of Jan / Apr / Jul / Oct at 09:00 UTC.
|
|
25
|
+
- cron: '0 9 1 */3 *'
|
|
26
|
+
workflow_dispatch:
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: write
|
|
30
|
+
pull-requests: write
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
generate:
|
|
34
|
+
name: Generate quarterly periodic-review.md
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
with:
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
# Need write access for the chore branch.
|
|
41
|
+
token: ${{ secrets.DEVAUDIT_USER_TOKEN || github.token }}
|
|
42
|
+
|
|
43
|
+
- name: Compute review period
|
|
44
|
+
id: period
|
|
45
|
+
run: |
|
|
46
|
+
# End of period = today. Start = ~90 days back (rough quarter).
|
|
47
|
+
PERIOD_END=$(date -u +%Y-%m-%d)
|
|
48
|
+
PERIOD_START=$(date -u -d '90 days ago' +%Y-%m-%d)
|
|
49
|
+
REVIEW_ID=$(date -u +%Y-Q%q 2>/dev/null || date -u +%Y-%m)
|
|
50
|
+
BRANCH="chore/periodic-review-${REVIEW_ID//[^A-Za-z0-9-]/-}"
|
|
51
|
+
echo "period_start=${PERIOD_START}" >> "$GITHUB_OUTPUT"
|
|
52
|
+
echo "period_end=${PERIOD_END}" >> "$GITHUB_OUTPUT"
|
|
53
|
+
echo "review_id=${REVIEW_ID}" >> "$GITHUB_OUTPUT"
|
|
54
|
+
echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT"
|
|
55
|
+
|
|
56
|
+
- name: Derive metrics from repo state
|
|
57
|
+
id: metrics
|
|
58
|
+
env:
|
|
59
|
+
GH_TOKEN: ${{ github.token }}
|
|
60
|
+
PERIOD_START: ${{ steps.period.outputs.period_start }}
|
|
61
|
+
run: |
|
|
62
|
+
# Counts come from local state — no portal API call needed.
|
|
63
|
+
# The portal can render against its own data; this summary is
|
|
64
|
+
# for the operator's review-and-attest moment.
|
|
65
|
+
RTM_REQS=0
|
|
66
|
+
PENDING_RELEASES=0
|
|
67
|
+
APPROVED_RELEASES=0
|
|
68
|
+
INCIDENT_REPORTS=0
|
|
69
|
+
if [ -f compliance/RTM.md ]; then
|
|
70
|
+
RTM_REQS=$(grep -oE '^\| REQ-[0-9]+ ' compliance/RTM.md | wc -l | tr -d ' ')
|
|
71
|
+
fi
|
|
72
|
+
if [ -d compliance/pending-releases ]; then
|
|
73
|
+
PENDING_RELEASES=$(find compliance/pending-releases -maxdepth 1 -name 'RELEASE-TICKET-REQ-*.md' -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
74
|
+
fi
|
|
75
|
+
if [ -d compliance/approved-releases ]; then
|
|
76
|
+
APPROVED_RELEASES=$(find compliance/approved-releases -maxdepth 1 -name 'RELEASE-TICKET-REQ-*.md' -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
77
|
+
fi
|
|
78
|
+
if [ -d compliance/governance ]; then
|
|
79
|
+
INCIDENT_REPORTS=$(find compliance/governance -maxdepth 1 -name 'incident-report*.md' -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
80
|
+
fi
|
|
81
|
+
# CI pass-rate over the period — best-effort via gh.
|
|
82
|
+
CI_RUNS=$(gh run list --branch develop --limit 100 --json conclusion,createdAt 2>/dev/null || echo '[]')
|
|
83
|
+
TOTAL_RUNS=$(echo "$CI_RUNS" | jq --arg since "${PERIOD_START}T00:00:00Z" '[.[] | select(.createdAt >= $since)] | length' 2>/dev/null || echo 0)
|
|
84
|
+
PASSED_RUNS=$(echo "$CI_RUNS" | jq --arg since "${PERIOD_START}T00:00:00Z" '[.[] | select(.createdAt >= $since and .conclusion == "success")] | length' 2>/dev/null || echo 0)
|
|
85
|
+
PASS_RATE="n/a"
|
|
86
|
+
if [ "$TOTAL_RUNS" -gt 0 ]; then
|
|
87
|
+
PASS_RATE="$((100 * PASSED_RUNS / TOTAL_RUNS))%"
|
|
88
|
+
fi
|
|
89
|
+
{
|
|
90
|
+
echo "rtm_reqs=${RTM_REQS}"
|
|
91
|
+
echo "pending_releases=${PENDING_RELEASES}"
|
|
92
|
+
echo "approved_releases=${APPROVED_RELEASES}"
|
|
93
|
+
echo "incident_reports=${INCIDENT_REPORTS}"
|
|
94
|
+
echo "ci_total=${TOTAL_RUNS}"
|
|
95
|
+
echo "ci_passed=${PASSED_RUNS}"
|
|
96
|
+
echo "ci_pass_rate=${PASS_RATE}"
|
|
97
|
+
} >> "$GITHUB_OUTPUT"
|
|
98
|
+
|
|
99
|
+
- name: Render periodic-review.md
|
|
100
|
+
env:
|
|
101
|
+
PERIOD_START: ${{ steps.period.outputs.period_start }}
|
|
102
|
+
PERIOD_END: ${{ steps.period.outputs.period_end }}
|
|
103
|
+
REVIEW_ID: ${{ steps.period.outputs.review_id }}
|
|
104
|
+
RTM_REQS: ${{ steps.metrics.outputs.rtm_reqs }}
|
|
105
|
+
PENDING_RELEASES: ${{ steps.metrics.outputs.pending_releases }}
|
|
106
|
+
APPROVED_RELEASES: ${{ steps.metrics.outputs.approved_releases }}
|
|
107
|
+
INCIDENT_REPORTS: ${{ steps.metrics.outputs.incident_reports }}
|
|
108
|
+
CI_TOTAL: ${{ steps.metrics.outputs.ci_total }}
|
|
109
|
+
CI_PASSED: ${{ steps.metrics.outputs.ci_passed }}
|
|
110
|
+
CI_PASS_RATE: ${{ steps.metrics.outputs.ci_pass_rate }}
|
|
111
|
+
run: |
|
|
112
|
+
mkdir -p compliance/governance
|
|
113
|
+
cat > compliance/governance/periodic-review.md <<EOF
|
|
114
|
+
---
|
|
115
|
+
title: "Periodic Review of Internal Controls (${REVIEW_ID})"
|
|
116
|
+
period_start: "${PERIOD_START}"
|
|
117
|
+
period_end: "${PERIOD_END}"
|
|
118
|
+
reviewer: "REPLACE — name + role"
|
|
119
|
+
last_reviewed_at: "${PERIOD_END}"
|
|
120
|
+
review_cadence_days: 90
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
> ℹ️ Auto-generated by Periodic Review workflow on ${PERIOD_END}.
|
|
124
|
+
> The CI-derived metrics below are filled in automatically. The
|
|
125
|
+
> **Review notes** and **Sign-off** sections still require the
|
|
126
|
+
> human attestation — replace the REPLACE markers before merging
|
|
127
|
+
> this PR. Auditors will reject auto-generated stubs with no
|
|
128
|
+
> human attestation.
|
|
129
|
+
|
|
130
|
+
# Periodic Review of Internal Controls
|
|
131
|
+
|
|
132
|
+
**Framework coverage:** \`SOC2.CC4.1\` (Monitoring of internal controls), \`ISO27001.A.12.1\` (Operational procedures and responsibilities)
|
|
133
|
+
|
|
134
|
+
**Evidence type:** \`periodic_review\` · **Cadence:** every 90 days. The portal flags this evidence as \`expired\` after 365 days.
|
|
135
|
+
|
|
136
|
+
## 1. Review period
|
|
137
|
+
|
|
138
|
+
- **From:** ${PERIOD_START}
|
|
139
|
+
- **To:** ${PERIOD_END}
|
|
140
|
+
- **Reviewer:** REPLACE — name + role
|
|
141
|
+
- **Approver (different person, dual-actor):** REPLACE
|
|
142
|
+
|
|
143
|
+
## 2. Activity summary (auto-derived from repo state)
|
|
144
|
+
|
|
145
|
+
| Metric | Value |
|
|
146
|
+
| ----------------------------------------- | ------------------------- |
|
|
147
|
+
| Total tracked REQs in RTM | ${RTM_REQS} |
|
|
148
|
+
| Pending releases at period end | ${PENDING_RELEASES} |
|
|
149
|
+
| Approved releases (cumulative) | ${APPROVED_RELEASES} |
|
|
150
|
+
| Incident reports on disk | ${INCIDENT_REPORTS} |
|
|
151
|
+
| CI runs on \`develop\` (this period) | ${CI_TOTAL} |
|
|
152
|
+
| CI runs passing | ${CI_PASSED} |
|
|
153
|
+
| CI pass rate | ${CI_PASS_RATE} |
|
|
154
|
+
|
|
155
|
+
## 3. Review notes (operator fills in)
|
|
156
|
+
|
|
157
|
+
REPLACE — qualitative observations about the period. What worked? What didn't? What follow-ups are required? Reference incident reports under \`compliance/governance/incident-report-*.md\` if any.
|
|
158
|
+
|
|
159
|
+
## 4. Control-effectiveness judgement
|
|
160
|
+
|
|
161
|
+
REPLACE — for each material control area, document evidence + reviewer judgement:
|
|
162
|
+
|
|
163
|
+
- **Access control (ISO 27001 A.5.15):** REPLACE — effective / partially / not
|
|
164
|
+
- **Change management (ISO 27001 A.8.32 / SOC 2 CC8.1):** REPLACE
|
|
165
|
+
- **Security testing (ISO 27001 A.8.29):** REPLACE
|
|
166
|
+
- **Logging and monitoring (ISO 27001 A.8.16 / EUAIA Art. 12):** REPLACE
|
|
167
|
+
- **Operational procedures (ISO 27001 A.12.1):** REPLACE
|
|
168
|
+
|
|
169
|
+
## 5. Follow-up actions
|
|
170
|
+
|
|
171
|
+
| # | Finding | Severity | Owner | Due | Issue |
|
|
172
|
+
| - | ------- | -------- | ----- | --- | ----- |
|
|
173
|
+
| 1 | REPLACE | REPLACE | REPLACE | REPLACE | REPLACE |
|
|
174
|
+
|
|
175
|
+
## 6. Sign-off
|
|
176
|
+
|
|
177
|
+
| Role | Name | Date |
|
|
178
|
+
| -------------------------- | ------- | ------- |
|
|
179
|
+
| Reviewer | REPLACE | REPLACE |
|
|
180
|
+
| Approver (dual-actor) | REPLACE | REPLACE |
|
|
181
|
+
| Decision | REPLACE — controls effective / partially effective / not effective |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
_Source data: \`compliance/RTM.md\`, \`compliance/pending-releases/\`, \`compliance/approved-releases/\`, \`compliance/governance/incident-report-*.md\`, GitHub Actions runs on \`develop\` since ${PERIOD_START}._
|
|
186
|
+
EOF
|
|
187
|
+
echo "Generated compliance/governance/periodic-review.md"
|
|
188
|
+
|
|
189
|
+
- name: Open / update review PR
|
|
190
|
+
env:
|
|
191
|
+
GH_TOKEN: ${{ secrets.DEVAUDIT_USER_TOKEN || github.token }}
|
|
192
|
+
BRANCH: ${{ steps.period.outputs.branch }}
|
|
193
|
+
REVIEW_ID: ${{ steps.period.outputs.review_id }}
|
|
194
|
+
run: |
|
|
195
|
+
# Configure git for the bot commit.
|
|
196
|
+
git config user.name 'devaudit-bot'
|
|
197
|
+
git config user.email 'devaudit-bot@users.noreply.github.com'
|
|
198
|
+
# Branch from develop. If a branch from a prior failed run exists,
|
|
199
|
+
# blow it away and start fresh; we always regenerate from the latest
|
|
200
|
+
# repo state.
|
|
201
|
+
git fetch origin develop
|
|
202
|
+
git checkout -B "${BRANCH}" origin/develop
|
|
203
|
+
git add compliance/governance/periodic-review.md
|
|
204
|
+
if git diff --cached --quiet; then
|
|
205
|
+
echo "No change to periodic-review.md — nothing to do."
|
|
206
|
+
exit 0
|
|
207
|
+
fi
|
|
208
|
+
git commit -m "chore(compliance): periodic review ${REVIEW_ID} (auto-generated)" -m "Quarterly periodic-review.md regenerated by Periodic Review workflow." -m "" -m "REPLACE markers in the Review notes / Control-effectiveness / Sign-off sections require human attestation before this PR can merge."
|
|
209
|
+
git push --force-with-lease origin "${BRANCH}"
|
|
210
|
+
# Open PR (or update existing).
|
|
211
|
+
EXISTING=$(gh pr list --head "${BRANCH}" --json number --jq '.[0].number' || true)
|
|
212
|
+
if [ -z "$EXISTING" ]; then
|
|
213
|
+
gh pr create --base develop --head "${BRANCH}" \
|
|
214
|
+
--title "chore(compliance): periodic review ${REVIEW_ID}" \
|
|
215
|
+
--body "Auto-generated quarterly periodic-review for **${REVIEW_ID}** by the \`Periodic Review\` workflow.\n\n**Required before merge:**\n- [ ] Replace \`REPLACE — …\` markers in the **Review notes** section\n- [ ] Fill in control-effectiveness judgement for each control area\n- [ ] Add reviewer + approver names (dual-actor)\n- [ ] List follow-up actions with owners + dates\n\nSee [\`docs/governance-templates.md\`](https://github.com/metasession-dev/DevAudit-Installer/blob/main/docs/governance-templates.md#soc-2--trust-services-criteria) (SDLC repo) for guidance.\n\nCloses \`SOC2.CC4.1\` + \`ISO27001.A.12.1\` for the period once the PR lands on develop."
|
|
216
|
+
else
|
|
217
|
+
echo "PR #${EXISTING} already open for this period — branch updated in place."
|
|
218
|
+
fi
|