@metasession.co/devaudit-cli 0.3.17 → 0.3.19

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.
Files changed (45) hide show
  1. package/dist/index.js +36 -34
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/scripts/upload-evidence.sh +23 -19
  5. package/sdlc/files/_common/governance/incident-report.md.template +6 -1
  6. package/sdlc/files/_common/governance/nil-incident-report.md.template +5 -2
  7. package/sdlc/files/_common/scripts/check-host-deployment.sh +58 -8
  8. package/sdlc/files/_common/scripts/check-host-deployment.test.sh +35 -3
  9. package/sdlc/files/_common/scripts/check-release-pr-scope.sh +8 -1
  10. package/sdlc/files/_common/scripts/check-release-pr-scope.test.sh +61 -12
  11. package/sdlc/files/_common/scripts/check-self-hosted-runner.sh +103 -0
  12. package/sdlc/files/_common/scripts/check-self-hosted-runner.test.sh +74 -0
  13. package/sdlc/files/_common/scripts/derive-release-version.sh +7 -9
  14. package/sdlc/files/_common/scripts/derive-release-version.test.sh +18 -18
  15. package/sdlc/files/_common/scripts/generate-bundled-changes.sh +24 -3
  16. package/sdlc/files/_common/scripts/generate-bundled-changes.test.sh +9 -5
  17. package/sdlc/files/_common/scripts/reconcile-railway-deployment.sh +37 -0
  18. package/sdlc/files/_common/scripts/reconcile-railway-deployment.test.sh +30 -0
  19. package/sdlc/files/_common/scripts/record-uat-execution.sh +195 -0
  20. package/sdlc/files/_common/scripts/record-uat-execution.test.sh +144 -0
  21. package/sdlc/files/_common/scripts/render-test-executions.sh +146 -0
  22. package/sdlc/files/_common/scripts/{render-test-cycles.test.sh → render-test-executions.test.sh} +12 -18
  23. package/sdlc/files/_common/scripts/{report-test-cycle.sh → report-test-execution.sh} +66 -74
  24. package/sdlc/files/_common/scripts/report-test-execution.test.sh +177 -0
  25. package/sdlc/files/_common/scripts/submit-for-uat-review.sh +21 -0
  26. package/sdlc/files/_common/scripts/upload-evidence.sh +23 -19
  27. package/sdlc/files/_common/scripts/validate-commits.sh +6 -4
  28. package/sdlc/files/_common/scripts/validate-commits.test.sh +15 -0
  29. package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +9 -7
  30. package/sdlc/files/_common/skills/e2e-test-engineer/references/e2e-regression-3-tier.yml +62 -10
  31. package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +19 -16
  32. package/sdlc/files/ci/ci.yml.template +86 -46
  33. package/sdlc/files/ci/compliance-evidence.yml.template +173 -49
  34. package/sdlc/files/ci/feature-e2e.yml.template +17 -17
  35. package/sdlc/files/ci/incident-export.yml.template +18 -4
  36. package/sdlc/files/ci/post-deploy-prod.yml.template +92 -48
  37. package/sdlc/files/ci/python/ci.yml.template +14 -14
  38. package/sdlc/files/ci/quality-gates-provenance.yml.template +3 -0
  39. package/sdlc/files/ci/reconcile-deployment.yml.template +59 -0
  40. package/sdlc/package.json +1 -1
  41. package/sdlc/src/blueprints/3-compile-evidence.raw.md +10 -10
  42. package/sdlc/src/blueprints/4-submit-for-review.raw.md +1 -1
  43. package/sdlc/src/blueprints/5-deploy-main.raw.md +1 -1
  44. package/sdlc/src/blueprints/implementing-an-sdlc-issue.raw.md +3 -3
  45. package/sdlc/files/_common/scripts/render-test-cycles.sh +0 -227
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: sdlc-implementer
3
- description: Take a GitHub issue end-to-end through the Metasession SDLC. Opens with a Workflow Triage step (Phase 0) that classifies the change and routes it — tracked work continues into the full cycle; housekeeping/trivial/doc-only is driven to merge down a lightweight path (same step-by-step guidance, no tracked ceremony). Use when the user wants to implement a single GitHub issue as a complete SDLC cycle — Phase 1 (classify risk, write implementation plan, update RTM) through Phase 4 (open PR, request UAT review on the portal), then halt; and Phase 5 (merge, post-deploy smoke evidence, mark Released, or change-request loop) on resume. Trigger phrases — "implement issue #N", "fix issue #N", "do issue #N", "implement #N", "implement issue #N under the SDLC", "run the SDLC for issue #N", "automate REQ-XXX from issue to release", "do the SDLC stages for [issue]". Resume phrase — "resume REQ-XXX". MUST delegate end-to-end and visual-regression test work to the e2e-test-engineer skill in Phase 2; never authors e2e tests directly. Do NOT use for partial work — for stage-1 planning only, run the manual walkthrough; for test work alone, invoke e2e-test-engineer directly.
3
+ description: Take a GitHub issue end-to-end through the Metasession SDLC. Opens with a Workflow Triage step (Phase 0) that classifies the change and routes it — tracked work continues into the full execution; housekeeping/trivial/doc-only is driven to merge down a lightweight path (same step-by-step guidance, no tracked ceremony). Use when the user wants to implement a single GitHub issue as a complete SDLC execution — Phase 1 (classify risk, write implementation plan, update RTM) through Phase 4 (open PR, request UAT review on the portal), then halt; and Phase 5 (merge, post-deploy smoke evidence, mark Released, or change-request loop) on resume. Trigger phrases — "implement issue #N", "fix issue #N", "do issue #N", "implement #N", "implement issue #N under the SDLC", "run the SDLC for issue #N", "automate REQ-XXX from issue to release", "do the SDLC stages for [issue]". Resume phrase — "resume REQ-XXX". MUST delegate end-to-end and visual-regression test work to the e2e-test-engineer skill in Phase 2; never authors e2e tests directly. Do NOT use for partial work — for stage-1 planning only, run the manual walkthrough; for test work alone, invoke e2e-test-engineer directly.
4
4
  tags: [sdlc, orchestration, compliance, automation]
5
5
  ---
6
6
 
7
7
  # SDLC implementer
8
8
 
9
- Take a single GitHub issue end-to-end through the Metasession SDLC. The skill **triages first** (Phase 0): it classifies the change, announces the path it will take, and routes — only a **tracked** change continues into the full cycle, while housekeeping, trivial, and compliance-doc-only work is driven down its lighter path **to completion** (the skill still guides every step to merge; it just skips the tracked ceremony). For a tracked change, one command runs Phase 1 through Phase 4 unattended (with a plan-approval pause for HIGH/CRITICAL risk); the human enters the loop at the UAT review gate on the portal. On resume, the skill runs Phase 5 — merge, post-deploy smoke evidence, mark the release Released, or address change-requests and re-submit for UAT re-review.
9
+ Take a single GitHub issue end-to-end through the Metasession SDLC. The skill **triages first** (Phase 0): it classifies the change, announces the path it will take, and routes — only a **tracked** change continues into the full execution, while housekeeping, trivial, and compliance-doc-only work is driven down its lighter path **to completion** (the skill still guides every step to merge; it just skips the tracked ceremony). For a tracked change, one command runs Phase 1 through Phase 4 unattended (with a plan-approval pause for HIGH/CRITICAL risk); the human enters the loop at the UAT review gate on the portal. On resume, the skill runs Phase 5 — merge, post-deploy smoke evidence, mark the release Released, or address change-requests and re-submit for UAT re-review.
10
10
 
11
11
  This skill is a single entry point that **routes**, not one that always runs heavy. The change-type taxonomy it routes against is the canonical table in [`change-workflows.md`](https://github.com/metasession-dev/DevAudit-Installer/blob/main/docs/change-workflows.md) (six change-types → commit-type → requirement? → path).
12
12
 
@@ -105,7 +105,7 @@ This gate is distinct from Phase 2 step 4's "any deviation from the plan must be
105
105
 
106
106
  ## Requirements gap flow (devaudit-installer#212)
107
107
 
108
- A **requirements gap** is "the ACs themselves were wrong, incomplete, or missing" — discovered by the skill or sub-skills mid-cycle, not by a user request. This is distinct from:
108
+ A **requirements gap** is "the ACs themselves were wrong, incomplete, or missing" — discovered by the skill or sub-skills mid-execution, not by a user request. This is distinct from:
109
109
 
110
110
  - **Scope expansion** (user asks for something beyond the ACs → scope-expansion halt gate above)
111
111
  - **Defect** (implementation doesn't match the ACs → incident filing)
@@ -146,7 +146,7 @@ Amendment steps:
146
146
  8. If Phase 3+: re-compile affected evidence
147
147
  9. Continue from current phase
148
148
 
149
- **(c) File a follow-up REQ** — ship the current REQ as-is, file a separate issue for the requirements gap. Appropriate when the gap is a genuine new behaviour that deserves its own cycle. Continue with the current REQ unchanged.
149
+ **(c) File a follow-up REQ** — ship the current REQ as-is, file a separate issue for the requirements gap. Appropriate when the gap is a genuine new behaviour that deserves its own execution. Continue with the current REQ unchanged.
150
150
 
151
151
  ### Distinction from scope-expansion halt gate
152
152
 
@@ -324,7 +324,7 @@ _Workflow tweak (CI artifact upload, gate timeout bump, etc.)_
324
324
 
325
325
  ### Lightweight path (housekeeping / trivial / compliance-doc-only)
326
326
 
327
- Reached from Phase 0 for non-tracked change-types. The skill drives this end-to-end; the only difference from the tracked cycle is the absence of _ceremony_, not the absence of _guidance_. It pauses only where a human is genuinely required (PR review, merge).
327
+ Reached from Phase 0 for non-tracked change-types. The skill drives this end-to-end; the only difference from the tracked execution is the absence of _ceremony_, not the absence of _guidance_. It pauses only where a human is genuinely required (PR review, merge).
328
328
 
329
329
  **CI trigger shape — read once before step 7.** DevAudit-Installer-generated `ci.yml.template` runs `Quality Gates` on PRs to the integration branch and on pushes to the integration branch. Older consumers may still have post-merge-only CI (`push: branches: [<integration>]`, no `pull_request:` trigger) until they re-run `devaudit update`. The skill must adapt step 7's wording to whichever shape the project uses; never poll a PR for checks that won't arrive on that consumer yet.
330
330
 
@@ -342,7 +342,7 @@ Reached from Phase 0 for non-tracked change-types. The skill drives this end-to-
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
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.
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 a later tracked release explicitly absorbs it through the required bundled-changes markdown/JSON manifest and portal lineage. Never infer that an unrelated hotfix or mandatory back-merge belongs to the only pending REQ. A submitted REQ bundle is frozen approval scope; changing it returns that REQ to evidence compilation and UAT resubmission. A doc-only push attaches documents to the existing `REQ-XXX` release.
346
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.
347
347
 
348
348
  ### Phase 1 — Plan (SDLC stage 1)
@@ -499,9 +499,10 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
499
499
 
500
500
  5. **Generate nil incident report if no incidents were closed (devaudit-installer#210 §8a).** After the test pack re-run passes, check whether any `incident`-labelled issues were closed during this REQ's lifecycle (`gh issue list --label incident --state closed --search "REQ-XXX" --json number`). If none were closed and the test pack re-run passes:
501
501
  - Create `compliance/governance/nil-incident-report-<version>.md` from the `nil-incident-report.md.template` template.
502
+ - Set frontmatter `incident_kind: "no_incidents_attestation"`, `source_release: "<version>"`, and `semantic_id: "NIL-<version>"`. The `source_release` must be the REQ or standalone housekeeping release that actually owns the attestation.
502
503
  - Fill in the scope section (test cases executed/passed/failed, defects filed, incidents reported).
503
504
  - Leave the sign-off section with REPLACE markers — the operator fills it in.
504
- - The nil report uploads as `incident_report` evidence via `compliance-evidence.yml`'s `upload_incident_report` function, flipping `ISO29119.3.5.4` to COVERED for clean releases.
505
+ - The nil report uploads as source-owned `incident_report` evidence via `compliance-evidence.yml`'s `upload_incident_report` function, flipping `ISO29119.3.5.4` to COVERED for clean releases. Historical nil reports are not re-uploaded to later REQs unless an explicit bundle manifest references the source release; even then ownership remains on the source release.
505
506
  - If incidents WERE closed, skip nil report generation — the populated incident report(s) from `incident-export.yml` serve as the evidence.
506
507
 
507
508
  5b. **Validate test-execution-summary.md gate states (devaudit-installer#240).** Before uploading evidence, verify that `compliance/evidence/REQ-XXX/test-execution-summary.md` does not contain invalid gate states. Run:
@@ -512,22 +513,22 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
512
513
 
513
514
  If the validator fails, fix the summary before proceeding. E2E gate results must be one of: `PASS`, `FAIL`, `NOT_NEEDED` (with reason), or `SKIPPED` (with operator-approved rationale). The word "deferred" must never appear in `test-execution-summary.md` — not as a gate state, not in prose, not in final assessment. "Deferred to CI" and "Playwright browsers not installed locally" are environment issues, not gate states. The CI validator (`validate-test-summary.sh`) will reject any summary containing "deferred" or "browsers not installed" on PRs to main.
514
515
 
515
- 5bb. **Render the Test Cycles table from the portal's cycle read model when available (devaudit-installer#394).** Do not hand-build cycle rows from uploaded artefacts if the portal exposes first-class cycle records. Query the release-journey / cycle API, save the JSON, and render the markdown table with:
516
+ 5b.1. **Render the Test Executions table from the portal's execution read model when available (devaudit-installer#394).** Do not hand-build execution rows from uploaded artefacts if the portal exposes first-class test execution records. Query the release-journey / execution API, save the JSON, and render the markdown table with:
516
517
 
517
518
  ```bash
518
- bash scripts/render-test-cycles.sh /tmp/release-journey.json
519
+ bash scripts/render-test-executions.sh /tmp/release-journey.json
519
520
  ```
520
521
 
521
522
  First-class rows must preserve:
522
523
  - source release
523
524
  - SDLC stage
524
- - cycle ordinal within that source release + stage
525
- - cycle kind / outcome
525
+ - execution ordinal within that source release + stage
526
+ - execution kind / outcome
526
527
  - workflow or run link
527
528
  - related evidence
528
529
  - incident or remediation reference
529
530
 
530
- If the portal does not yet expose first-class cycles, the same helper emits the legacy `testCycleId` grouping fallback and labels it as such. Keep the fallback note in the final `test-execution-summary.md`; it is part of the audit trail during rollout.
531
+ If the portal does not expose first-class test executions, do not infer executions from uploaded artefact groups. Record the gap in the summary and keep the release unresolved until the execution read model is available.
531
532
 
532
533
  5c. **Run the Phase 3 completion guard before any PR/release-review step (devaudit-installer#341).** The tracked-release evidence pack is incomplete until the required Git artefacts exist. Before proceeding past Phase 3, verify:
533
534
 
@@ -634,7 +635,7 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
634
635
  **Solo-operator teams.** On a one-person team, the literal "reviewer ≠ submitter" check is structurally unsatisfiable. The supported interpretation is _actor type, not human identity_ — AI tooling (the skill-trigger) and the human operator (the portal-approver) are distinct actors. Document this on the release ticket under `## Sign-off (dual-actor)` with the explicit interpretation, and ensure the human operator has independently reviewed the diff before clicking _Approve Production_ in the portal. Without this attestation the four-eyes claim is performative.
635
636
 
636
637
  3. **Apply labels** — `awaiting-uat-review`, `risk:<class>`.
637
- 4. **Comment on the issue**: "Implementation complete. PR #M opened. Evidence on portal: <link>. UAT review requested. Resume with `resume REQ-XXX` once UAT approval is granted on the portal."
638
+ 4. **Comment on the issue**: "Implementation complete. PR #M opened. Evidence on portal: <link>. UAT review requested. Reviewer action — perform UAT, record it with `scripts/record-uat-execution.sh`, then approve or request changes on the portal. Resume with `resume REQ-XXX` once UAT approval is granted on the portal."
638
639
  5. **Inspect PR blockers before handoff (devaudit-installer#304).** Run `gh pr view <N> --json mergeStateStatus,reviewDecision,statusCheckRollup,url` and categorize the state:
639
640
  - `reviewDecision` not approved → human approval blocker; hand off to the reviewer.
640
641
  - Release Approval Gate failing while portal state is not `uat_approved` → portal approval blocker; hand off to portal UAT reviewer.
@@ -658,7 +659,8 @@ Reached only on the **tracked** route from Phase 0 (the issue is already fetched
658
659
  The watcher persists retry state in `.sdlc-pr-watch.json`, polls `gh pr view` + `gh pr checks`, re-runs likely flaky workflows automatically, and re-runs the Release Approval Gate when the portal is already approved but GitHub has not converged yet. For a release PR (`$INTEGRATION_BRANCH -> $RELEASE_BRANCH`), it must not call the PR green until the full required release check set has reached terminal success: `Quality Gates`, `Release Scope Integrity`, `Compliance Validation`, `DevAudit Release Approval`, and `E2E Regression Suite`. Use `--once` when you only need a single classification pass.
659
660
 
660
661
  6. **Hard stop only after blocker classification.** Phase 4 ends here. Do not proceed to merge; the human's next action is reviewing on the portal or clearing the categorized blocker above.
661
- 7. **Update SDLC status sticky** before halting: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 4 truthful release PR #<N> prepared against $RELEASE_BRANCH; blockers classified" "Operator action review PR #<N> + approve UAT release on the portal; sdlc-implementer halts until you ping resume REQ-XXX"`. This is a critical handoff the sticky must reflect that the agent has stopped + the operator is on the hook.
662
+ 7. **Require a separate UAT execution record.** `scripts/submit-for-uat-review.sh` is a queue transition only. The reviewer must run `scripts/record-uat-execution.sh --project-slug <slug> --release REQ-XXX --outcome passed|failed --executor "<identity>" --tested-sha <sha> --checklist-ref "<ref>" --evidence-ref "<ref>"` after performing UAT and before portal approval. A `failed` execution must include `--remediation-ref <issue-or-pr>` and returns to the change-request loop. The skill must not create a passing UAT execution on behalf of the reviewer.
663
+ 8. **Update SDLC status sticky** before halting: `bash scripts/update-sdlc-status.sh "$ISSUE_NUM" "Phase 4 — truthful release PR #<N> prepared against $RELEASE_BRANCH; blockers classified" "Operator action — review PR #<N>, perform UAT, record Stage 4 UAT execution, then approve or request changes on the portal; sdlc-implementer halts until you ping resume REQ-XXX"`. This is a critical handoff — the sticky must reflect that the agent has stopped + the operator is on the hook.
662
664
 
663
665
  **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:
664
666
 
@@ -693,8 +695,9 @@ Invoked separately by the user after UAT activity on the portal. Trigger: "resum
693
695
  - **Enumerate all in-scope REQs** from `compliance/pending-releases/RELEASE-TICKET-REQ-*.md` and check each one's portal state before merging. If ANY are `uat_changes_requested`: halt — "REQ-YYY has changes requested on the portal. Cannot merge bundled PR until all in-scope REQs are approved." If ANY are still `uat_review`: halt — "REQ-YYY is still pending UAT review." If ALL are `uat_approved` (or `prod_review`/`prod_approved`/`released`): proceed with merge.
694
696
  - `gh pr merge <M> --merge` (merge commit; `--squash` and `--rebase` are blocked by branch protection on SDLC repos and would break the audit trail).
695
697
  - **Before watching `post-deploy-prod.yml`**, verify the workflow exists and has triggered: `gh run list --workflow post-deploy-prod.yml --limit 1 --json status,conclusion`. If no run appears within 5 minutes of the merge, halt — "post-deploy-prod.yml did not trigger after merge. Check workflow configuration, secrets, and deployment platform. Operator action — run `gh workflow run post-deploy-prod.yml` manually, then ping `resume REQ-XXX`."
698
+ - **Gate topology:** when a host waits for CI before deploy, only pre-deploy eligibility belongs to the main-push suite. Post-deploy evidence and full regression trigger from a successful production `deployment_status`, not that same push. If the provider skipped the SHA due to CI gating, redeploy that exact SHA before running post-deploy verification.
696
699
  - Watch `post-deploy-prod.yml` via `gh run watch` — block until the workflow reaches a terminal state. Wrap in a timeout (30 minutes default, configurable via `sdlc-config.json:post_deploy.timeout_minutes`). If the timeout fires: halt — "post-deploy-prod.yml has not reached a terminal state in N minutes. The deployment may be stuck. Operator action — check hosting platform logs, decide whether to wait or rollback, then ping `resume REQ-XXX`."
697
- - Treat `post-deploy-prod.yml` success as necessary but not sufficient. The workflow must also confirm the hosting platform's GitHub deployment status for the merged SHA reached terminal `success`. If the host deployment is still `queued` / `in_progress`, remain blocked. If it reaches `failure`, halt — "The host deployment for <sha> failed after merge. Do not approve Production or mark Released. Operator action — inspect hosting logs / rollback / resume once the production state is understood."
700
+ - Treat `post-deploy-prod.yml` success as necessary but not sufficient. The workflow must also confirm the hosting platform's GitHub deployment status for the merged SHA reached terminal `success`. If it reports `deployment_status_timeout`, `deployment_status_missing`, or terminal failure, remain blocked and preserve the deployment ID, SHA, environment, final observed state, target URL, elapsed time, and independent health-probe result. A successful health probe is corroboration only, never deployment-status proof. Halt — "The host deployment for <sha> is not terminal-successful after merge. Do not approve Production or mark Released. Operator action — inspect provider logs, fix forward or rollback, then rerun post-deploy verification once the production state is understood."
698
701
  - Verify production smoke evidence uploaded (`--environment production`) at `https://devaudit.metasession.co/projects/<slug>/releases/<version>`.
699
702
  - **Read `production_review.terminal_status` from `sdlc-config.json`** (default: `prod_review`). Branch:
700
703
  - If `released` (Option B): PATCH directly to `released` — `PATCH /releases/<version>` with `{"status": "released"}`.
@@ -705,7 +708,7 @@ Invoked separately by the user after UAT activity on the portal. Trigger: "resum
705
708
  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
709
  - **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
710
  - Close the issue only after close-out reconciliation completes.
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"`.
711
+ - 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, testExecutionId (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"`.
709
712
 
710
713
  - **Changes requested** → run change-request loop:
711
714
  - Fetch change-request comments from the PR (`gh pr view <M> --comments`) and from the portal release page.
@@ -57,7 +57,18 @@ jobs:
57
57
  # tag each captured screenshot as feature vs regression.
58
58
  fetch-depth: 0
59
59
 
60
- - name: Start authoritative quality-gate cycle
60
+ - name: Validate self-hosted runner prerequisites
61
+ env:
62
+ DEVAUDIT_RUNNER_ENVIRONMENT: ${{ runner.environment }}
63
+ run: |
64
+ if [ -f scripts/check-self-hosted-runner.sh ]; then
65
+ chmod +x scripts/check-self-hosted-runner.sh
66
+ bash scripts/check-self-hosted-runner.sh
67
+ else
68
+ echo "::warning::scripts/check-self-hosted-runner.sh missing; run devaudit update to sync runner preflight."
69
+ fi
70
+
71
+ - name: Start authoritative quality-gate execution
61
72
  if: github.event_name != 'pull_request' && github.ref_name == '{{INTEGRATION_BRANCH}}' && needs.register-release.outputs.version != 'skip'
62
73
  run: |
63
74
  BASE=""
@@ -65,13 +76,13 @@ jobs:
65
76
  BASE=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
66
77
  fi
67
78
  [ -n "$BASE" ] || BASE="$DEVAUDIT_BASE_URL_VAR"
68
- [ -n "$BASE" ] || { echo "::warning::No DevAudit URL; cycle lifecycle disabled"; exit 0; }
79
+ [ -n "$BASE" ] || { echo "::warning::No DevAudit URL; test execution lifecycle disabled"; exit 0; }
69
80
  echo "DEVAUDIT_BASE_URL=${BASE%/}" >> "$GITHUB_ENV"
70
- chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
71
- bash scripts/report-test-cycle.sh start \
81
+ chmod +x scripts/report-test-execution.sh scripts/report-release-check.sh 2>/dev/null || true
82
+ bash scripts/report-test-execution.sh start \
72
83
  --project-slug {{PROJECT_SLUG}} \
73
84
  --release "${{ needs.register-release.outputs.version }}" \
74
- --sdlc-stage 2 --environment ci --cycle-kind quality_gate \
85
+ --sdlc-stage 2 --environment ci --suite-kind quality_gate \
75
86
  --provider github_actions --external-run-id "${{ github.run_id }}" \
76
87
  --external-run-attempt "${{ github.run_attempt }}" \
77
88
  --external-job-id "quality-gates" \
@@ -323,11 +334,11 @@ jobs:
323
334
  echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
324
335
  echo "Release version: ${VERSION}"
325
336
 
326
- - name: Ensure release exists for gate-cycle lineage
337
+ - name: Ensure release exists for gate-execution lineage
327
338
  if: env.DEVAUDIT_BASE_URL != '' && steps.version.outputs.version != 'skip'
328
339
  run: |
329
- # First-class cycle APIs need an exact release row before the
330
- # stage-2 cycle can start. This keeps failed gate runs visible even
340
+ # First-class test execution APIs need an exact release row before the
341
+ # stage-2 execution can start. This keeps failed gate runs visible even
331
342
  # when later evidence generation fails.
332
343
  chmod +x scripts/upload-evidence.sh 2>/dev/null || true
333
344
  bash scripts/upload-evidence.sh \
@@ -501,17 +512,17 @@ jobs:
501
512
  - name: Generate and upload gate evidence
502
513
  if: env.DEVAUDIT_BASE_URL != ''
503
514
  run: |
504
- chmod +x scripts/upload-evidence.sh scripts/report-test-cycle.sh 2>/dev/null || true
515
+ chmod +x scripts/upload-evidence.sh scripts/report-test-execution.sh 2>/dev/null || true
505
516
  FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
506
517
  FLAGS="${FLAGS} --release ${{ needs.register-release.outputs.version }} --create-release-if-missing"
507
518
  FLAGS="${FLAGS} --environment uat"
508
519
  FLAGS="${FLAGS} --sdlc-stage 2"
509
- PRIMARY_LINEAGE_FLAGS=(--test-cycle "${{ github.run_id }}")
510
- CYCLE_OUT="$(mktemp)"
511
- bash scripts/report-test-cycle.sh start \
520
+ PRIMARY_LINEAGE_FLAGS=(--test-execution "${{ github.run_id }}")
521
+ EXECUTION_OUT="$(mktemp)"
522
+ bash scripts/report-test-execution.sh start \
512
523
  --project-slug {{PROJECT_SLUG}} \
513
524
  --release "${{ needs.register-release.outputs.version }}" \
514
- --sdlc-stage 2 --environment ci --cycle-kind quality_gate \
525
+ --sdlc-stage 2 --environment ci --suite-kind quality_gate \
515
526
  --provider github_actions --external-run-id "${{ github.run_id }}" \
516
527
  --external-run-attempt "${{ github.run_attempt }}" \
517
528
  --external-job-id "quality-gates" \
@@ -519,11 +530,11 @@ jobs:
519
530
  --commit-sha "${{ github.sha }}" --branch "${{ github.ref_name }}" \
520
531
  --workflow-name "Quality Gates" \
521
532
  --workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
522
- --output-file "$CYCLE_OUT"
523
- . "$CYCLE_OUT"
524
- rm -f "$CYCLE_OUT"
525
- if [ "${cycle_supported:-false}" = "true" ] && [ -n "${cycle_record_id:-}" ]; then
526
- PRIMARY_LINEAGE_FLAGS+=(--evidence-scope cycle --test-cycle-record-id "${cycle_record_id}")
533
+ --output-file "$EXECUTION_OUT"
534
+ . "$EXECUTION_OUT"
535
+ rm -f "$EXECUTION_OUT"
536
+ if [ "${execution_supported:-false}" = "true" ] && [ -n "${execution_record_id:-}" ]; then
537
+ PRIMARY_LINEAGE_FLAGS+=(--evidence-scope execution --test-execution-record-id "${execution_record_id}")
527
538
  fi
528
539
 
529
540
  # Track failures across all uploads so we can fail the job at the
@@ -646,20 +657,51 @@ jobs:
646
657
  --environment uat --sdlc-stage 2
647
658
  fi
648
659
 
649
- # devaudit-installer#200 Fix 4 — Per-REQ gate evidence fan-out.
650
- # The gate evidence above was uploaded to the single commit-derived
651
- # release (FLAGS carries --release <derived>). When multiple REQs
652
- # have pending release tickets, only that one REQ gets gate evidence
653
- # on the portal other in-scope REQs show "no gate evidence" and
654
- # "no tests tagged with this REQ" even though the gates ran and
655
- # passed. Fan out gate-outcomes.json + e2e-results.json to each
656
- # in-scope REQ's release so the portal's per-REQ test parser can
657
- # extract REQ-tagged tests from the e2e-results.json associated with
658
- # that REQ's release. Heavy run-context artefacts (SAST, dep-audit,
659
- # playwright zip, coverage) remain on the commit-derived release
660
- # only they are run-level, not per-REQ.
660
+ # Per-REQ E2E JSON attachment only.
661
+ # Gate outcomes above are intentionally uploaded only to the single
662
+ # commit-derived release in FLAGS. A generic integration Quality
663
+ # Gates run is run-level evidence; pending release tickets alone do
664
+ # not make that gate current approval evidence for every REQ. Bundle
665
+ # membership must be represented through an explicit bundle manifest
666
+ # and portal lineage, not by fanning out gate-outcomes.json rows
667
+ # (DevAudit-Installer#438).
668
+ #
669
+ # E2E JSON is different: it may be attached to a REQ only when the
670
+ # report itself contains an executed test tagged for that REQ. A smoke
671
+ # run must never overwrite or obscure a prior targeted feature
672
+ # execution merely because a pending ticket exists
673
+ # (DevAudit-Installer#435).
674
+ has_req_tagged_e2e_result() {
675
+ python3 - "$1" "$2" <<'PY'
676
+ import json, re, sys
677
+
678
+ req_id, filename = sys.argv[1:]
679
+ def values(value):
680
+ if isinstance(value, str): return [value]
681
+ if isinstance(value, dict): return [str(value.get('type', '')), str(value.get('description', ''))]
682
+ return []
683
+ def tagged(node, ancestry):
684
+ text = [*ancestry, str(node.get('title', ''))]
685
+ for field in ('annotations', 'tags'):
686
+ for value in node.get(field, []): text.extend(values(value))
687
+ return any(req_id in value for value in text)
688
+ def executed(test):
689
+ return any(result.get('status') not in {'skipped', 'interrupted'} for result in test.get('results', []))
690
+ def contains(node, ancestry):
691
+ title = str(node.get('title', ''))
692
+ path = [*ancestry, title] if title else ancestry
693
+ for spec in node.get('specs', []):
694
+ if tagged(spec, path) and any(executed(test) for test in spec.get('tests', [])): return True
695
+ for test in node.get('tests', []):
696
+ if tagged(test, path) and executed(test): return True
697
+ return any(contains(suite, path) for suite in node.get('suites', []))
698
+ with open(filename, encoding='utf-8') as handle:
699
+ report = json.load(handle)
700
+ raise SystemExit(0 if re.fullmatch(r'REQ-[A-Z0-9-]+', req_id) and contains(report, []) else 1)
701
+ PY
702
+ }
661
703
  COMMIT_REQ="${{ needs.register-release.outputs.version }}"
662
- FANOUT_REQS=()
704
+ E2E_REQS=()
663
705
  if [ -d compliance/pending-releases ]; then
664
706
  for TICKET in compliance/pending-releases/RELEASE-TICKET-REQ-*.md; do
665
707
  [ -f "$TICKET" ] || continue
@@ -673,25 +715,23 @@ jobs:
673
715
  if [ "$REQ_ID" = "$COMMIT_REQ" ]; then
674
716
  continue
675
717
  fi
676
- FANOUT_REQS+=("$REQ_ID")
718
+ E2E_REQS+=("$REQ_ID")
677
719
  done
678
720
  fi
679
- if [ "${#FANOUT_REQS[@]}" -gt 0 ]; then
680
- echo "Fanning out gate evidence to in-scope REQs: ${FANOUT_REQS[*]}"
681
- for REQ_ID in "${FANOUT_REQS[@]}"; do
721
+ if [ "${#E2E_REQS[@]}" -gt 0 ]; then
722
+ echo "Checking tagged E2E JSON attachment candidates: ${E2E_REQS[*]}"
723
+ echo "::notice::Not fanning out gate-outcomes.json to pending REQs; gate evidence remains on ${COMMIT_REQ} unless represented through explicit release lineage."
724
+ for REQ_ID in "${E2E_REQS[@]}"; do
682
725
  FANOUT_FLAGS="--git-sha ${{ github.sha }} --ci-run-id ${{ github.run_id }} --branch ${{ github.ref_name }}"
683
726
  FANOUT_FLAGS="${FANOUT_FLAGS} --release ${REQ_ID} --create-release-if-missing"
684
727
  FANOUT_FLAGS="${FANOUT_FLAGS} --environment uat --sdlc-stage 2"
685
- FANOUT_FLAGS="${FANOUT_FLAGS} --test-cycle ${{ github.run_id }}"
686
- if [ -f ci-evidence/gate-outcomes.json ]; then
687
- upload "gate-outcomes.json -> ${REQ_ID}" \
688
- {{PROJECT_SLUG}} "${REQ_ID}" gate_outcome ci-evidence/gate-outcomes.json \
689
- --category ci_pipeline ${FANOUT_FLAGS}
690
- fi
691
- if [ -f ci-evidence/e2e-results.json ]; then
728
+ FANOUT_FLAGS="${FANOUT_FLAGS} --test-execution ${{ github.run_id }}"
729
+ if [ -f ci-evidence/e2e-results.json ] && has_req_tagged_e2e_result "$REQ_ID" ci-evidence/e2e-results.json; then
692
730
  upload "e2e-results.json -> ${REQ_ID}" \
693
731
  {{PROJECT_SLUG}} "${REQ_ID}" e2e_result ci-evidence/e2e-results.json \
694
732
  --category e2e_result ${FANOUT_FLAGS}
733
+ elif [ -f ci-evidence/e2e-results.json ]; then
734
+ echo "::notice::Not attaching generic E2E JSON to ${REQ_ID}: this CI tier executed no tagged ${REQ_ID} test. Run-level evidence remains on _compliance-docs."
695
735
  fi
696
736
  done
697
737
  fi
@@ -945,7 +985,7 @@ jobs:
945
985
  exit 1
946
986
  fi
947
987
 
948
- - name: Complete primary quality-gate cycle
988
+ - name: Complete primary quality-gate execution
949
989
  if: always() && env.DEVAUDIT_BASE_URL != ''
950
990
  run: |
951
991
  case "${{ needs.quality-gates.result }}" in
@@ -953,13 +993,13 @@ jobs:
953
993
  cancelled) OUTCOME=cancelled ;;
954
994
  *) OUTCOME=failed ;;
955
995
  esac
956
- chmod +x scripts/report-test-cycle.sh scripts/report-release-check.sh 2>/dev/null || true
957
- bash scripts/report-test-cycle.sh complete \
996
+ chmod +x scripts/report-test-execution.sh scripts/report-release-check.sh 2>/dev/null || true
997
+ bash scripts/report-test-execution.sh complete \
958
998
  --project-slug {{PROJECT_SLUG}} \
959
999
  --release "${{ needs.register-release.outputs.version }}" \
960
1000
  --sdlc-stage 2 \
961
1001
  --environment ci \
962
- --cycle-kind quality_gate \
1002
+ --suite-kind quality_gate \
963
1003
  --provider github_actions \
964
1004
  --external-run-id "${{ github.run_id }}" \
965
1005
  --external-run-attempt "${{ github.run_attempt }}" \