@metasession.co/devaudit-cli 0.3.14 → 0.3.15
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 +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/sdlc/files/_common/scripts/check-release-pr-scope.sh +11 -0
- package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +42 -1
- package/sdlc/files/_common/scripts/standalone-housekeeping-release.sh +98 -0
- package/sdlc/files/_common/scripts/standalone-housekeeping-release.test.sh +45 -0
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +9 -13
- package/sdlc/files/ci/ci.yml.template +20 -0
- package/sdlc/files/ci/compliance-evidence.yml.template +13 -43
- package/sdlc/files/ci/post-deploy-prod.yml.template +32 -3
- package/sdlc/package.json +1 -1
- package/sdlc/src/blueprints/5-deploy-main.raw.md +23 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metasession.co/devaudit-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
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.3.
|
|
36
|
+
"@metasession.co/devaudit-plugin-sdk": "^0.3.15",
|
|
37
37
|
"ajv": "^8.20.0",
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
39
|
"consola": "^3.2.3",
|
|
@@ -73,6 +73,17 @@ if [ "$DECLARED_SCOPE" != "$CURRENT_RELEASE" ]; then
|
|
|
73
73
|
exit 1
|
|
74
74
|
fi
|
|
75
75
|
|
|
76
|
+
if [[ "$CURRENT_RELEASE" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then
|
|
77
|
+
DECLARATION="compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${CURRENT_RELEASE}.json"
|
|
78
|
+
if ! printf '%s\n%s\n' "${PR_TITLE:-}" "${PR_BODY:-}" | grep -Fqi "Standalone housekeeping promotion"; then
|
|
79
|
+
echo "::error::Bare-date releases may only be promoted with an explicit standalone housekeeping exception."
|
|
80
|
+
echo "::error::Add 'Standalone housekeeping promotion' to the PR title or body and include ${DECLARATION}."
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
chmod +x scripts/standalone-housekeeping-release.sh 2>/dev/null || true
|
|
84
|
+
bash scripts/standalone-housekeeping-release.sh validate "$CURRENT_RELEASE" "$DECLARATION"
|
|
85
|
+
fi
|
|
86
|
+
|
|
76
87
|
if [[ "$CURRENT_RELEASE" =~ ^REQ-[0-9]+$ ]]; then
|
|
77
88
|
if [ ! -x scripts/extract-release-metadata.sh ]; then
|
|
78
89
|
chmod +x scripts/extract-release-metadata.sh 2>/dev/null || true
|
|
@@ -39,9 +39,10 @@ assert_grep() {
|
|
|
39
39
|
make_fixture() {
|
|
40
40
|
local dir="$1" subject="$2"
|
|
41
41
|
rm -rf "$dir"
|
|
42
|
-
mkdir -p "$dir/scripts" "$dir/compliance/pending-releases"
|
|
42
|
+
mkdir -p "$dir/scripts" "$dir/compliance/pending-releases" "$dir/compliance/standalone-housekeeping"
|
|
43
43
|
cp "$SCRIPT_DIR/derive-release-version.sh" "$dir/scripts/derive-release-version.sh"
|
|
44
44
|
cp "$SCRIPT_DIR/extract-release-metadata.sh" "$dir/scripts/extract-release-metadata.sh"
|
|
45
|
+
cp "$SCRIPT_DIR/standalone-housekeeping-release.sh" "$dir/scripts/standalone-housekeeping-release.sh"
|
|
45
46
|
cp "$HELPER" "$dir/scripts/check-release-pr-scope.sh"
|
|
46
47
|
chmod +x "$dir/scripts/"*.sh
|
|
47
48
|
(
|
|
@@ -133,5 +134,45 @@ assert_exit "hotfix branch skips" 0 "$CODE"
|
|
|
133
134
|
assert_grep "hotfix skip message" 'Hotfix PR detected' "$OUT"
|
|
134
135
|
echo ""
|
|
135
136
|
|
|
137
|
+
echo "--- Test 5: bare-date releases require an explicit standalone declaration ---"
|
|
138
|
+
make_fixture "$WORK/test5" "chore: standalone housekeeping release"
|
|
139
|
+
cat > "$WORK/test5/compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-v2026.07.18.json" <<'EOF'
|
|
140
|
+
{
|
|
141
|
+
"schemaVersion": 1,
|
|
142
|
+
"version": "v2026.07.18",
|
|
143
|
+
"releaseMode": "standalone_housekeeping",
|
|
144
|
+
"reason": "Urgent operational change cannot wait for the next tracked requirement release."
|
|
145
|
+
}
|
|
146
|
+
EOF
|
|
147
|
+
OUT="$WORK/test5.out"
|
|
148
|
+
if run_check "$WORK/test5" "$OUT" env PR_TITLE="Standalone housekeeping promotion: v2026.07.18" PR_BODY=$'## Release\n- Release: v2026.07.18\n'; then
|
|
149
|
+
CODE=0
|
|
150
|
+
else
|
|
151
|
+
CODE=$?
|
|
152
|
+
fi
|
|
153
|
+
assert_exit "declared standalone housekeeping release passes" 0 "$CODE"
|
|
154
|
+
assert_grep "standalone validation is emitted" 'Standalone housekeeping declaration is valid' "$OUT"
|
|
155
|
+
echo ""
|
|
156
|
+
|
|
157
|
+
echo "--- Test 6: bare-date releases without the exception marker fail ---"
|
|
158
|
+
make_fixture "$WORK/test6" "chore: standalone housekeeping release"
|
|
159
|
+
cat > "$WORK/test6/compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-v2026.07.18.json" <<'EOF'
|
|
160
|
+
{
|
|
161
|
+
"schemaVersion": 1,
|
|
162
|
+
"version": "v2026.07.18",
|
|
163
|
+
"releaseMode": "standalone_housekeeping",
|
|
164
|
+
"reason": "Urgent operational change cannot wait for the next tracked requirement release."
|
|
165
|
+
}
|
|
166
|
+
EOF
|
|
167
|
+
OUT="$WORK/test6.out"
|
|
168
|
+
if run_check "$WORK/test6" "$OUT" env PR_TITLE="Release: v2026.07.18" PR_BODY=$'## Release\n- Release: v2026.07.18\n'; then
|
|
169
|
+
CODE=0
|
|
170
|
+
else
|
|
171
|
+
CODE=$?
|
|
172
|
+
fi
|
|
173
|
+
assert_exit "bare-date release without exception marker fails" 1 "$CODE"
|
|
174
|
+
assert_grep "exception marker failure is clear" 'explicit standalone housekeeping exception' "$OUT"
|
|
175
|
+
echo ""
|
|
176
|
+
|
|
136
177
|
echo "=== check-release-pr-scope.test.sh: $PASS passed, $FAIL failed ==="
|
|
137
178
|
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# standalone-housekeeping-release.sh — validate and promote an explicit
|
|
3
|
+
# standalone-housekeeping exception after a reviewed, green main promotion.
|
|
4
|
+
#
|
|
5
|
+
# Manifest contract:
|
|
6
|
+
# compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-vYYYY.MM.DD.json
|
|
7
|
+
# {
|
|
8
|
+
# "schemaVersion": 1,
|
|
9
|
+
# "version": "vYYYY.MM.DD",
|
|
10
|
+
# "releaseMode": "standalone_housekeeping",
|
|
11
|
+
# "reason": "Why this cannot wait for the next tracked REQ release."
|
|
12
|
+
# }
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
COMMAND="${1:-}"
|
|
17
|
+
VERSION="${2:-}"
|
|
18
|
+
MANIFEST_PATH="${3:-}"
|
|
19
|
+
|
|
20
|
+
usage() {
|
|
21
|
+
echo "Usage: $0 validate <version> <manifest-path>" >&2
|
|
22
|
+
echo " $0 promote <project-slug> <version> <manifest-path>" >&2
|
|
23
|
+
exit 2
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
validate_manifest() {
|
|
27
|
+
local expected_version="$1" path="$2"
|
|
28
|
+
if ! [[ "$expected_version" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then
|
|
29
|
+
echo "Error: ${expected_version} is not a bare-date housekeeping version." >&2
|
|
30
|
+
return 1
|
|
31
|
+
fi
|
|
32
|
+
if [ ! -f "$path" ]; then
|
|
33
|
+
echo "Error: standalone-housekeeping declaration not found: ${path}" >&2
|
|
34
|
+
return 1
|
|
35
|
+
fi
|
|
36
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
37
|
+
echo "Error: jq is required." >&2
|
|
38
|
+
return 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
local schema_version manifest_version release_mode reason
|
|
42
|
+
schema_version=$(jq -r '.schemaVersion // empty' "$path")
|
|
43
|
+
manifest_version=$(jq -r '.version // empty' "$path")
|
|
44
|
+
release_mode=$(jq -r '.releaseMode // empty' "$path")
|
|
45
|
+
reason=$(jq -r '.reason // empty' "$path")
|
|
46
|
+
|
|
47
|
+
[ "$schema_version" = "1" ] || { echo "Error: schemaVersion must be 1." >&2; return 1; }
|
|
48
|
+
[ "$manifest_version" = "$expected_version" ] || { echo "Error: declaration version must exactly match ${expected_version}." >&2; return 1; }
|
|
49
|
+
[ "$release_mode" = "standalone_housekeeping" ] || { echo "Error: releaseMode must be standalone_housekeeping." >&2; return 1; }
|
|
50
|
+
if [ "${#reason}" -lt 20 ]; then
|
|
51
|
+
echo "Error: reason must explain why this cannot wait for the next tracked REQ (minimum 20 characters)." >&2
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case "$COMMAND" in
|
|
57
|
+
validate)
|
|
58
|
+
[ -n "$VERSION" ] && [ -n "$MANIFEST_PATH" ] || usage
|
|
59
|
+
validate_manifest "$VERSION" "$MANIFEST_PATH"
|
|
60
|
+
echo "Standalone housekeeping declaration is valid for ${VERSION}."
|
|
61
|
+
;;
|
|
62
|
+
promote)
|
|
63
|
+
PROJECT_SLUG="${2:-}"
|
|
64
|
+
VERSION="${3:-}"
|
|
65
|
+
MANIFEST_PATH="${4:-}"
|
|
66
|
+
[ -n "$PROJECT_SLUG" ] && [ -n "$VERSION" ] && [ -n "$MANIFEST_PATH" ] || usage
|
|
67
|
+
validate_manifest "$VERSION" "$MANIFEST_PATH"
|
|
68
|
+
: "${DEVAUDIT_BASE_URL:?DEVAUDIT_BASE_URL must be set}"
|
|
69
|
+
: "${DEVAUDIT_API_KEY:?DEVAUDIT_API_KEY must be set}"
|
|
70
|
+
|
|
71
|
+
BASE_URL="${DEVAUDIT_BASE_URL%/}"
|
|
72
|
+
ENCODED_SLUG=$(jq -rn --arg value "$PROJECT_SLUG" '$value|@uri')
|
|
73
|
+
ENCODED_VERSION=$(jq -rn --arg value "$VERSION" '$value|@uri')
|
|
74
|
+
RESOLVED=$(curl -fsS -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
75
|
+
"${BASE_URL}/api/ci/releases/resolve?projectSlug=${ENCODED_SLUG}&versionPrefix=${ENCODED_VERSION}")
|
|
76
|
+
RELEASE_ID=$(jq -r '.latest.id // empty' <<<"$RESOLVED")
|
|
77
|
+
RESOLVED_VERSION=$(jq -r '.latest.version // empty' <<<"$RESOLVED")
|
|
78
|
+
[ -n "$RELEASE_ID" ] || { echo "Error: could not resolve ${VERSION}." >&2; exit 1; }
|
|
79
|
+
[ "$RESOLVED_VERSION" = "$VERSION" ] || { echo "Error: resolve returned ${RESOLVED_VERSION}, not exact ${VERSION}." >&2; exit 1; }
|
|
80
|
+
|
|
81
|
+
REASON=$(jq -r '.reason' "$MANIFEST_PATH")
|
|
82
|
+
PAYLOAD=$(jq -n --arg releaseMode standalone_housekeeping --arg standaloneReason "$REASON" \
|
|
83
|
+
'{releaseMode: $releaseMode, standaloneReason: $standaloneReason}')
|
|
84
|
+
RESPONSE_FILE=$(mktemp)
|
|
85
|
+
trap 'rm -f "$RESPONSE_FILE"' EXIT
|
|
86
|
+
HTTP_CODE=$(curl -sS -o "$RESPONSE_FILE" -w '%{http_code}' \
|
|
87
|
+
-X PATCH "${BASE_URL}/api/ci/releases/${RELEASE_ID}" \
|
|
88
|
+
-H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
89
|
+
-H 'Content-Type: application/json' --data "$PAYLOAD")
|
|
90
|
+
if [ "$HTTP_CODE" != "200" ]; then
|
|
91
|
+
echo "Error: standalone housekeeping promotion failed with HTTP ${HTTP_CODE}." >&2
|
|
92
|
+
sed 's/^/ /' "$RESPONSE_FILE" >&2
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
echo "Promoted ${VERSION} as standalone housekeeping (release ${RELEASE_ID})."
|
|
96
|
+
;;
|
|
97
|
+
*) usage ;;
|
|
98
|
+
esac
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
HELPER="$SCRIPT_DIR/standalone-housekeeping-release.sh"
|
|
6
|
+
WORK=$(mktemp -d)
|
|
7
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
8
|
+
|
|
9
|
+
MANIFEST="$WORK/standalone.json"
|
|
10
|
+
cat > "$MANIFEST" <<'JSON'
|
|
11
|
+
{"schemaVersion":1,"version":"v2026.07.18","releaseMode":"standalone_housekeeping","reason":"Urgent production CI repair cannot wait for the next tracked requirement."}
|
|
12
|
+
JSON
|
|
13
|
+
|
|
14
|
+
bash "$HELPER" validate v2026.07.18 "$MANIFEST"
|
|
15
|
+
|
|
16
|
+
MOCK_BIN="$WORK/bin"
|
|
17
|
+
mkdir -p "$MOCK_BIN"
|
|
18
|
+
cat > "$MOCK_BIN/curl" <<'MOCK'
|
|
19
|
+
#!/usr/bin/env bash
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
if [[ "$*" == *"/resolve?"* ]]; then
|
|
22
|
+
printf '{"latest":{"id":"rel_123","version":"v2026.07.18"}}'
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
output_file=""
|
|
26
|
+
for ((i=1; i <= $#; i++)); do
|
|
27
|
+
if [ "${!i}" = "-o" ]; then
|
|
28
|
+
next=$((i + 1))
|
|
29
|
+
output_file="${!next}"
|
|
30
|
+
fi
|
|
31
|
+
done
|
|
32
|
+
[ -z "$output_file" ] || printf '{"ok":true}' > "$output_file"
|
|
33
|
+
printf '200'
|
|
34
|
+
MOCK
|
|
35
|
+
chmod +x "$MOCK_BIN/curl"
|
|
36
|
+
|
|
37
|
+
PATH="$MOCK_BIN:$PATH" DEVAUDIT_BASE_URL=https://devaudit.example.test DEVAUDIT_API_KEY=mc_test \
|
|
38
|
+
bash "$HELPER" promote fixture-project v2026.07.18 "$MANIFEST"
|
|
39
|
+
|
|
40
|
+
if bash "$HELPER" validate REQ-999 "$MANIFEST" >/dev/null 2>&1; then
|
|
41
|
+
echo "Expected tracked version validation to fail" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
echo "standalone-housekeeping-release: PASS"
|
|
@@ -341,8 +341,9 @@ Reached from Phase 0 for non-tracked change-types. The skill drives this end-to-
|
|
|
341
341
|
- **Post-merge-only CI (older generated workflows — `push: branches: [<integration>]` with no `pull_request:` trigger)** — say so explicitly in the LAST/NEXT sticky: _"no PR-time checks will fire; review + merge is the gate; CI runs post-merge on `$INTEGRATION_BRANCH`."_ Don't poll the PR for checks that won't arrive. The post-merge run (CI Pipeline + Compliance Evidence Upload on the integration branch) is the actual gate; address it via fix-forward if it fails.
|
|
342
342
|
|
|
343
343
|
Either way, never bypass a gate (no `--no-verify`, no `--admin` merge of a red required check); the only difference is **where** you wait for the gate to fire — before merge vs. after merge.
|
|
344
|
-
8. **Guide review
|
|
345
|
-
9. **
|
|
344
|
+
8. **Guide review -> merge.** A human still reviews the PR. Merge only after all required checks are terminal green on the current PR head SHA; queued, running, stale, cancelled, unexpectedly skipped, or failed checks are not green. There is no portal release approval, UAT four-eyes, Production gate, or standalone close-out on the normal lightweight path.
|
|
345
|
+
9. **Record the correct release meaning.** A housekeeping push produces at most a bare-date release (`vYYYY.MM.DD`) that is integration/history, not an active approval by default. It waits on `$INTEGRATION_BRANCH` until the next tracked release absorbs it through the required bundled-changes markdown/JSON manifest and portal lineage. A doc-only push attaches documents to the existing `REQ-XXX` release.
|
|
346
|
+
10. **Standalone exception.** Only when housekeeping cannot reasonably wait for the next tracked release may the skill prepare a standalone promotion. It must state `Standalone housekeeping promotion` and why in the release PR, add `compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-vYYYY.MM.DD.json` with `schemaVersion: 1`, the exact derived bare-date version, `releaseMode: standalone_housekeeping`, and a specific reason. It must obtain terminal-green CI and PR review. The installed release-scope check validates the declaration before merge; post-deploy uploads it as release-ticket evidence and changes the portal row to standalone housekeeping. Use portal approval only if project policy explicitly opts in. If the installed workflow cannot implement that policy, halt and keep the change on the normal integration path.
|
|
346
347
|
|
|
347
348
|
### Phase 1 — Plan (SDLC stage 1)
|
|
348
349
|
|
|
@@ -699,16 +700,11 @@ Invoked separately by the user after UAT activity on the portal. Trigger: "resum
|
|
|
699
700
|
- If `released` (Option B): PATCH directly to `released` — `PATCH /releases/<version>` with `{"status": "released"}`.
|
|
700
701
|
- If `prod_review` (Option A, default): after `post-deploy-prod.yml` completes, the release is at `prod_review`. Update the sticky to "Phase 5 — production deployed; awaiting prod approval on portal" and halt. The operator clicks "Approve Production" (`prod_review` → `prod_approved`) then "Mark as Released" (`prod_approved` → `released`) in the portal. On next `resume REQ-XXX`, the skill reads the portal state — if `released`, finalise (close issue, update sticky to terminal). If `prod_approved`, prompt the operator to click "Mark as Released". If still `prod_review`, report "awaiting production approval" and stop.
|
|
701
702
|
- Comment on the issue: "Released. Production smoke evidence: <link>." (only after release status is `released`)
|
|
702
|
-
- **Phase 5 close-out — mandatory
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
5. **Push the close-out commit** to `$INTEGRATION_BRANCH`.
|
|
708
|
-
|
|
709
|
-
These steps are mandatory — the native agent must not treat "PR merged" or "release marked Released" as completion without performing the close-out. Without moving the release ticket to `approved-releases/`, CI continues uploading evidence for the REQ on every subsequent release, polluting the portal's audit trail.
|
|
710
|
-
- **Update SDLC status sticky** to the terminal state: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 5 complete — release marked Released; production smoke evidence uploaded; close-out committed (RTM → APPROVED - DEPLOYED, ticket → approved-releases/)" "Done — close issue + retire feature branch (sdlc-implementer halts)"`.
|
|
711
|
-
- Close the issue.
|
|
703
|
+
- **Phase 5 close-out — mandatory automated reconciliation.** After the portal release is `released`, wait for the portal's `repository_dispatch('release-closed')` event to open `chore/close-out-REQ-XXX` against `$INTEGRATION_BRANCH`. Verify the PR updates the RTM, moves the ticket to `approved-releases/`, and moves manifest-listed predecessors to `superseded-releases/`. Review and merge the close-out PR only after its required checks are terminal green on its current head SHA. If no PR appears, manually dispatch the Release Close-out workflow. Use `scripts/close-out-release.sh` locally only for documented recovery/catch-up; never make a normal direct close-out commit to a protected branch.
|
|
704
|
+
|
|
705
|
+
These steps are mandatory — "PR merged" or "release marked Released" is not complete until the automated close-out PR has merged or a documented fallback reconciliation is complete. Without it, CI can keep uploading evidence against a completed REQ and pollute the audit trail.
|
|
706
|
+
- **Update SDLC status sticky** to the terminal state: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 5 complete — release marked Released; production smoke evidence uploaded; automated close-out reconciled RTM and release ticket" "Done — close issue + retire feature branch (sdlc-implementer halts)"`.
|
|
707
|
+
- Close the issue only after close-out reconciliation completes.
|
|
712
708
|
- If production smoke fails: do NOT mark as Released. **Delegate incident filing to `governance-doc-author` (devaudit-installer#210 §6c)** — invoke `Skill(name: "governance-doc-author", args: "Production smoke failure for REQ-XXX. File an incident issue with the `incident` label + `### Framework attribution` section (ISO29119.3.5.4 + SOC2.CC7.2), severity `blocker`. Include the production URL, git SHA, testCycleId (CI run ID), and smoke results in the issue body.")`. Page the on-call per the project's incident playbook, follow the rollback plan from the implementation plan. If the rollback also fails: update the sticky to "Phase 5 CRITICAL — production smoke failed AND rollback failed. Production is in a broken state. Operator action — page on-call immediately, engage hosting platform support, declare incident per the project's incident playbook. This is beyond the skill's scope." Do NOT attempt further automated remediation. **Update the sticky** to reflect the incident state: `… "Phase 5 BLOCKED — production smoke failed; INCIDENT issue #N filed" "Operator action — read INCIDENT #N + execute rollback per plan"`.
|
|
713
709
|
|
|
714
710
|
- **Changes requested** → run change-request loop:
|
|
@@ -769,7 +765,7 @@ The skill re-reads state from the filesystem and continues from where it left of
|
|
|
769
765
|
|
|
770
766
|
The native agent must NOT continue to the next phase (Phase 3 → Phase 4, Phase 2 → Phase 3) without re-invoking the skill. The only exception is the skill's own auto-continue steps (Phase 2 step 11) where the skill explicitly says it will continue to the next phase in the same turn.
|
|
771
767
|
|
|
772
|
-
**PR merged to main
|
|
768
|
+
**PR merged to main is not done.** After the release PR is merged, Phase 5 waits for deploy/host verification, portal `released`, and the automated close-out PR back to the integration branch. The merge is not the end of the workflow.
|
|
773
769
|
|
|
774
770
|
### Commit-history hygiene recovery (devaudit-installer#315)
|
|
775
771
|
|
|
@@ -336,6 +336,26 @@ jobs:
|
|
|
336
336
|
--environment uat --category planning \
|
|
337
337
|
--git-sha ${{ github.sha }} --branch ${{ github.ref_name }} || true
|
|
338
338
|
|
|
339
|
+
- name: Upload standalone housekeeping declaration
|
|
340
|
+
if: env.DEVAUDIT_BASE_URL != '' && steps.version.outputs.version != 'skip'
|
|
341
|
+
run: |
|
|
342
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
343
|
+
if ! [[ "$VERSION" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then
|
|
344
|
+
exit 0
|
|
345
|
+
fi
|
|
346
|
+
DECLARATION="compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${VERSION}.json"
|
|
347
|
+
if [ ! -f "$DECLARATION" ]; then
|
|
348
|
+
echo "${VERSION} is integration housekeeping; no standalone declaration uploaded."
|
|
349
|
+
exit 0
|
|
350
|
+
fi
|
|
351
|
+
chmod +x scripts/standalone-housekeeping-release.sh scripts/upload-evidence.sh 2>/dev/null || true
|
|
352
|
+
bash scripts/standalone-housekeeping-release.sh validate "$VERSION" "$DECLARATION"
|
|
353
|
+
bash scripts/upload-evidence.sh \
|
|
354
|
+
{{PROJECT_SLUG}} "$VERSION" release_ticket "$DECLARATION" \
|
|
355
|
+
--release "$VERSION" --create-release-if-missing \
|
|
356
|
+
--environment uat --category release_artifact --sdlc-stage 2 \
|
|
357
|
+
--git-sha ${{ github.sha }} --branch ${{ github.ref_name }}
|
|
358
|
+
|
|
339
359
|
- name: Generate and upload bundled changes (REQ releases only)
|
|
340
360
|
if: env.DEVAUDIT_BASE_URL != '' && steps.version.outputs.version != 'skip'
|
|
341
361
|
run: |
|
|
@@ -49,26 +49,11 @@ jobs:
|
|
|
49
49
|
# double-upload every compliance doc.
|
|
50
50
|
if: github.event_name != 'workflow_run'
|
|
51
51
|
runs-on: {{RUNNER}}
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# 2. dispatches `ci.yml` on develop after that stub PR merges, so
|
|
56
|
-
# compliance-only housekeeping releases still get the gate evidence
|
|
57
|
-
# the portal approval path currently requires (#361)
|
|
58
|
-
#
|
|
59
|
-
# Without these the github-actions[bot] token gets HTTP 403 on the push,
|
|
60
|
-
# PR create, or workflow dispatch and the housekeeping path leaves a red
|
|
61
|
-
# badge or an unapprovable release behind.
|
|
62
|
-
#
|
|
63
|
-
# `DEVAUDIT_USER_TOKEN` is no longer consulted by this workflow (the
|
|
64
|
-
# auto-stub step now uses `github.token` directly — see the env block
|
|
65
|
-
# on that step). The permissions below grant everything `gh pr create`
|
|
66
|
-
# needs; preferring a (potentially stale) PAT was the cause of the
|
|
67
|
-
# 2026-06-07 401 chain on this issue.
|
|
52
|
+
# #409: this workflow only uploads evidence. Normal housekeeping is
|
|
53
|
+
# integration history, so it must not create PRs, dispatch gates, or
|
|
54
|
+
# receive write-scoped GitHub credentials.
|
|
68
55
|
permissions:
|
|
69
|
-
|
|
70
|
-
contents: write # push the auto-PR branch
|
|
71
|
-
pull-requests: write # gh pr create
|
|
56
|
+
contents: read
|
|
72
57
|
env:
|
|
73
58
|
DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
|
|
74
59
|
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
@@ -141,28 +126,13 @@ jobs:
|
|
|
141
126
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
142
127
|
echo "Release version: ${VERSION}"
|
|
143
128
|
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
# open a PR that the operator reviews + signs off + merges. Once
|
|
152
|
-
# merged, the next compliance-evidence.yml run uploads them as
|
|
153
|
-
# evidence and the matrix flips both items to ✓.
|
|
154
|
-
#
|
|
155
|
-
# devaudit#284 — release reconciliation merges (from close-out-release.yml)
|
|
156
|
-
# carry a `Release-Closeout: REQ-XXX` marker in the commit body.
|
|
157
|
-
# derive-release-version.sh detects this marker and emits an empty
|
|
158
|
-
# version, which this workflow converts to the explicit `skip` sentinel.
|
|
159
|
-
# Reconciliation pushes therefore never create housekeeping stubs and
|
|
160
|
-
# never attach fresh evidence to an already released tracked record.
|
|
161
|
-
#
|
|
162
|
-
# Mirrors the `incident-export.yml` pattern: auto-PR, operator fills
|
|
163
|
-
# in sign-off, merge to land.
|
|
164
|
-
- name: Auto-generate housekeeping stubs (if needed)
|
|
165
|
-
if: steps.resolve.outputs.skip != 'true' && steps.version.outputs.version != 'skip'
|
|
129
|
+
# #409: normal bare-date changes are integration CI history, not portal
|
|
130
|
+
# releases awaiting approval. The old auto-generated ticket/security
|
|
131
|
+
# summary path is deliberately disabled. An independently promoted
|
|
132
|
+
# exception must carry the reviewed standalone declaration validated by
|
|
133
|
+
# check-release-pr-scope.sh and post-deploy-prod.yml.
|
|
134
|
+
- name: Legacy housekeeping approval path (disabled)
|
|
135
|
+
if: ${{ false }}
|
|
166
136
|
env:
|
|
167
137
|
# Use the workflow's GITHUB_TOKEN directly. The `permissions:`
|
|
168
138
|
# block on the job grants `contents: write` + `pull-requests:
|
|
@@ -241,8 +211,8 @@ jobs:
|
|
|
241
211
|
echo "PR #${EXISTING} already open for ${VERSION} — branch updated in place."
|
|
242
212
|
fi
|
|
243
213
|
|
|
244
|
-
- name:
|
|
245
|
-
if:
|
|
214
|
+
- name: Legacy housekeeping gate dispatch (disabled)
|
|
215
|
+
if: ${{ false }}
|
|
246
216
|
env:
|
|
247
217
|
GH_TOKEN: ${{ github.token }}
|
|
248
218
|
VERSION: ${{ steps.version.outputs.version }}
|
|
@@ -97,10 +97,18 @@ jobs:
|
|
|
97
97
|
# release ticket (the same set the dev/UAT pipeline versioned via
|
|
98
98
|
# derive-release-version.sh → REQ-XXX). A bundled develop→main PR
|
|
99
99
|
# carries several; promote ALL of them, not just the first. A manual
|
|
100
|
-
# dispatch can target one via the `release` input.
|
|
101
|
-
#
|
|
100
|
+
# dispatch can target one via the `release` input. A ticketless
|
|
101
|
+
# main promotion is only valid when its explicit standalone
|
|
102
|
+
# housekeeping declaration is present and valid.
|
|
102
103
|
if [ -n "${RELEASE_INPUT}" ]; then
|
|
103
104
|
REQS="${RELEASE_INPUT}"
|
|
105
|
+
if [[ "$REQS" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then
|
|
106
|
+
chmod +x scripts/standalone-housekeeping-release.sh 2>/dev/null || true
|
|
107
|
+
DECLARATION="compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${REQS}.json"
|
|
108
|
+
bash scripts/standalone-housekeeping-release.sh validate "$REQS" "$DECLARATION"
|
|
109
|
+
echo "STANDALONE_DECLARATION=${DECLARATION}" >> "$GITHUB_ENV"
|
|
110
|
+
echo "TERMINAL_STATUS=released" >> "$GITHUB_ENV"
|
|
111
|
+
fi
|
|
104
112
|
else
|
|
105
113
|
REQS=""
|
|
106
114
|
if [ -d compliance/pending-releases ]; then
|
|
@@ -111,7 +119,20 @@ jobs:
|
|
|
111
119
|
fi
|
|
112
120
|
REQS=$(echo ${REQS} | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/[[:space:]]*$//')
|
|
113
121
|
if [ -z "${REQS}" ]; then
|
|
114
|
-
|
|
122
|
+
chmod +x scripts/derive-release-version.sh scripts/standalone-housekeeping-release.sh 2>/dev/null || true
|
|
123
|
+
VERSION=$(bash scripts/derive-release-version.sh)
|
|
124
|
+
DECLARATION="compliance/standalone-housekeeping/STANDALONE-HOUSEKEEPING-${VERSION}.json"
|
|
125
|
+
if [ -f "$DECLARATION" ]; then
|
|
126
|
+
bash scripts/standalone-housekeeping-release.sh validate "$VERSION" "$DECLARATION"
|
|
127
|
+
REQS="$VERSION"
|
|
128
|
+
echo "STANDALONE_DECLARATION=${DECLARATION}" >> "$GITHUB_ENV"
|
|
129
|
+
# Standalone housekeeping is reviewed in its GitHub promotion
|
|
130
|
+
# PR. It does not enter the portal UAT/production queue unless
|
|
131
|
+
# a project deliberately supplies a tracked REQ instead.
|
|
132
|
+
echo "TERMINAL_STATUS=released" >> "$GITHUB_ENV"
|
|
133
|
+
else
|
|
134
|
+
echo "No tracked release ticket or standalone declaration: ordinary housekeeping has no portal promotion."
|
|
135
|
+
fi
|
|
115
136
|
fi
|
|
116
137
|
fi
|
|
117
138
|
echo "In-scope releases to promote: ${REQS}"
|
|
@@ -413,6 +434,9 @@ jobs:
|
|
|
413
434
|
# Carry the release ticket into the production environment so the
|
|
414
435
|
# prod-review gate is self-contained.
|
|
415
436
|
TICKET=""
|
|
437
|
+
if [ -n "${STANDALONE_DECLARATION:-}" ] && [ "$VERSION" = "${REQS}" ]; then
|
|
438
|
+
TICKET="$STANDALONE_DECLARATION"
|
|
439
|
+
fi
|
|
416
440
|
for DIR in compliance/pending-releases compliance/approved-releases; do
|
|
417
441
|
if [ -f "${DIR}/RELEASE-TICKET-${VERSION}.md" ]; then
|
|
418
442
|
TICKET="${DIR}/RELEASE-TICKET-${VERSION}.md"; break
|
|
@@ -447,6 +471,11 @@ jobs:
|
|
|
447
471
|
fi
|
|
448
472
|
# Advance status (idempotent — re-PATCHing an already-promoted release is a no-op).
|
|
449
473
|
if [ -n "$RELEASE_ID" ]; then
|
|
474
|
+
if [ -n "${STANDALONE_DECLARATION:-}" ] && [ "$VERSION" = "${REQS}" ]; then
|
|
475
|
+
chmod +x scripts/standalone-housekeeping-release.sh 2>/dev/null || true
|
|
476
|
+
bash scripts/standalone-housekeeping-release.sh promote \
|
|
477
|
+
"$PROJECT_SLUG" "$VERSION" "$STANDALONE_DECLARATION"
|
|
478
|
+
fi
|
|
450
479
|
curl -s -o /dev/null -w " ${VERSION} status patch: HTTP %{http_code}\n" \
|
|
451
480
|
-X PATCH "${BASE}/api/ci/releases/${RELEASE_ID}" \
|
|
452
481
|
-H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
package/sdlc/package.json
CHANGED
|
@@ -45,13 +45,13 @@ gh pr merge [PR-NUMBER] --merge --delete-branch=false
|
|
|
45
45
|
|
|
46
46
|
**Do NOT delete `develop`** — it's the permanent working branch.
|
|
47
47
|
|
|
48
|
-
### Step 2:
|
|
48
|
+
### Step 2: Preserve GitFlow
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
Normal release synchronization is performed by the portal-dispatched close-out
|
|
51
|
+
PR after the release reaches `released`; do not directly merge the release
|
|
52
|
+
branch into the integration branch. A real hotfix is the exception: after its
|
|
53
|
+
reviewed merge to the release branch, open the mandatory `backmerge/*` PR to the
|
|
54
|
+
integration branch and merge it only after terminal-green checks.
|
|
55
55
|
|
|
56
56
|
### Step 3: Verify Production Deployment
|
|
57
57
|
|
|
@@ -149,11 +149,11 @@ The backend stores both with reviewer identity, SHA, and timestamp. This satisfi
|
|
|
149
149
|
|
|
150
150
|
#### Steps
|
|
151
151
|
|
|
152
|
-
1. Wait for `post-deploy-prod.yml` to complete
|
|
152
|
+
1. Wait for `post-deploy-prod.yml` to complete successfully **and** for its host-deployment check to confirm terminal success for the merged SHA.
|
|
153
153
|
2. Open the release in DevAudit: `https://[DEVAUDIT_BASE_URL]/projects/[PROJECT_SLUG]/releases/[releaseId]`.
|
|
154
|
-
3. Review the
|
|
154
|
+
3. Review the production deployment and smoke cycle records, their evidence, and any post-deploy actions logged in the release ticket. Do not approve while either cycle or the host deployment remains queued/in progress.
|
|
155
155
|
4. Click **Approve Production** — status transitions to `prod_approved`.
|
|
156
|
-
5. Click **Mark as Released** — status transitions to `released`.
|
|
156
|
+
5. Click **Mark as Released** — status transitions to `released`. This dispatches the automated close-out flow.
|
|
157
157
|
|
|
158
158
|
If the smoke results look wrong or a manual verification fails, click **Reject** on the production approval and follow the Rollback procedure below before retrying.
|
|
159
159
|
|
|
@@ -161,41 +161,21 @@ If the smoke results look wrong or a manual verification fails, click **Reject**
|
|
|
161
161
|
|
|
162
162
|
`approval.mode` is checked again here. `dual_actor` means the post-deploy approver must differ from the release creator. `solo_with_gap` accepts self-approval but records the control gap. `auto_low_risk` allows LOW-risk requirements to auto-advance through both transitions on workflow completion; MEDIUM/HIGH always require a human click.
|
|
163
163
|
|
|
164
|
-
### Step 6:
|
|
165
|
-
|
|
166
|
-
> **Automated path (preferred).** Run the synced helper instead of doing this by hand — it flips the ticket Status → `RELEASED` (+ backlinks the release PR and records the sign-off), flips the matching `RTM.md` row, and `git mv`s the ticket to `approved-releases/`, then stages the changes for you to commit:
|
|
167
|
-
>
|
|
168
|
-
> ```bash
|
|
169
|
-
> ./scripts/close-out-release.sh REQ-XXX --release-pr <release-PR-#>
|
|
170
|
-
> git add -A && git commit -m "docs(compliance): close out REQ-XXX release ticket (RELEASED)" && git push origin develop
|
|
171
|
-
> ```
|
|
172
|
-
>
|
|
173
|
-
> It is **idempotent** (a no-op if already closed out) and, when `DEVAUDIT_API_KEY` + `DEVAUDIT_BASE_URL` are set, **refuses** unless the portal reports the release as `released` (so you can't flip the local tree ahead of the Production approval). The `close-out-release.yml` workflow runs the same script automatically when the portal marks a release released (and is `workflow_dispatch`-able for catch-up). The manual steps below are the fallback / reference for what the script does.
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
mv compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md compliance/approved-releases/
|
|
177
|
-
```
|
|
164
|
+
### Step 6: Automated Close-out (Tracked Requirements Only)
|
|
178
165
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
166
|
+
When the portal status becomes `released`, it dispatches `release-closed` to the
|
|
167
|
+
consumer repository. `close-out-release.yml` then opens a
|
|
168
|
+
`chore/close-out-REQ-XXX` PR to the configured integration branch. That PR is
|
|
169
|
+
the canonical reconciliation record: it updates the RTM, moves the release
|
|
170
|
+
ticket to `approved-releases/`, and moves manifest-listed absorbed predecessors
|
|
171
|
+
to `superseded-releases/`.
|
|
183
172
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
| [date] | PROD post-deploy verification | [who] | Health + security checks passed on PROD |
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
git add compliance/RTM.md compliance/approved-releases/ compliance/evidence/REQ-XXX/
|
|
195
|
-
git rm compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md 2>/dev/null
|
|
196
|
-
git commit -m "compliance: [REQ-XXX] approved and deployed - PR #[number]"
|
|
197
|
-
git push origin develop
|
|
198
|
-
```
|
|
173
|
+
Review and merge the close-out PR after its required checks are terminal green
|
|
174
|
+
on its current head SHA. It is an administrative reconciliation PR, not a new
|
|
175
|
+
production release. If the dispatch did not arrive, manually dispatch the
|
|
176
|
+
Release Close-out workflow. Run `scripts/close-out-release.sh` locally only for
|
|
177
|
+
documented recovery or historical catch-up; never create a normal direct
|
|
178
|
+
close-out commit on a protected branch.
|
|
199
179
|
|
|
200
180
|
### Step 7: Close the GitHub Issue
|
|
201
181
|
|
|
@@ -207,17 +187,10 @@ gh issue close [ISSUE-NUMBER] --comment "Implemented in PR #[PR-NUMBER] (REQ-XXX
|
|
|
207
187
|
|
|
208
188
|
This is the final traceability link: Issue → Requirement → PR → Deployment → Issue closed.
|
|
209
189
|
|
|
210
|
-
### Step 8: Final Sync
|
|
211
|
-
|
|
212
|
-
```bash
|
|
213
|
-
git checkout main && git merge develop --no-edit && git push origin main
|
|
214
|
-
git checkout develop
|
|
215
|
-
```
|
|
216
|
-
|
|
217
190
|
## Rollback
|
|
218
191
|
|
|
219
192
|
1. **Hosting dashboard:** Redeploy previous version
|
|
220
|
-
2. **Git:** `
|
|
193
|
+
2. **Git:** create `hotfix/revert-REQ-XXX` from the release branch, revert the merge, and open a reviewed PR to the release branch. After terminal-green checks and merge, open the mandatory backmerge PR to integration.
|
|
221
194
|
3. **Document:** Add rollback entry to release ticket audit trail
|
|
222
195
|
4. **Correlate with incident (DevAudit-Installer#210 §10a):** If the rollback was triggered by a post-deploy smoke failure, the `post-deploy-prod.yml` workflow files an incident issue with the `incident` label. Reference this incident in the rollback commit message (`Fixes #N` or a comment on the issue) so the incident report captures the containment action. This ensures `incident-export.yml` exports the full incident timeline including the rollback.
|
|
223
196
|
|