@metasession.co/devaudit-cli 0.3.14 → 0.3.16

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.3.14",
3
+ "version": "0.3.16",
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.14",
36
+ "@metasession.co/devaudit-plugin-sdk": "^0.3.16",
37
37
  "ajv": "^8.20.0",
38
38
  "commander": "^12.1.0",
39
39
  "consola": "^3.2.3",
@@ -155,8 +155,8 @@ AI involvement in Medium or High categories raises risk by one level. The Test S
155
155
  The MoSCoW prioritisation of acceptance criteria maps onto three E2E gates, each enforced at a different point in the workflow:
156
156
 
157
157
  - **Must-tier ACs in the smoke subset** must pass on every push to the integration branch. Blocking — a red smoke gate stops the integration hop.
158
- - **Must-tier ACs in the critical subset** must pass on every PR to the release branch. Blocking — a red critical gate stops the release.
159
- - **Should/Could-tier ACs (full regression)** must pass on the next post-merge run to the release branch OR a hotfix issue is auto-filed. Not pre-merge blocking the safety net is post-hoc triage by the operator within working hours.
158
+ - **Must-tier ACs in the critical subset** must pass on every consumer-enabled PR to the release branch. Blocking — a red critical gate stops that release.
159
+ - **Should/Could-tier ACs (full regression)** are not pre-merge blocking. The consumer chooses a post-merge, scheduled, or manual-dispatch safety net and must triage any recorded failure within working hours.
160
160
 
161
161
  Operator override on a hotfix issue (accept-with-rationale) is logged on the issue itself + carried in the next release's `test-execution-summary.md` design record (devaudit#50). The framework does not permit silently shipping a failing test — every red regression spec ends as either fixed, reverted, or accepted-with-recorded-rationale.
162
162
 
@@ -45,14 +45,14 @@ Full E2E regression on every PR is expensive — a 30+ minute wait per release-P
45
45
  | Tier | Location | When it runs | Wall-clock target | Audit role |
46
46
  | --- | --- | --- | --- | --- |
47
47
  | **smoke** | `e2e/smoke/*.spec.ts` | every push to `$INTEGRATION_BRANCH` (via `ci.yml`) | ~3–5 min | fast feedback on every change |
48
- | **critical** | `e2e/smoke/` + `e2e/critical/*.spec.ts` | PR-to-`$RELEASE_BRANCH` (via `e2e-regression.yml`) | ~10–15 min | release-readiness Must gate |
49
- | **regression** | all `e2e/**/*.spec.ts` | nightly + push-to-`$RELEASE_BRANCH` + `workflow_dispatch` | ~35 min (or your project's full pack) | full audit trail + drift catch |
48
+ | **critical** | `e2e/smoke/` + `e2e/critical/*.spec.ts` | PR-to-`$RELEASE_BRANCH` when the consumer enables `e2e-regression.yml` | ~10–15 min | release-readiness Must gate |
49
+ | **regression** | all `e2e/**/*.spec.ts` | `workflow_dispatch` and any consumer-configured post-merge or scheduled run | ~35 min (or your project's full pack) | full audit trail + drift catch |
50
50
 
51
51
  The mapping to MoSCoW: **Must-priority SRS items live in `e2e/smoke/` (fast feedback) and `e2e/critical/` (release gate); Should/Could items live in `e2e/` and are covered by the regression tier.** The classifier is the developer authoring the spec — see `skills/e2e-test-engineer/SKILL.md` Phase 3 for the decision tree.
52
52
 
53
- **Cost philosophy.** Smoke protects every push from breaking the headline flow. Critical protects every release from a Must-tier regression. Full regression protects the audit trail + catches drift overnight. We accept that a Should/Could-tier regression *can* slip past the PR gate; we catch it on the next post-merge run + auto-file a hotfix issue. The framework prefers this over a 35-min wait on every release because operator velocity matters and the safety net stays intact.
53
+ **Cost philosophy.** Smoke protects every push from breaking the headline flow. Critical protects every release from a Must-tier regression. Full regression protects the audit trail and catches drift when the consumer elects to run it. We accept that a Should/Could-tier regression can sit outside the PR gate; the consumer chooses whether its safety net is post-merge, scheduled, or manual dispatch. The framework does not impose a daily/nightly run.
54
54
 
55
- **Post-merge safety net.** Every push to `$RELEASE_BRANCH` re-runs the full regression. On failure, `e2e-regression.yml` auto-files a `bug, priority:high` issue tagging the merge commit + the failing specs. The operator triages within working hours — hotfix forward, revert the commit, or accept-with-rationale if the failure is environmental. No automated revert (false positives + flakes + UAT-data drift are real classes; an operator triages each individually).
55
+ **Post-merge safety net.** When a consumer enables a post-merge regression run, `e2e-regression.yml` may file a `bug, priority:high` issue tagging the merge commit and failing specs. The operator triages within working hours — hotfix forward, revert the commit, or accept-with-rationale if the failure is environmental. No automated revert: flakes and UAT-data drift require human judgment.
56
56
 
57
57
  **Reference workflow.** A copy-pasteable `e2e-regression.yml` shape lives at `skills/e2e-test-engineer/references/e2e-regression-3-tier.yml`. Adoption is opt-in per consumer (the framework doesn't currently sync this workflow; consumers own their own `e2e-regression.yml`).
58
58
 
@@ -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"
@@ -139,7 +139,7 @@ When designing each scenario, also pick the tier it'll live in. Three tiers map
139
139
  | --- | --- | --- |
140
140
  | **smoke** | `e2e/smoke/*.spec.ts` | Cross-cutting sanity that proves the app is up: login, basic nav, one canonical CRUD per main domain. Runs on every push to the integration branch. Keep small — total smoke wall-clock target is ~3–5 min. |
141
141
  | **critical** | `e2e/critical/*.spec.ts` | Must-priority SRS item that breaks a headline flow if it regresses. Examples: payment authorisation, order completion, admin permission editing, RBAC enforcement on financial surfaces. Runs on PR-to-release-branch. Total critical wall-clock target ~10–15 min (includes smoke). |
142
- | **regression** | `e2e/<area>/*.spec.ts` | Should/Could-priority SRS item, edge cases, less-load-bearing flows. Runs nightly + post-merge + dispatch. Total full pack can be 30+ min; that's the point of the tier. |
142
+ | **regression** | `e2e/<area>/*.spec.ts` | Should/Could-priority SRS item, edge cases, less-load-bearing flows. Runs by dispatch and any consumer-configured post-merge or scheduled cadence. Total full pack can be 30+ min; that is the point of the tier. |
143
143
 
144
144
  Decision tree, applied per scenario:
145
145
 
@@ -229,6 +229,12 @@ Before running the suite (Phase 6), verify that the evidence traceability wiring
229
229
 
230
230
  Also verify the import is present: `import { tagTest } from './helpers/test-tags'` (or the correct relative path from the spec's location to `e2e/helpers/test-tags.ts`).
231
231
 
232
+ 4. **Check release and cycle provenance.** Before handing the work back to `sdlc-implementer`, identify the tracked `REQ-XXX`, the source branch SHA, and the expected SDLC stage/cycle for the run. The CI evidence upload must retain those values so the portal can show the result in the correct numbered cycle for the release that produced it.
233
+ - A passing local run is not portal evidence by itself. Report it as local verification and let the configured CI workflow upload the resulting report and screenshots.
234
+ - For a bundled release, evidence remains attributed to its originating REQ and cycle. The absorbing release may reference that source release through bundle lineage, but must not relabel the source E2E run as newly produced by the absorbing REQ.
235
+ - Confirm the CI invocation has an unambiguous release context (`REQ-XXX` or the documented standalone-housekeeping declaration). A bare-date integration housekeeping run is historical CI only; it must not create an approval-ready E2E evidence set.
236
+ - If the project workflow cannot provide this context, halt and report a workflow-contract gap. Do not claim that screenshots or E2E results will appear correctly on the portal.
237
+
232
238
  **If any check fails:**
233
239
 
234
240
  Halt and report the gap to the user:
@@ -241,7 +247,7 @@ Halt and report the gap to the user:
241
247
 
242
248
  Do **not** proceed to Phase 6 until all gaps are resolved. The user may choose to skip an AC (e.g. API-only, transport-only) — that's valid, but it must be an explicit decision recorded in the test-execution-summary, not an omission.
243
249
 
244
- **Write the evidence-wiring sentinel (devaudit-installer#226).** After all Phase 5½ checks pass (all `@requirement`, `evidenceShot()`, and `tagTest()` calls verified), write a `.e2e-evidence-wired` sentinel file in the repo root so the pre-push hook and `sdlc-implementer` Phase 2 step 5b can verify evidence wiring was validated:
250
+ **Write the evidence-wiring sentinel (devaudit-installer#226).** After all Phase 5½ checks pass (all `@requirement`, `evidenceShot()`, `tagTest()`, and release/cycle provenance checks verified), write a `.e2e-evidence-wired` sentinel file in the repo root so the pre-push hook and `sdlc-implementer` Phase 2 step 5b can verify evidence wiring was validated:
245
251
 
246
252
  ```bash
247
253
  echo "WIRED $(date -u +%Y-%m-%dT%H:%M:%SZ) REQ-XXX" > .e2e-evidence-wired
@@ -442,6 +448,7 @@ Wrap up with a summary the user can drop into the PR or ticket:
442
448
  - Defects filed — count, with links.
443
449
  - Requirements gap reports — count, with details (devaudit-installer#212 Gap 4). These are ACs classified as "impossible to test" or "missing AC" — returned to `sdlc-implementer` for the requirements gap flow, not filed as defects.
444
450
  - Missed requirements — count, with links.
451
+ - **Release provenance** — originating `REQ-XXX`, local run result, expected CI workflow/run, and expected portal stage/cycle. For bundled work, state the source REQ explicitly and that the absorbing release will reference it through lineage rather than overwrite its attribution.
445
452
 
446
453
  **Then feed the test-design record (devaudit#50).** The Stage 3 `test-execution-summary.md` (generated per `3-compile-evidence.md` Step 1a) carries a `## Test design` section at the top. Before Stage 3 finalises the file, populate that section with the design-time decisions this skill made, so the SDLC has a recorded trace that scope was *decided*, not implicit:
447
454
 
@@ -2,8 +2,8 @@
2
2
  #
3
3
  # Copy this into your consumer-owned .github/workflows/e2e-regression.yml
4
4
  # to adopt the 3-tier model: smoke (every develop push, fast) / critical
5
- # (PR-to-main, ~10-15 min target) / regression (nightly + push-to-main +
6
- # dispatch, full audit trail with auto-issue on failure).
5
+ # (consumer-enabled release PR, ~10-15 min target) / regression (post-merge
6
+ # or scheduled only when the consumer opts in, plus dispatch).
7
7
  #
8
8
  # The framework does NOT sync this file automatically — your consumer
9
9
  # owns its e2e-regression.yml. Apply the patterns below to your own
@@ -25,8 +25,6 @@ on:
25
25
  branches: [main] # critical-tier gate before merge
26
26
  push:
27
27
  branches: [main] # full regression after merge; auto-issues on failure
28
- schedule:
29
- - cron: '0 2 * * *' # nightly full regression
30
28
  workflow_dispatch:
31
29
  inputs:
32
30
  specs:
@@ -62,8 +60,8 @@ jobs:
62
60
  run: npx playwright install --with-deps chromium
63
61
 
64
62
  # Decide which Playwright project to run based on the trigger.
65
- # PR-to-main uses critical with smoke fall-back; push-to-main and
66
- # schedule run the full regression project; workflow_dispatch
63
+ # PR-to-main uses critical with smoke fall-back; a consumer-enabled
64
+ # post-merge push runs the full regression project; workflow_dispatch
67
65
  # accepts an optional spec filter.
68
66
  - name: Determine E2E project + spec selector
69
67
  id: select
@@ -307,7 +307,7 @@ _Test fix surfaced by suite drift_
307
307
  > - **Requirement:** none
308
308
  > - **Risk:** LOW
309
309
  > - **Path:** Lightweight (gates → PR review → merge)
310
- > - **Gates/evidence:** quality-gates smoke (default CI Gate 4); no full regression on PR (run via `workflow_dispatch` while iterating, full regression on next nightly)
310
+ > - **Gates/evidence:** quality-gates smoke (default CI Gate 4); no full regression on PR (use `workflow_dispatch` while iterating; any post-merge or scheduled regression is consumer-configured)
311
311
  > - **Your approvals:** PR review only
312
312
  > - **Skipped:** RTM, evidence pack, UAT four-eyes, Production approval
313
313
 
@@ -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 merge.** A human still reviews the PR (separation of duties). There is **no** portal release approval, no UAT four-eyes, no Production gate, and no close-out. Merge once CI is green and the reviewer approves.
345
- 9. **Done.** A housekeeping push produces at most a bare-date release (`vYYYY.MM.DD`) with no approval gate; a doc-only push attaches its docs to the existing `REQ-XXX` release. No further action required — report completion and stop.
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
 
@@ -662,8 +663,8 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
662
663
  **When an external gate hangs or fails for unrelated reasons.** A required gate may fail for reasons outside the change's scope — flaky infra, an unrelated regression test that hangs at hour-plus runtime with no log activity, a known-failing suite. When this happens:
663
664
 
664
665
  1. **Verify it's actually unrelated.** Read the failure (or the lack of one). If it's the change's fault, fix it; this section does not apply.
665
- 2. **Document the rationale on the PR.** A sticky comment naming: which gate, what the failure was, why it's unrelated to the change, what the safety net is (nightly run on `$INTEGRATION_BRANCH`, post-deploy verification, etc.).
666
- 3. **Cancel-and-admin-merge is allowed** when **all three** hold: (a) ≥3 other required gates are green, (b) the change has no scope-overlap with the failing gate (e.g. service-layer fix vs hung UI e2e, or an `E2E: N/A by scope` test-plan), and (c) a fallback verification exists (nightly e2e on `$INTEGRATION_BRANCH`, post-deploy smoke, etc.). If any of the three fail, hold the merge and surface the blocker to the operator.
666
+ 2. **Document the rationale on the PR.** A sticky comment naming: which gate, what the failure was, why it's unrelated to the change, and what concrete fallback verification is available (for example, a consumer-configured regression run or post-deploy verification).
667
+ 3. **Cancel-and-admin-merge is allowed** when **all three** hold: (a) ≥3 other required gates are green, (b) the change has no scope-overlap with the failing gate (e.g. service-layer fix vs hung UI e2e, or an `E2E: N/A by scope` test-plan), and (c) a concrete fallback verification exists (a consumer-configured regression run, manual dispatch, or post-deploy smoke). If any of the three fail, hold the merge and surface the blocker to the operator.
667
668
  4. **Record the decision in the release ticket.** The release ticket's `## Verification` section must mention the cancelled gate by run-ID and the fallback that justifies bypassing it. Auditors look here first.
668
669
 
669
670
  **When the cancel-and-admin-merge conditions are NOT met.** If only one or two of the three conditions hold, the skill cannot bypass the gate. Escalation path:
@@ -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 post-merge compliance steps (devaudit-installer#226).** After the release is marked `released` on the portal, perform the close-out:
703
- 1. **Update RTM status** — change the REQ-XXX row from `TESTED - PENDING SIGN-OFF` to `APPROVED - DEPLOYED` in `compliance/RTM.md`.
704
- 2. **Move release ticket**move `compliance/pending-releases/RELEASE-TICKET-REQ-XXX.md` to `compliance/approved-releases/`. This prevents CI's `upload-evidence` job from treating the REQ as in-scope for subsequent releases.
705
- 3. **Verify portal approval** — confirm the DevAudit portal release record is in `released` status.
706
- 4. **Commit the close-out** `git add compliance/RTM.md compliance/pending-releases/ compliance/approved-releases/ && git commit -m "compliance: [REQ-XXX] close out release — RTM updated to APPROVED - DEPLOYED, ticket moved to approved-releases/"`.
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 done.** After the release PR is merged to `main`, the native agent must re-invoke the skill for Phase 5 close-out (see Phase 5 step 0 below). The merge is not the end of the workflow — post-merge compliance steps are mandatory.
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
 
@@ -137,20 +137,20 @@ Key files to expect:
137
137
 
138
138
  ### 5. Hand off to the sdlc-implementer skill
139
139
 
140
- The changes are staged in the working tree. The remaining steps — invoking the SDLC engine for the sentinel, running local gates, creating a feature branch, committing, pushing, opening a PR, monitoring CI, and handling post-merge housekeeping release stubs — are all owned by the **sdlc-implementer** skill that was synced into `.claude/skills/` by the install/update.
140
+ The changes are staged in the working tree. The remaining steps — invoking the SDLC engine for the sentinel, running local gates, creating a feature branch, committing, pushing, opening a PR, monitoring CI, and recording the correct housekeeping release meaning — are all owned by the **sdlc-implementer** skill that was synced into `.claude/skills/` by the install/update.
141
141
 
142
142
  Invoke the sdlc-implementer skill and tell it:
143
143
 
144
- > **Housekeeping change.** The working tree has uncommitted changes from `devaudit install` (or `devaudit update`). Commit type is `chore:`, no `REQ-XXX`. Use the SDLC lightweight path: invoke the SDLC engine for the sentinel, run local gates, create a `chore/sync-devaudit-sdlc-{version}` branch, commit, push, open a PR targeting `develop`, monitor CI, and guide merge. After merge, CI will auto-generate housekeeping release stubs (release ticket + security summary) remind the operator to review and sign off on those.
144
+ > **Housekeeping change.** The working tree has uncommitted changes from `devaudit install` (or `devaudit update`). Commit type is `chore:`, no `REQ-XXX`. Use the SDLC lightweight path: invoke the SDLC engine for the sentinel, run local gates, create a `chore/sync-devaudit-sdlc-{version}` branch, commit, push, open a PR targeting `develop`, wait for terminal-green checks on the current PR SHA, then guide merge. This is normal integration housekeeping: it has PR review only, creates no tracked approval release, and is absorbed into the next tracked REQ through bundled-change lineage. A standalone housekeeping release is allowed only when the `sdlc-implementer` standalone-exception contract is explicitly satisfied.
145
145
 
146
146
  The skill will:
147
147
  1. Invoke `node SDLC/bin/devaudit-sdlc.js --phase=issue --view` to write the `.sdlc-implementer-invoked` sentinel
148
148
  2. Run local gates (lint, tsc, test)
149
149
  3. Create a `chore/` branch, commit, and push
150
150
  4. Open a PR targeting `develop`
151
- 5. Monitor CI checks
151
+ 5. Monitor CI checks and wait for terminal green on the current PR SHA
152
152
  6. Guide review → merge
153
- 7. Remind the operator about the housekeeping release stub PR that CI auto-opens after merge
153
+ 7. Record the merge as integration history to be bundled into the next tracked REQ, unless the explicit standalone-housekeeping exception was used
154
154
 
155
155
  **Do not** manually run these steps yourself — the skill owns the SDLC ceremony and stays in sync with the framework's evolution. If the skill is not available (e.g. the AI agent doesn't support skills), fall back to the manual steps documented in `SDLC/0-project-setup.md` and the sdlc-implementer skill definition at `.claude/skills/sdlc-implementer/SKILL.md`.
156
156
 
@@ -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
- # Permissions are needed because the housekeeping path now does two
53
- # GitHub mutations:
54
- # 1. pushes a stub branch + opens a PR via `gh pr create`
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
- actions: write # gh workflow run ci.yml --ref develop
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
- # Housekeeping artefact auto-generation (DevAudit-Installer#116 WS3+WS4)
145
- #
146
- # Housekeeping releases (bare-date version) need two operator-authored
147
- # artefacts to clear the portal's release-completeness checklist:
148
- # `RELEASE-TICKET-<version>.md` and `security-summary-<version>.md`.
149
- # When the version resolves to a bare-date AND those artefacts don't
150
- # already exist on develop, generate stubs from the CI gate JSON and
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: Dispatch ci.yml for merged housekeeping stub
245
- if: github.event_name == 'push' && steps.resolve.outputs.skip != 'true' && steps.version.outputs.version != 'skip'
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 }}