@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metasession.co/devaudit-cli",
3
- "version": "0.1.30",
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.30",
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
- > Once Workstream 3 ships (`.github/workflows/periodic-review.yml`), this file will be
13
- > auto-regenerated quarterly with portal metrics. Until then — and to add the human
14
- > attestation the auto-generator can't produce edit this file yourself. Auditors
15
- > will reject unedited stubs. See `docs/governance-templates.md` for guidance.
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 test summary report precise evidence_type=test_report
434
- # (was compliance_document). The portal's Compliance Gates panel
435
- # filters by evidence_type, so the markdown summary belongs in the
436
- # Test Reports gate alongside playwright-report.zip + coverage
437
- # summary. Markdown renders inline (MarkdownRenderer); auditor
438
- # reads pass/fail counts + narrative without downloading the zip.
439
- # devaudit#370 follow-up.
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 test_report compliance/test-summary-report.md \
443
- --category test_report ${FLAGS}
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 summary reportprecise evidence_type=test_report so it
142
- # lands in the portal's Test Reports gate (rendered inline by the
143
- # MarkdownRenderer). devaudit#370 follow-up; same change applied
144
- # in ci.yml's gate-evidence upload step.
141
+ # Project-level Test Summary Reporta 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 (test_report type)"
151
+ echo "Uploading: test-summary-report.md (compliance_document — baseline)"
147
152
  bash scripts/upload-evidence.sh \
148
- {{PROJECT_SLUG}} _compliance-docs test_report compliance/test-summary-report.md \
149
- --category test_report ${FLAGS} --release "${DERIVED_RELEASE}" \
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
- echo "Uploading: ${REQ_ID}/$(basename "$ARTIFACT")"
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}\" compliance_document \"$ARTIFACT\" \
263
- --category planning ${FLAGS} --release \"${REQ_ID}\" \
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 $(basename "$ARTIFACT")"
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