@metasession.co/devaudit-cli 0.1.30 → 0.1.31

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.31",
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.31",
37
37
  "commander": "^12.1.0",
38
38
  "consola": "^3.2.3",
39
39
  "env-paths": "^3.0.0",
@@ -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
 
@@ -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