Part of epic #8286 (Phase 3 — AI observability). Follow-on to #10226, which closed on #10228's merge with two of its six named sources wired.
What actually landed under #10226
#10228 wired the first two deliverables and closed the issue:
reviewer_vote_fail — per reviewer, at src/queue/ai-review-orchestration.ts
judgment_agreement / judgment_sample_count — once per review, via judgmentAgreementMetrics
Live in PostHog since the 14:00Z image, so both are confirmed working end to end. But #10226 listed six sources and said "the remaining four follow", and the issue auto-closed on the linked-PR merge before they did. This issue carries the remainder — and narrows it, because three of the four turn out not to be $ai_metric material at all.
Three of the four are structurally ineligible, not merely unwired
capturePostHogAiMetric joins a measurement to the ambient OTel trace and drops the event when there is no trace:
const traceId = currentOtelTraceIds()?.trace_id;
if (!traceId) return;
That guard is deliberate — #10226 asked for it, so an orphan quality score cannot join to nothing and inflate counts. It also decides the scope here. Tracing the three remaining aggregate sources to their callers:
| Source |
Called from |
Ambient AI trace? |
loadGatePrecisionReport (src/services/gate-precision.ts) |
src/mcp/server.ts:3736, src/review/maintainer-recap-wire.ts:196, src/review/ops-wire.ts:275 |
No — MCP tool handler, recap and ops reporting |
loadPublicRulePrecision (src/review/public-rule-precision.ts) |
src/api/routes.ts |
No — public HTTP endpoint |
buildEvalScoreRecordsFromRulePrecision (src/review/eval-score-records.ts) |
src/api/routes.ts:966 (/v1/public/eval-scores) |
No — public HTTP endpoint |
None of these runs under a review's AI trace. They are corpus-level aggregates over a 90-day window, read on request by a reporting or serving path. Wiring capturePostHogAiMetric into any of them produces a call the helper's own guard discards on every invocation — instrumentation that provably never fires, which is the same "don't instrument ahead of traffic" call already made on #10200.
These are windowed rates, not per-review measurements. If gate and rule precision are wanted in PostHog they belong in a different shape — a periodic gauge keyed on the repo group, not a metric hung off a trace id that does not exist at the call site. That is a separate design question and deliberately not opened here.
Self-consistency is already covered
src/review/self-consistency.ts needs no separate emission. The orchestration call site already folds its samples into the agreement metric:
judgmentAgreementMetrics([...result.reviewerVotes, ...result.selfConsistencySamples])
The N=2–3 rotated-exemplar sampling that would produce a distinct self-consistency signal is explicitly not implemented (see the closing comment in src/review/judgment-agreement.ts) — it needs a per-review AI budget decision and a flag defaulting off, tracked under #8834. There is nothing to emit until that ships.
The one real deliverable: routing shadow
recordRoutingShadow (src/services/reviewer-routing.ts:135) runs at the same orchestration call site as the two metrics that already work, so it does have an ambient trace. It returns a RoutingShadowDecision | null naming the provider evidence-weighted routing would have preferred, against the providers the review actually used — and today that comparison reaches only the audit log and the recap.
As an $ai_metric it joins directly to the cost and model of the review that produced it, which is the whole point: whether the shadow's preference agrees with what ran is only interesting next to what that run cost.
Deliverables
Not in scope
Gate precision, rule precision and eval score records, for the reason above. $ai_evaluation remains PostHog's output rather than ours (#10232 covers the evaluation side).
Part of epic #8286 (Phase 3 — AI observability). Follow-on to #10226, which closed on #10228's merge with two of its six named sources wired.
What actually landed under #10226
#10228wired the first two deliverables and closed the issue:reviewer_vote_fail— per reviewer, atsrc/queue/ai-review-orchestration.tsjudgment_agreement/judgment_sample_count— once per review, viajudgmentAgreementMetricsLive in PostHog since the 14:00Z image, so both are confirmed working end to end. But #10226 listed six sources and said "the remaining four follow", and the issue auto-closed on the linked-PR merge before they did. This issue carries the remainder — and narrows it, because three of the four turn out not to be
$ai_metricmaterial at all.Three of the four are structurally ineligible, not merely unwired
capturePostHogAiMetricjoins a measurement to the ambient OTel trace and drops the event when there is no trace:That guard is deliberate — #10226 asked for it, so an orphan quality score cannot join to nothing and inflate counts. It also decides the scope here. Tracing the three remaining aggregate sources to their callers:
loadGatePrecisionReport(src/services/gate-precision.ts)src/mcp/server.ts:3736,src/review/maintainer-recap-wire.ts:196,src/review/ops-wire.ts:275loadPublicRulePrecision(src/review/public-rule-precision.ts)src/api/routes.tsbuildEvalScoreRecordsFromRulePrecision(src/review/eval-score-records.ts)src/api/routes.ts:966(/v1/public/eval-scores)None of these runs under a review's AI trace. They are corpus-level aggregates over a 90-day window, read on request by a reporting or serving path. Wiring
capturePostHogAiMetricinto any of them produces a call the helper's own guard discards on every invocation — instrumentation that provably never fires, which is the same "don't instrument ahead of traffic" call already made on #10200.These are windowed rates, not per-review measurements. If gate and rule precision are wanted in PostHog they belong in a different shape — a periodic gauge keyed on the repo group, not a metric hung off a trace id that does not exist at the call site. That is a separate design question and deliberately not opened here.
Self-consistency is already covered
src/review/self-consistency.tsneeds no separate emission. The orchestration call site already folds its samples into the agreement metric:The N=2–3 rotated-exemplar sampling that would produce a distinct self-consistency signal is explicitly not implemented (see the closing comment in
src/review/judgment-agreement.ts) — it needs a per-review AI budget decision and a flag defaulting off, tracked under #8834. There is nothing to emit until that ships.The one real deliverable: routing shadow
recordRoutingShadow(src/services/reviewer-routing.ts:135) runs at the same orchestration call site as the two metrics that already work, so it does have an ambient trace. It returns aRoutingShadowDecision | nullnaming the provider evidence-weighted routing would have preferred, against the providers the review actually used — and today that comparison reaches only the audit log and the recap.As an
$ai_metricit joins directly to the cost and model of the review that produced it, which is the whole point: whether the shadow's preference agrees with what ran is only interesting next to what that run cost.Deliverables
recordRoutingShadowcall site insrc/queue/ai-review-orchestration.ts, using the decision it already returns — no second load of the track records.recordRoutingShadowreturnsnull(no decision) or throws — it is fail-safe by contract and must stay that way; the review path cannot be affected.capturePostHogAiMetric's existing guard rather than re-implemented.Not in scope
Gate precision, rule precision and eval score records, for the reason above.
$ai_evaluationremains PostHog's output rather than ours (#10232 covers the evaluation side).