@prism-d1/cli 1.0.79 → 1.0.81
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/assets/github-workflows/prism-ai-metrics.yml +83 -122
- package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +66 -0
- package/dist/assets/github-workflows/prism-eval-gate.yml +1 -1
- package/dist/assets/gitlab-workflows/.gitlab-ci.yml +0 -3
- package/dist/assets/gitlab-workflows/prism-ai-metrics.yml +53 -56
- package/dist/assets/infra/bin/app.ts +1 -0
- package/dist/assets/infra/lib/constructs/otel-collector-construct.ts +4 -0
- package/dist/assets/infra/lib/dashboard-stack.ts +378 -1235
- package/dist/assets/infra/lib/lambda/event-schema.ts +144 -0
- package/dist/assets/infra/lib/lambda/metrics-processor.ts +124 -36
- package/dist/assets/infra/lib/lambda/otel-receiver.ts +249 -35
- package/dist/assets/infra/lib/lambda/security-remediation-tracker.ts +17 -2
- package/dist/assets/infra/lib/lambda/velocity-widget.ts +1483 -0
- package/dist/assets/infra/lib/metrics-pipeline-stack.ts +111 -119
- package/dist/assets/infra/package.json +2 -1
- package/dist/assets/infra/tsconfig.json +9 -2
- package/dist/src/commands/assessment/web.d.ts.map +1 -1
- package/dist/src/commands/assessment/web.js +25 -2
- package/dist/src/commands/assessment/web.js.map +1 -1
- package/dist/src/commands/bootstrapper/install-eval-harness.d.ts +8 -2
- package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-eval-harness.js +46 -36
- package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
- package/dist/src/commands/bootstrapper/install-git-hooks.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-git-hooks.js +10 -14
- package/dist/src/commands/bootstrapper/install-git-hooks.js.map +1 -1
- package/dist/src/commands/bootstrapper/install-github-workflows.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-github-workflows.js +7 -2
- package/dist/src/commands/bootstrapper/install-github-workflows.js.map +1 -1
- package/dist/src/commands/bootstrapper/install-gitlab-workflows.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-gitlab-workflows.js +7 -1
- package/dist/src/commands/bootstrapper/install-gitlab-workflows.js.map +1 -1
- package/dist/src/commands/bootstrapper/setup-github-oidc.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/setup-github-oidc.js +50 -27
- package/dist/src/commands/bootstrapper/setup-github-oidc.js.map +1 -1
- package/dist/src/commands/bootstrapper/setup-gitlab-oidc.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/setup-gitlab-oidc.js +69 -25
- package/dist/src/commands/bootstrapper/setup-gitlab-oidc.js.map +1 -1
- package/dist/src/commands/bootstrapper/setup-otel-sync.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/setup-otel-sync.js +145 -53
- package/dist/src/commands/bootstrapper/setup-otel-sync.js.map +1 -1
- package/dist/src/commands/workshop/generate-demo-data.d.ts +41 -2
- package/dist/src/commands/workshop/generate-demo-data.d.ts.map +1 -1
- package/dist/src/commands/workshop/generate-demo-data.js +391 -167
- package/dist/src/commands/workshop/generate-demo-data.js.map +1 -1
- package/dist/src/commands/workshop/verify-setup.d.ts.map +1 -1
- package/dist/src/commands/workshop/verify-setup.js +75 -14
- package/dist/src/commands/workshop/verify-setup.js.map +1 -1
- package/dist/src/utils/exec.d.ts +56 -0
- package/dist/src/utils/exec.d.ts.map +1 -0
- package/dist/src/utils/exec.js +72 -0
- package/dist/src/utils/exec.js.map +1 -0
- package/dist/src/utils/region.d.ts +50 -0
- package/dist/src/utils/region.d.ts.map +1 -0
- package/dist/src/utils/region.js +62 -0
- package/dist/src/utils/region.js.map +1 -0
- package/dist/src/utils/tempfile.d.ts +19 -0
- package/dist/src/utils/tempfile.d.ts.map +1 -0
- package/dist/src/utils/tempfile.js +32 -0
- package/dist/src/utils/tempfile.js.map +1 -0
- package/dist/src/utils/validate.d.ts +54 -0
- package/dist/src/utils/validate.d.ts.map +1 -0
- package/dist/src/utils/validate.js +143 -0
- package/dist/src/utils/validate.js.map +1 -0
- package/package.json +2 -2
- package/dist/assets/eval-harness/README.md +0 -144
- package/dist/assets/github-workflows/README.md +0 -110
- package/dist/assets/github-workflows/prism-dora-weekly.yml +0 -334
- package/dist/assets/gitlab-workflows/prism-dora-weekly.yml +0 -181
- package/dist/assets/infra/lib/lambda/defect-correlator.ts +0 -142
- package/dist/assets/infra/lib/lambda/spec-to-code-calculator.ts +0 -123
- package/dist/assets/metric-hooks/README.md +0 -107
|
@@ -1,12 +1,30 @@
|
|
|
1
|
-
# prism-ai-metrics.yml —
|
|
1
|
+
# prism-ai-metrics.yml — Per-merge delivery telemetry.
|
|
2
2
|
#
|
|
3
3
|
# Copy this file to .github/workflows/prism-ai-metrics.yml in your repo.
|
|
4
4
|
#
|
|
5
|
+
# DESIGN: this workflow emits immutable per-PR FACTS and computes no rates.
|
|
6
|
+
# Every aggregation (frequency, averages, change-failure rate, MTTR) happens at
|
|
7
|
+
# query time in the dashboard widgets. That means:
|
|
8
|
+
# - the dashboard's time-range picker applies to every KPI uniformly
|
|
9
|
+
# - no metric bakes in a fixed window (the old weekly job hardcoded 7 days)
|
|
10
|
+
# - lead-time distribution survives, so p50/p95 stay computable
|
|
11
|
+
#
|
|
12
|
+
# AI origin is deliberately NOT computed here. It is resolved at render time by
|
|
13
|
+
# joining `pr.commit_shas` against the codeburn attribution store. Origin
|
|
14
|
+
# verdicts computed at merge time are frequently wrong, because attribution
|
|
15
|
+
# syncs on a ~12h schedule and may not yet cover these commits. See
|
|
16
|
+
# docs/DATA-ARCHITECTURE.md → "AI Origin Resolution: Deferred Attribution Join".
|
|
17
|
+
#
|
|
18
|
+
# Replaces the former prism-dora-weekly.yml, which computed a parallel 7-day
|
|
19
|
+
# aggregate from the GitHub API. That duplicated deployment-frequency
|
|
20
|
+
# measurement, disagreed with this workflow on `master`-based repos, and
|
|
21
|
+
# published six CloudWatch metrics no dashboard read.
|
|
22
|
+
#
|
|
5
23
|
# Required setup:
|
|
6
24
|
# - OIDC provider configured for GitHub Actions in your AWS account
|
|
7
25
|
# - IAM role with events:PutEvents on the prism-d1-metrics bus
|
|
8
26
|
# - Repository secret PRISM_METRICS_ROLE_ARN set to the OIDC role ARN
|
|
9
|
-
# - .prism/config.json committed to the repo (
|
|
27
|
+
# - .prism/config.json committed to the repo (optional; defaults to no_team)
|
|
10
28
|
|
|
11
29
|
name: PRISM AI Metrics
|
|
12
30
|
|
|
@@ -28,6 +46,8 @@ jobs:
|
|
|
28
46
|
- name: Checkout
|
|
29
47
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
30
48
|
with:
|
|
49
|
+
# Full history is required: `git rev-list BASE..HEAD` below needs the
|
|
50
|
+
# base commit present to enumerate the PR's commit SHAs.
|
|
31
51
|
fetch-depth: 0
|
|
32
52
|
|
|
33
53
|
- name: Configure AWS credentials (OIDC)
|
|
@@ -44,178 +64,111 @@ jobs:
|
|
|
44
64
|
else
|
|
45
65
|
TEAM_ID=""
|
|
46
66
|
fi
|
|
67
|
+
# Missing config is not fatal — emit under no_team rather than failing
|
|
68
|
+
# the run and losing this PR's facts permanently.
|
|
47
69
|
if [[ -z "${TEAM_ID}" ]]; then
|
|
48
70
|
TEAM_ID="no_team"
|
|
49
71
|
fi
|
|
50
72
|
echo "team_id=${TEAM_ID}" >> "$GITHUB_OUTPUT"
|
|
51
73
|
|
|
52
|
-
- name:
|
|
53
|
-
id:
|
|
74
|
+
- name: Collect PR facts
|
|
75
|
+
id: facts
|
|
76
|
+
env:
|
|
77
|
+
# PR titles are attacker-controlled. Bind to env and match with
|
|
78
|
+
# printf rather than interpolating into the shell (script injection).
|
|
79
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
80
|
+
CREATED_AT: ${{ github.event.pull_request.created_at }}
|
|
81
|
+
MERGED_AT: ${{ github.event.pull_request.merged_at }}
|
|
54
82
|
run: |
|
|
83
|
+
set -uo pipefail
|
|
55
84
|
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
|
56
85
|
HEAD_SHA="${{ github.event.pull_request.merge_commit_sha }}"
|
|
57
86
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
AI_ASSISTED=0
|
|
63
|
-
HUMAN_ONLY=0
|
|
64
|
-
TOTAL_INPUT_TOKENS=0
|
|
65
|
-
TOTAL_OUTPUT_TOKENS=0
|
|
66
|
-
TOTAL_COST="0"
|
|
67
|
-
for SHA in $(git rev-list "${BASE_SHA}..${HEAD_SHA}"); do
|
|
68
|
-
MSG=$(git log -1 --format='%B' "${SHA}")
|
|
69
|
-
if echo "${MSG}" | grep -q '^AI-Origin: ai-generated'; then
|
|
70
|
-
AI_GENERATED=$((AI_GENERATED + 1))
|
|
71
|
-
elif echo "${MSG}" | grep -q '^AI-Origin: ai-assisted'; then
|
|
72
|
-
AI_ASSISTED=$((AI_ASSISTED + 1))
|
|
73
|
-
elif echo "${MSG}" | grep -q '^AI-Origin: human'; then
|
|
74
|
-
HUMAN_ONLY=$((HUMAN_ONLY + 1))
|
|
75
|
-
fi
|
|
76
|
-
|
|
77
|
-
# Accumulate token usage trailers (clamped to prevent injection)
|
|
78
|
-
INPUT_T=$(echo "${MSG}" | grep -oP '(?<=^AI-Input-Tokens: )\d+' || echo "0")
|
|
79
|
-
OUTPUT_T=$(echo "${MSG}" | grep -oP '(?<=^AI-Output-Tokens: )\d+' || echo "0")
|
|
80
|
-
COST_T=$(echo "${MSG}" | grep -oP '(?<=^AI-Cost: )[0-9.]+' || echo "0")
|
|
81
|
-
[[ ${INPUT_T} -gt 1000000 ]] && INPUT_T=0
|
|
82
|
-
[[ ${OUTPUT_T} -gt 1000000 ]] && OUTPUT_T=0
|
|
83
|
-
(( $(echo "${COST_T} > 100" | bc -l) )) && COST_T=0
|
|
84
|
-
TOTAL_INPUT_TOKENS=$((TOTAL_INPUT_TOKENS + INPUT_T))
|
|
85
|
-
TOTAL_OUTPUT_TOKENS=$((TOTAL_OUTPUT_TOKENS + OUTPUT_T))
|
|
86
|
-
TOTAL_COST=$(echo "${TOTAL_COST} + ${COST_T}" | bc -l)
|
|
87
|
-
done
|
|
88
|
-
|
|
89
|
-
AI_COMMITS=$((AI_GENERATED + AI_ASSISTED))
|
|
90
|
-
if [[ "${TOTAL_COMMITS}" -gt 0 ]]; then
|
|
91
|
-
AI_RATIO=$(echo "scale=4; ${AI_COMMITS} / ${TOTAL_COMMITS}" | bc -l | sed 's/^\./0./')
|
|
92
|
-
else
|
|
93
|
-
AI_RATIO="0"
|
|
94
|
-
fi
|
|
95
|
-
|
|
96
|
-
# Detect tools used
|
|
97
|
-
TOOLS_USED=$(git log "${BASE_SHA}..${HEAD_SHA}" --format='%B' | grep '^AI-Origin:' | sort -u | head -5 | tr '\n' ',' | sed 's/,$//')
|
|
98
|
-
|
|
99
|
-
echo "total_commits=${TOTAL_COMMITS}" >> "$GITHUB_OUTPUT"
|
|
100
|
-
echo "ai_commits=${AI_COMMITS}" >> "$GITHUB_OUTPUT"
|
|
101
|
-
echo "ai_ratio=${AI_RATIO}" >> "$GITHUB_OUTPUT"
|
|
102
|
-
echo "ai_generated=${AI_GENERATED}" >> "$GITHUB_OUTPUT"
|
|
103
|
-
echo "ai_assisted=${AI_ASSISTED}" >> "$GITHUB_OUTPUT"
|
|
104
|
-
echo "human_only=${HUMAN_ONLY}" >> "$GITHUB_OUTPUT"
|
|
105
|
-
echo "total_input_tokens=${TOTAL_INPUT_TOKENS}" >> "$GITHUB_OUTPUT"
|
|
106
|
-
echo "total_output_tokens=${TOTAL_OUTPUT_TOKENS}" >> "$GITHUB_OUTPUT"
|
|
107
|
-
echo "total_cost=${TOTAL_COST}" >> "$GITHUB_OUTPUT"
|
|
108
|
-
|
|
109
|
-
- name: Calculate lead time
|
|
110
|
-
id: lead-time
|
|
111
|
-
run: |
|
|
112
|
-
CREATED_AT="${{ github.event.pull_request.created_at }}"
|
|
113
|
-
MERGED_AT="${{ github.event.pull_request.merged_at }}"
|
|
87
|
+
# --- Commit SHAs: immutable facts for deferred origin resolution.
|
|
88
|
+
# Capped at 50 to bound the event size and the render-time join.
|
|
89
|
+
COMMIT_SHAS=$(git rev-list "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null | head -50 | jq -R . | jq -sc . || echo '[]')
|
|
90
|
+
TOTAL_COMMITS=$(git rev-list --count "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null || echo 0)
|
|
114
91
|
|
|
92
|
+
# --- Lead time: PR open → merge. From the event payload, not git, so
|
|
93
|
+
# it is unaffected by rebases and squashes.
|
|
115
94
|
CREATED_TS=$(date -d "${CREATED_AT}" +%s 2>/dev/null || date -jf "%Y-%m-%dT%H:%M:%SZ" "${CREATED_AT}" +%s)
|
|
116
95
|
MERGED_TS=$(date -d "${MERGED_AT}" +%s 2>/dev/null || date -jf "%Y-%m-%dT%H:%M:%SZ" "${MERGED_AT}" +%s)
|
|
117
|
-
|
|
118
96
|
LEAD_TIME_SECONDS=$((MERGED_TS - CREATED_TS))
|
|
119
|
-
|
|
97
|
+
[[ "${LEAD_TIME_SECONDS}" -lt 0 ]] && LEAD_TIME_SECONDS=0
|
|
98
|
+
|
|
99
|
+
# --- Failure-fix label: the change-failure-rate NUMERATOR, emitted as
|
|
100
|
+
# a per-PR boolean. The rate itself (and MTTR, the mean lead time of
|
|
101
|
+
# labelled PRs) is computed at query time over the dashboard's window.
|
|
102
|
+
# Still a title heuristic — it misses failures that aren't named.
|
|
103
|
+
if printf '%s' "${PR_TITLE}" | grep -qiE 'revert|hotfix|rollback'; then
|
|
104
|
+
IS_FAILURE_FIX=true
|
|
105
|
+
else
|
|
106
|
+
IS_FAILURE_FIX=false
|
|
107
|
+
fi
|
|
120
108
|
|
|
109
|
+
echo "commit_shas=${COMMIT_SHAS}" >> "$GITHUB_OUTPUT"
|
|
110
|
+
echo "total_commits=${TOTAL_COMMITS}" >> "$GITHUB_OUTPUT"
|
|
121
111
|
echo "lead_time_seconds=${LEAD_TIME_SECONDS}" >> "$GITHUB_OUTPUT"
|
|
122
|
-
echo "
|
|
112
|
+
echo "is_failure_fix=${IS_FAILURE_FIX}" >> "$GITHUB_OUTPUT"
|
|
123
113
|
|
|
124
|
-
- name:
|
|
125
|
-
id:
|
|
114
|
+
- name: Collect review verdicts
|
|
115
|
+
id: reviews
|
|
126
116
|
env:
|
|
127
117
|
GH_TOKEN: ${{ github.token }}
|
|
128
118
|
run: |
|
|
119
|
+
set -uo pipefail
|
|
129
120
|
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
130
121
|
REPO="${{ github.repository }}"
|
|
131
122
|
|
|
132
|
-
#
|
|
123
|
+
# Raw counts only. The approval RATE is derived at query time, where it
|
|
124
|
+
# can be compared against the human-origin baseline — an absolute
|
|
125
|
+
# approval rate answers no useful question on its own.
|
|
133
126
|
REVIEWS=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
|
|
134
127
|
--jq '[.[] | select(.state == "APPROVED" or .state == "CHANGES_REQUESTED")]' 2>/dev/null || echo "[]")
|
|
135
128
|
|
|
136
129
|
APPROVED=$(echo "${REVIEWS}" | jq '[.[] | select(.state == "APPROVED")] | length')
|
|
137
130
|
CHANGES_REQUESTED=$(echo "${REVIEWS}" | jq '[.[] | select(.state == "CHANGES_REQUESTED")] | length')
|
|
138
|
-
TOTAL_REVIEWS=$((APPROVED + CHANGES_REQUESTED))
|
|
139
131
|
|
|
140
|
-
if [[ "${TOTAL_REVIEWS}" -gt 0 ]]; then
|
|
141
|
-
ACCEPTANCE_RATE=$(echo "scale=4; ${APPROVED} / ${TOTAL_REVIEWS}" | bc -l | sed 's/^\./0./')
|
|
142
|
-
else
|
|
143
|
-
ACCEPTANCE_RATE="1" # No reviews = accepted by default
|
|
144
|
-
fi
|
|
145
|
-
|
|
146
|
-
# Only meaningful for AI PRs
|
|
147
|
-
AI_RATIO="${{ steps.ai-ratio.outputs.ai_ratio }}"
|
|
148
|
-
if [[ $(echo "${AI_RATIO} > 0" | bc -l) -eq 1 ]]; then
|
|
149
|
-
AI_ACCEPTANCE_RATE="${ACCEPTANCE_RATE}"
|
|
150
|
-
else
|
|
151
|
-
AI_ACCEPTANCE_RATE=""
|
|
152
|
-
fi
|
|
153
|
-
|
|
154
|
-
echo "acceptance_rate=${ACCEPTANCE_RATE}" >> "$GITHUB_OUTPUT"
|
|
155
|
-
echo "ai_acceptance_rate=${AI_ACCEPTANCE_RATE}" >> "$GITHUB_OUTPUT"
|
|
156
132
|
echo "approved=${APPROVED}" >> "$GITHUB_OUTPUT"
|
|
157
133
|
echo "changes_requested=${CHANGES_REQUESTED}" >> "$GITHUB_OUTPUT"
|
|
158
134
|
|
|
159
135
|
- name: Emit prism.d1.pr event
|
|
160
136
|
run: |
|
|
161
137
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
162
|
-
TEAM_ID="${{ steps.prism-config.outputs.team_id }}"
|
|
163
|
-
REPO="${{ github.repository }}"
|
|
164
138
|
|
|
165
139
|
PR_EVENT=$(jq -n \
|
|
166
|
-
--arg team_id "${
|
|
167
|
-
--arg repo "${
|
|
140
|
+
--arg team_id "${{ steps.prism-config.outputs.team_id }}" \
|
|
141
|
+
--arg repo "${{ github.repository }}" \
|
|
168
142
|
--arg timestamp "${TIMESTAMP}" \
|
|
169
|
-
--argjson
|
|
170
|
-
--argjson
|
|
171
|
-
--argjson
|
|
172
|
-
--argjson
|
|
173
|
-
--argjson ai_assisted "${{ steps.ai-ratio.outputs.ai_assisted }}" \
|
|
174
|
-
--argjson lead_time_seconds "${{ steps.lead-time.outputs.lead_time_seconds }}" \
|
|
143
|
+
--argjson lead_time_seconds "${{ steps.facts.outputs.lead_time_seconds }}" \
|
|
144
|
+
--argjson is_failure_fix "${{ steps.facts.outputs.is_failure_fix }}" \
|
|
145
|
+
--argjson total_commits "${{ steps.facts.outputs.total_commits }}" \
|
|
146
|
+
--argjson commit_shas '${{ steps.facts.outputs.commit_shas }}' \
|
|
175
147
|
--arg pr_number "${{ github.event.pull_request.number }}" \
|
|
176
148
|
--arg pr_author "${{ github.event.pull_request.user.login }}" \
|
|
177
|
-
--argjson approved "${{ steps.
|
|
178
|
-
--argjson changes_requested "${{ steps.
|
|
179
|
-
--arg acceptance_rate "${{ steps.acceptance.outputs.acceptance_rate }}" \
|
|
180
|
-
--arg ai_acceptance_rate "${{ steps.acceptance.outputs.ai_acceptance_rate }}" \
|
|
181
|
-
--argjson total_input_tokens "${{ steps.ai-ratio.outputs.total_input_tokens }}" \
|
|
182
|
-
--argjson total_output_tokens "${{ steps.ai-ratio.outputs.total_output_tokens }}" \
|
|
183
|
-
--arg total_cost "${{ steps.ai-ratio.outputs.total_cost }}" \
|
|
149
|
+
--argjson approved "${{ steps.reviews.outputs.approved }}" \
|
|
150
|
+
--argjson changes_requested "${{ steps.reviews.outputs.changes_requested }}" \
|
|
184
151
|
'{
|
|
185
152
|
"team_id": $team_id,
|
|
186
153
|
"repo": $repo,
|
|
187
154
|
"timestamp": $timestamp,
|
|
188
155
|
"prism_level": 2,
|
|
189
156
|
"metric": {
|
|
190
|
-
"name": "
|
|
191
|
-
"value":
|
|
192
|
-
"unit": "
|
|
193
|
-
},
|
|
194
|
-
"ai_context": {
|
|
195
|
-
"tool": "github-actions",
|
|
196
|
-
"model": "n/a",
|
|
197
|
-
"origin": (if $ai_ratio > 0.5 then "ai-assisted" elif $ai_ratio > 0 then "ai-assisted" else "human" end)
|
|
157
|
+
"name": "pr_merged",
|
|
158
|
+
"value": 1,
|
|
159
|
+
"unit": "count"
|
|
198
160
|
},
|
|
199
161
|
"dora": {
|
|
200
|
-
"
|
|
201
|
-
"
|
|
202
|
-
},
|
|
203
|
-
"ai_dora": {
|
|
204
|
-
"ai_to_merge_ratio": $ai_ratio,
|
|
205
|
-
"ai_acceptance_rate": (if $ai_acceptance_rate != "" then ($ai_acceptance_rate | tonumber) else null end),
|
|
206
|
-
"total_commits": $total_commits,
|
|
207
|
-
"ai_commits": $ai_commits,
|
|
208
|
-
"ai_generated_commits": $ai_generated,
|
|
209
|
-
"ai_assisted_commits": $ai_assisted,
|
|
210
|
-
"total_input_tokens": $total_input_tokens,
|
|
211
|
-
"total_output_tokens": $total_output_tokens,
|
|
212
|
-
"total_cost_usd": (if $total_cost != "0" and $total_cost != "" then ($total_cost | tonumber) else 0 end)
|
|
162
|
+
"lead_time_seconds": $lead_time_seconds,
|
|
163
|
+
"is_failure_fix": $is_failure_fix
|
|
213
164
|
},
|
|
214
165
|
"pr": {
|
|
215
166
|
"number": ($pr_number | tonumber),
|
|
216
167
|
"author": $pr_author,
|
|
217
168
|
"reviews_approved": $approved,
|
|
218
|
-
"reviews_changes_requested": $changes_requested
|
|
169
|
+
"reviews_changes_requested": $changes_requested,
|
|
170
|
+
"total_commits": $total_commits,
|
|
171
|
+
"commit_shas": $commit_shas
|
|
219
172
|
}
|
|
220
173
|
}')
|
|
221
174
|
|
|
@@ -228,10 +181,18 @@ jobs:
|
|
|
228
181
|
\"Detail\": $(echo "${PR_EVENT}" | jq -c '.' | jq -Rs '.')
|
|
229
182
|
}]"
|
|
230
183
|
|
|
184
|
+
echo "Emitted prism.d1.pr for #${{ github.event.pull_request.number }}"
|
|
185
|
+
echo " lead time: ${{ steps.facts.outputs.lead_time_seconds }}s"
|
|
186
|
+
echo " failure fix: ${{ steps.facts.outputs.is_failure_fix }}"
|
|
187
|
+
echo " commit SHAs: ${{ steps.facts.outputs.total_commits }} (origin resolved at render time)"
|
|
188
|
+
|
|
231
189
|
- name: Emit prism.d1.deploy event
|
|
232
190
|
run: |
|
|
233
191
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
234
192
|
|
|
193
|
+
# Sole deployment signal. `dora.deployment_frequency` lives ONLY here
|
|
194
|
+
# and not on prism.d1.pr — both events fire on the same merge, so
|
|
195
|
+
# carrying it on both double-counted the DeploymentFrequency metric.
|
|
235
196
|
DEPLOY_EVENT=$(jq -n \
|
|
236
197
|
--arg team_id "${{ steps.prism-config.outputs.team_id }}" \
|
|
237
198
|
--arg repo "${{ github.repository }}" \
|
|
@@ -372,10 +372,76 @@ jobs:
|
|
|
372
372
|
echo "critical=${CRITICAL}" >> "$GITHUB_OUTPUT"
|
|
373
373
|
echo "high=${HIGH}" >> "$GITHUB_OUTPUT"
|
|
374
374
|
|
|
375
|
+
# Persist full findings for the EventBridge emission step. A file
|
|
376
|
+
# (not a step output) sidesteps GITHUB_OUTPUT size limits and avoids
|
|
377
|
+
# interpolating finding text into a later script (injection surface).
|
|
378
|
+
echo "$FINDINGS_JSON" > "$RUNNER_TEMP/continuum-findings.json"
|
|
379
|
+
|
|
375
380
|
if [ "$TOTAL" -gt 0 ]; then
|
|
376
381
|
echo "$FINDINGS_JSON" | jq -r '.findingsSummaries[] | "::warning::[\(.riskLevel)] \(.name)"'
|
|
377
382
|
fi
|
|
378
383
|
|
|
384
|
+
- name: Emit Continuum findings to EventBridge
|
|
385
|
+
if: steps.security-gate.outputs.skipped != 'true' && steps.security-gate.outputs.findings != '0' && steps.security-gate.outputs.findings != ''
|
|
386
|
+
continue-on-error: true
|
|
387
|
+
env:
|
|
388
|
+
AWS_REGION: us-west-2
|
|
389
|
+
TEAM_ID: ${{ steps.prism-config.outputs.team_id }}
|
|
390
|
+
REPO: ${{ github.repository }}
|
|
391
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
392
|
+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
393
|
+
HEAD_SHA: ${{ github.sha }}
|
|
394
|
+
run: |
|
|
395
|
+
# Feeds the Team Velocity / CISO dashboards: without this step,
|
|
396
|
+
# Continuum findings gate the PR but never reach the metrics
|
|
397
|
+
# pipeline (prism.d1.security.code_review -> metrics-processor).
|
|
398
|
+
FINDINGS_FILE="$RUNNER_TEMP/continuum-findings.json"
|
|
399
|
+
if [ ! -s "$FINDINGS_FILE" ]; then
|
|
400
|
+
echo "No findings file — nothing to emit"
|
|
401
|
+
exit 0
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
# Emit the PR's commit SHAs, NOT a derived AI-origin verdict.
|
|
405
|
+
#
|
|
406
|
+
# Deferred join: dashboards resolve origin at render time by looking
|
|
407
|
+
# these SHAs up in the attribution store. Two reasons not to decide
|
|
408
|
+
# here: (1) trailer grep dies with the git hooks, and (2) attribution
|
|
409
|
+
# spans arrive via `codeburn sync push --attribution` on a ~12h
|
|
410
|
+
# schedule, so at merge time the data usually does not exist yet — a
|
|
411
|
+
# verdict computed now would be wrong and permanently baked into an
|
|
412
|
+
# immutable event. SHAs are facts that never expire, so a PR merged
|
|
413
|
+
# before its attribution syncs simply renders correctly the next day.
|
|
414
|
+
#
|
|
415
|
+
# Capped at 50 to bound event size; PRs larger than that are rare and
|
|
416
|
+
# a partial sample is enough to classify origin.
|
|
417
|
+
COMMIT_SHAS=$(git rev-list "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null | head -50 | jq -R . | jq -sc . || echo '[]')
|
|
418
|
+
echo "Emitting findings with $(echo "${COMMIT_SHAS}" | jq 'length') commit SHAs for deferred origin resolution"
|
|
419
|
+
|
|
420
|
+
ENTRIES=$(jq --arg team_id "${TEAM_ID}" --arg repo "${REPO}" \
|
|
421
|
+
--argjson commit_shas "${COMMIT_SHAS}" --argjson pr_number "${PR_NUMBER}" '
|
|
422
|
+
.findingsSummaries | to_entries | map(.value as $f |
|
|
423
|
+
{Source:"prism.d1.velocity", DetailType:"prism.d1.security.code_review",
|
|
424
|
+
EventBusName:"prism-d1-metrics",
|
|
425
|
+
Detail:({team_id:$team_id, repo:$repo, timestamp:(now | todate), prism_level:3,
|
|
426
|
+
metric:{name:"security_finding",value:1,unit:"count"},
|
|
427
|
+
ai_context:{tool:"continuum",model:"aws-continuum"},
|
|
428
|
+
security_agent_finding:{finding_id:($f.findingId // "cf-pr\($pr_number)-\(.key)"), phase:"code_review",
|
|
429
|
+
severity:($f.riskLevel // "MEDIUM"),
|
|
430
|
+
cwe_id:($f.cweId // null),
|
|
431
|
+
commit_shas:$commit_shas, pr_number:$pr_number,
|
|
432
|
+
found_at:($f.createdAt // null)}
|
|
433
|
+
} | tostring)})' "$FINDINGS_FILE" 2>/dev/null || echo "[]")
|
|
434
|
+
|
|
435
|
+
TOTAL=$(echo "${ENTRIES}" | jq 'length')
|
|
436
|
+
if [ "$TOTAL" -gt 0 ]; then
|
|
437
|
+
# EventBridge accepts max 10 entries per call
|
|
438
|
+
for i in $(seq 0 10 $((TOTAL - 1))); do
|
|
439
|
+
aws events put-events --region us-west-2 \
|
|
440
|
+
--entries "$(echo "${ENTRIES}" | jq -c ".[$i:$((i+10))]")" || true
|
|
441
|
+
done
|
|
442
|
+
echo "Emitted ${TOTAL} Continuum finding events to EventBridge"
|
|
443
|
+
fi
|
|
444
|
+
|
|
379
445
|
- name: Fail on eval gate failure
|
|
380
446
|
if: ${{ steps.review.outputs.result == 'FAIL' || steps.review.outputs.result == 'ERROR' || (steps.security-gate.outputs.skipped != 'true' && (steps.security-gate.outputs.critical != '0' || steps.security-gate.outputs.high != '0')) }}
|
|
381
447
|
env:
|
|
@@ -426,7 +426,7 @@ jobs:
|
|
|
426
426
|
Detail:({team_id:$team_id, repo:$repo, timestamp:($f.createdAt // now | tostring), prism_level:3,
|
|
427
427
|
metric:{name:"security_finding",value:1,unit:"count"},
|
|
428
428
|
ai_context:{tool:"continuum",model:"aws-continuum",origin:$ai_origin},
|
|
429
|
-
|
|
429
|
+
security_agent_finding:{finding_id:($f.findingId // "cf-pr\($pr_number)-\(.key)"), phase:"code_review",
|
|
430
430
|
severity:($f.riskLevel // "MEDIUM"),
|
|
431
431
|
cwe_id:($f.cweId // null),
|
|
432
432
|
ai_origin:$ai_origin, found_at:($f.createdAt // null)}
|
|
@@ -8,17 +8,14 @@
|
|
|
8
8
|
# 2. Add CI/CD variable PRISM_METRICS_ROLE_ARN with the output role ARN
|
|
9
9
|
# 3. Copy gitlab-workflows/*.yml to .prism/gitlab-workflows/ in your repo
|
|
10
10
|
# 4. (Optional) Set GITLAB_OIDC_AUDIENCE if not using gitlab.aws.dev
|
|
11
|
-
# 5. (Optional) Create a pipeline schedule for prism-dora-weekly (Monday 09:00 UTC)
|
|
12
11
|
|
|
13
12
|
workflow:
|
|
14
13
|
rules:
|
|
15
14
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
16
15
|
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
|
17
|
-
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
|
18
16
|
- if: '$CI_PIPELINE_SOURCE == "web"'
|
|
19
17
|
|
|
20
18
|
include:
|
|
21
19
|
- local: '.prism/gitlab-workflows/prism-ai-metrics.yml'
|
|
22
|
-
- local: '.prism/gitlab-workflows/prism-dora-weekly.yml'
|
|
23
20
|
- local: '.prism/gitlab-workflows/prism-agent-eval.yml'
|
|
24
21
|
- local: '.prism/gitlab-workflows/prism-eval-gate.yml'
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
# prism-ai-metrics.yml —
|
|
1
|
+
# prism-ai-metrics.yml — Per-merge delivery telemetry (GitLab).
|
|
2
2
|
#
|
|
3
3
|
# Include this file in your .gitlab-ci.yml:
|
|
4
4
|
# include:
|
|
5
5
|
# - local: '.prism/gitlab-workflows/prism-ai-metrics.yml'
|
|
6
6
|
#
|
|
7
|
+
# DESIGN: emits immutable per-MR FACTS and computes no rates. All aggregation
|
|
8
|
+
# (frequency, lead-time percentiles, change-failure rate, MTTR) happens at query
|
|
9
|
+
# time in the dashboard widgets. See the GitHub twin at
|
|
10
|
+
# bootstrapper/github-workflows/prism-ai-metrics.yml for the full rationale.
|
|
11
|
+
#
|
|
12
|
+
# AI origin is NOT computed here — it is resolved at render time by joining
|
|
13
|
+
# pr.commit_shas against the codeburn attribution store. See
|
|
14
|
+
# docs/DATA-ARCHITECTURE.md → "AI Origin Resolution: Deferred Attribution Join".
|
|
15
|
+
#
|
|
16
|
+
# Replaces the former prism-dora-weekly.yml, which published a parallel 7-day
|
|
17
|
+
# aggregate that no dashboard consumed.
|
|
18
|
+
#
|
|
7
19
|
# Required setup:
|
|
8
20
|
# - CI/CD variable PRISM_METRICS_ROLE_ARN (IAM role ARN with events:PutEvents)
|
|
9
|
-
# -
|
|
10
|
-
# -
|
|
11
|
-
# - AWS credentials configured via OIDC or CI/CD variables
|
|
21
|
+
# - .prism/config.json committed to the repo (optional; defaults to no_team)
|
|
22
|
+
# - AWS credentials configured via OIDC
|
|
12
23
|
|
|
13
24
|
prism-ai-metrics:
|
|
14
25
|
stage: test
|
|
@@ -23,7 +34,7 @@ prism-ai-metrics:
|
|
|
23
34
|
variables:
|
|
24
35
|
AWS_DEFAULT_REGION: us-west-2
|
|
25
36
|
before_script:
|
|
26
|
-
- yum install -y -q jq
|
|
37
|
+
- yum install -y -q jq git
|
|
27
38
|
- git fetch --unshallow || true
|
|
28
39
|
- |
|
|
29
40
|
# Assume AWS role via GitLab OIDC
|
|
@@ -47,87 +58,69 @@ prism-ai-metrics:
|
|
|
47
58
|
fi
|
|
48
59
|
script:
|
|
49
60
|
- |
|
|
50
|
-
# Skip if no AWS credentials available
|
|
51
61
|
if ! aws sts get-caller-identity &>/dev/null; then
|
|
52
62
|
echo "⚠️ No AWS credentials. Set PRISM_METRICS_ROLE_ARN or configure AWS access."
|
|
53
63
|
exit 0
|
|
54
64
|
fi
|
|
55
65
|
|
|
56
|
-
# Read team ID
|
|
57
66
|
TEAM_ID=$(jq -r '.team_id // "no_team"' .prism/config.json 2>/dev/null || echo "no_team")
|
|
58
67
|
|
|
59
|
-
#
|
|
68
|
+
# Commit range for this merge
|
|
60
69
|
if [[ -n "${CI_MERGE_REQUEST_DIFF_BASE_SHA}" ]]; then
|
|
61
70
|
BASE_SHA="${CI_MERGE_REQUEST_DIFF_BASE_SHA}"
|
|
62
|
-
HEAD_SHA="${CI_COMMIT_SHA}"
|
|
63
71
|
else
|
|
64
72
|
BASE_SHA=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD)
|
|
65
|
-
HEAD_SHA="${CI_COMMIT_SHA}"
|
|
66
73
|
fi
|
|
74
|
+
HEAD_SHA="${CI_COMMIT_SHA}"
|
|
67
75
|
|
|
76
|
+
# --- Commit SHAs: immutable facts for deferred origin resolution.
|
|
77
|
+
COMMIT_SHAS=$(git rev-list "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null | head -50 | jq -R . | jq -sc . || echo '[]')
|
|
68
78
|
TOTAL_COMMITS=$(git rev-list --count "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null || echo "1")
|
|
69
79
|
|
|
70
|
-
#
|
|
71
|
-
AI_GENERATED=0; AI_ASSISTED=0; HUMAN_ONLY=0
|
|
72
|
-
TOTAL_INPUT_TOKENS=0; TOTAL_OUTPUT_TOKENS=0; TOTAL_COST="0"
|
|
73
|
-
|
|
74
|
-
for SHA in $(git rev-list "${BASE_SHA}..${HEAD_SHA}" 2>/dev/null || echo "${HEAD_SHA}"); do
|
|
75
|
-
MSG=$(git log -1 --format='%B' "${SHA}")
|
|
76
|
-
echo "${MSG}" | grep -q '^AI-Origin: ai-generated' && AI_GENERATED=$((AI_GENERATED + 1))
|
|
77
|
-
echo "${MSG}" | grep -q '^AI-Origin: ai-assisted' && AI_ASSISTED=$((AI_ASSISTED + 1))
|
|
78
|
-
echo "${MSG}" | grep -q '^AI-Origin: human' && HUMAN_ONLY=$((HUMAN_ONLY + 1))
|
|
79
|
-
|
|
80
|
-
INPUT_T=$(echo "${MSG}" | grep -oP '(?<=^AI-Input-Tokens: )\d+' || echo "0")
|
|
81
|
-
OUTPUT_T=$(echo "${MSG}" | grep -oP '(?<=^AI-Output-Tokens: )\d+' || echo "0")
|
|
82
|
-
COST_T=$(echo "${MSG}" | grep -oP '(?<=^AI-Cost: )[0-9.]+' || echo "0")
|
|
83
|
-
[[ ${INPUT_T} -gt 1000000 ]] && INPUT_T=0
|
|
84
|
-
[[ ${OUTPUT_T} -gt 1000000 ]] && OUTPUT_T=0
|
|
85
|
-
TOTAL_INPUT_TOKENS=$((TOTAL_INPUT_TOKENS + INPUT_T))
|
|
86
|
-
TOTAL_OUTPUT_TOKENS=$((TOTAL_OUTPUT_TOKENS + OUTPUT_T))
|
|
87
|
-
TOTAL_COST=$(echo "${TOTAL_COST} + ${COST_T}" | bc -l)
|
|
88
|
-
done
|
|
89
|
-
|
|
90
|
-
AI_COMMITS=$((AI_GENERATED + AI_ASSISTED))
|
|
91
|
-
[[ "${TOTAL_COMMITS}" -gt 0 ]] && AI_RATIO=$(echo "scale=4; ${AI_COMMITS} / ${TOTAL_COMMITS}" | bc -l | sed 's/^\./0./') || AI_RATIO="0"
|
|
92
|
-
|
|
93
|
-
# Calculate lead time (MR created to merged)
|
|
80
|
+
# --- Lead time: MR open → now (this job runs on the post-merge push).
|
|
94
81
|
LEAD_TIME_SECONDS=0
|
|
95
82
|
if [[ -n "${CI_MERGE_REQUEST_CREATED_AT}" ]]; then
|
|
96
83
|
CREATED_TS=$(date -d "${CI_MERGE_REQUEST_CREATED_AT}" +%s 2>/dev/null || echo "0")
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
if [[ "${CREATED_TS}" -gt 0 ]]; then
|
|
85
|
+
LEAD_TIME_SECONDS=$(( $(date +%s) - CREATED_TS ))
|
|
86
|
+
[[ "${LEAD_TIME_SECONDS}" -lt 0 ]] && LEAD_TIME_SECONDS=0
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# --- Failure-fix label: change-failure-rate numerator as a per-MR fact.
|
|
91
|
+
# On the post-merge push pipeline CI_MERGE_REQUEST_TITLE is usually unset,
|
|
92
|
+
# so fall back to the merge commit title. Matched via printf against a
|
|
93
|
+
# variable rather than interpolated, since titles are user-controlled.
|
|
94
|
+
FIX_TITLE="${CI_MERGE_REQUEST_TITLE:-${CI_COMMIT_TITLE:-}}"
|
|
95
|
+
if printf '%s' "${FIX_TITLE}" | grep -qiE 'revert|hotfix|rollback'; then
|
|
96
|
+
IS_FAILURE_FIX=true
|
|
97
|
+
else
|
|
98
|
+
IS_FAILURE_FIX=false
|
|
99
99
|
fi
|
|
100
100
|
|
|
101
101
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
102
102
|
|
|
103
|
+
# NOTE: no review verdicts here. GitLab approvals would need an API call
|
|
104
|
+
# with a project token; the GitHub twin gets them from the reviews API.
|
|
105
|
+
# Emitted under `pr` (not `mr`) so both CI providers land in the same
|
|
106
|
+
# shape — the processor only persists a known section list.
|
|
103
107
|
PR_EVENT=$(jq -n \
|
|
104
108
|
--arg team_id "${TEAM_ID}" \
|
|
105
109
|
--arg repo "${CI_PROJECT_PATH}" \
|
|
106
110
|
--arg timestamp "${TIMESTAMP}" \
|
|
107
|
-
--argjson ai_ratio "${AI_RATIO}" \
|
|
108
|
-
--argjson total_commits "${TOTAL_COMMITS}" \
|
|
109
|
-
--argjson ai_commits "${AI_COMMITS}" \
|
|
110
|
-
--argjson ai_generated "${AI_GENERATED}" \
|
|
111
|
-
--argjson ai_assisted "${AI_ASSISTED}" \
|
|
112
111
|
--argjson lead_time_seconds "${LEAD_TIME_SECONDS}" \
|
|
112
|
+
--argjson is_failure_fix "${IS_FAILURE_FIX}" \
|
|
113
|
+
--argjson total_commits "${TOTAL_COMMITS}" \
|
|
114
|
+
--argjson commit_shas "${COMMIT_SHAS}" \
|
|
113
115
|
--arg mr_iid "${CI_MERGE_REQUEST_IID:-0}" \
|
|
114
116
|
--arg mr_author "${GITLAB_USER_LOGIN}" \
|
|
115
|
-
--argjson total_input_tokens "${TOTAL_INPUT_TOKENS}" \
|
|
116
|
-
--argjson total_output_tokens "${TOTAL_OUTPUT_TOKENS}" \
|
|
117
|
-
--arg total_cost "${TOTAL_COST}" \
|
|
118
117
|
'{
|
|
119
118
|
team_id: $team_id, repo: $repo, timestamp: $timestamp, prism_level: 2,
|
|
120
|
-
metric: {name: "
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
ai_commits: $ai_commits, ai_generated_commits: $ai_generated,
|
|
126
|
-
ai_assisted_commits: $ai_assisted,
|
|
127
|
-
total_input_tokens: $total_input_tokens,
|
|
128
|
-
total_output_tokens: $total_output_tokens,
|
|
129
|
-
total_cost_usd: (if $total_cost != "0" and $total_cost != "" then ($total_cost | tonumber) else 0 end)},
|
|
130
|
-
mr: {iid: ($mr_iid | tonumber), author: $mr_author}
|
|
119
|
+
metric: {name: "pr_merged", value: 1, unit: "count"},
|
|
120
|
+
dora: {lead_time_seconds: $lead_time_seconds, is_failure_fix: $is_failure_fix},
|
|
121
|
+
pr: {number: ($mr_iid | tonumber), author: $mr_author,
|
|
122
|
+
reviews_approved: 0, reviews_changes_requested: 0,
|
|
123
|
+
total_commits: $total_commits, commit_shas: $commit_shas}
|
|
131
124
|
}')
|
|
132
125
|
|
|
133
126
|
aws events put-events --region "${AWS_DEFAULT_REGION}" \
|
|
@@ -138,7 +131,8 @@ prism-ai-metrics:
|
|
|
138
131
|
\"Detail\": $(echo "${PR_EVENT}" | jq -c '.' | jq -Rs '.')
|
|
139
132
|
}]"
|
|
140
133
|
|
|
141
|
-
#
|
|
134
|
+
# Sole deployment signal — deployment_frequency lives ONLY on this event.
|
|
135
|
+
# Carrying it on prism.d1.pr as well double-counted the metric.
|
|
142
136
|
DEPLOY_EVENT=$(jq -n --arg team_id "${TEAM_ID}" --arg repo "${CI_PROJECT_PATH}" --arg timestamp "${TIMESTAMP}" \
|
|
143
137
|
'{team_id: $team_id, repo: $repo, timestamp: $timestamp, prism_level: 2,
|
|
144
138
|
metric: {name: "deployment", value: 1, unit: "count"},
|
|
@@ -151,3 +145,6 @@ prism-ai-metrics:
|
|
|
151
145
|
\"EventBusName\": \"prism-d1-metrics\",
|
|
152
146
|
\"Detail\": $(echo "${DEPLOY_EVENT}" | jq -c '.' | jq -Rs '.')
|
|
153
147
|
}]"
|
|
148
|
+
|
|
149
|
+
echo "Emitted prism.d1.pr + prism.d1.deploy"
|
|
150
|
+
echo " lead time: ${LEAD_TIME_SECONDS}s · failure fix: ${IS_FAILURE_FIX} · commits: ${TOTAL_COMMITS}"
|
|
@@ -42,6 +42,7 @@ const dashboardStack = new DashboardStack(app, 'PrismD1Dashboard', {
|
|
|
42
42
|
env,
|
|
43
43
|
description: 'PRISM D1 Velocity - CloudWatch dashboards and alarms',
|
|
44
44
|
productivityWidgetArn: pipelineStack.otelCollector?.productivityWidgetArn,
|
|
45
|
+
velocityWidgetArn: pipelineStack.velocityWidgetArn,
|
|
45
46
|
tags: {
|
|
46
47
|
'prism:project': 'PRISM',
|
|
47
48
|
'prism:domain': 'D1-Velocity',
|
|
@@ -56,6 +56,9 @@ export class OtelCollectorConstruct extends Construct {
|
|
|
56
56
|
public readonly userPoolClient?: cognito.UserPoolClient;
|
|
57
57
|
/** ARN of the CloudWatch custom-widget Lambda (Developer Productivity table). */
|
|
58
58
|
public productivityWidgetArn?: string;
|
|
59
|
+
/** The otel-receiver Lambda — other constructs may grant themselves invoke
|
|
60
|
+
* for direct aggregation queries (single implementation, no drift). */
|
|
61
|
+
public receiverFunction!: lambda.Function;
|
|
59
62
|
|
|
60
63
|
constructor(scope: Construct, id: string, props: OtelCollectorConstructProps) {
|
|
61
64
|
super(scope, id);
|
|
@@ -198,6 +201,7 @@ export class OtelCollectorConstruct extends Construct {
|
|
|
198
201
|
props.aiUsageTable.grantReadWriteData(receiver);
|
|
199
202
|
this.archiveBucket.grantPut(receiver);
|
|
200
203
|
props.kmsKey.grantEncryptDecrypt(receiver);
|
|
204
|
+
this.receiverFunction = receiver;
|
|
201
205
|
|
|
202
206
|
// -------------------------------------------------------
|
|
203
207
|
// CloudWatch Custom Widget: Developer Productivity
|