Skip to content

feat(power): ingest and expose power audit provenance / 功率:摄取并公开功耗审计溯源字段(power_invalid_reasons、power_audit) - #929

Closed
edwingao28 wants to merge 3 commits into
klaud/powerx-04-api-contractfrom
klaud/powerx-07-provenance-ingest
Closed

feat(power): ingest and expose power audit provenance / 功率:摄取并公开功耗审计溯源字段(power_invalid_reasons、power_audit)#929
edwingao28 wants to merge 3 commits into
klaud/powerx-04-api-contractfrom
klaud/powerx-07-provenance-ingest

Conversation

@edwingao28

@edwingao28 edwingao28 commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Important

STACKED PR — do not merge out of order. Base is klaud/powerx-04-api-contract (#921), which is itself stacked on #909. Merge order: #909#921 → this PR. This PR contains only the PLAN-07 deltas relative to #921.

Implements PLAN-07: ingest and expose the power audit provenance fields (power_invalid_reasons, power_audit) that the producer (InferenceX aggregate_power.py, PLAN-06) emits alongside the power_valid verdict. PLAN-04 (#921) reserved the two fields in the OpenAPI schema; this PR turns them live end-to-end while remaining fully tolerant of their absence (legacy artifacts, pre-PLAN-06 runs, legacy DB rows).

What changed

Storage — migration 014_power_provenance.sql

  • Two dedicated jsonb columns on benchmark_results (power_invalid_reasons, power_audit), mirroring the migration-006 workers precedent: metrics is a flat Record<string, number>, so structured data gets its own columns.
  • Recreates latest_benchmarks with the migration-012 definition verbatim (recursive append-only-curve body plus both indexes) solely so br.* picks up the new columns — not the obsolete 006 definition.

ETL — benchmark-mapper.ts

  • Both keys join NON_METRIC_KEYS: Number(['5']) === 5, so without the guard a malformed single-element reasons array would be auto-captured as a bogus numeric metric.
  • extractPowerInvalidReasons: keeps snake_case codes (≤ 64 chars), dedupes preserving order, caps at 32; empty result → undefined so the column stores SQL NULL, never [].
  • extractPowerAudit: fixed 8-key shape (window_start_unix, window_end_unix, expected_gpu_count, observed_gpu_count, sample_count, max_sample_gap_s, producer_sha, exporter_image_sha256); finite-number/safe-integer narrowing, malformed numerics omitted (partial audit beats none), shas collapse to null per the contract's string|null, unknown keys dropped, empty husk → undefined.
  • Extraction is unconditional on power_valid — tolerance in both directions.
  • No changes needed in ingest-ci-run.ts or run-overrides.ts: the persistence input is built by spreading ({...row, configId} / applyBenchmarkPointBackfill's {...point}), so the new BenchmarkParams fields flow through.

Ingest — benchmark-ingest.ts

  • Two jsonb unnest lanes mirroring workers; NULL when absent; excluded.* refresh on conflict (fresh artifact is authoritative, same as workers).

Reads — queries/benchmarks.ts (deploy-order safety, the #405/#407 lesson)

  • All four read paths select the columns as to_jsonb(br) -> 'power_invalid_reasons' / to_jsonb(lb) -> ... — never bare column references. Migrations run in the ingest workflows, not at Vercel deploy; a bare reference fails at query plan time until the next ingest applies migration 014, which is exactly how PR feat(power): measured-power multinode support (workers[] + per-stage joules) #405 produced a ~63% error rate. The jsonb key lookup degrades to NULL while the column is missing and is byte-identical once it exists, so merge order and deploy timing are irrelevant. A regression test pins the tolerant form with a negative regex.

API & frontend

  • /api/v1/benchmarks and /history return the fields verbatim when stored; calculator view strips them (payload-trimmed projection excludes measured-power data by design); unofficial-run overlay rows carry both fields via the shared mapper.
  • Chart tooltips (official + unofficial overlay) render a muted bilingual "Measured power withheld / 实测功耗未采信" line only when the point carries reasons; codes are re-validated against the snake_case regex before HTML interpolation (tooltips are raw HTML strings).
  • OpenAPI schema wording moves from "Reserved (forthcoming)" to live; api-route-catalog.ts digests refreshed for the two changed contract sources.
  • power_audit is API-only — no UI surface (follow-up).

Testing

  • bun run --cwd packages/db test:unit — 650 passed (extractor suites, mapper integration incl. the ['5'] guard, ingest lane recording-mock, query-shape assertions with the bare-reference regex guard, supplemental-path provenance sequence)
  • bun run --cwd packages/app test:unit — 4491 passed (transform narrowing, tooltip en/zh + injection filtering + overlay parity, unofficial-run overlay parity, calculator strip, OpenAPI docs invariants, catalog guard)
  • bun run typecheck / bun run lint / bun run fmt — clean
  • Note: packages/mcp server.test.ts fails identically on the base branch in this environment (z.enum undefined — zod resolution, unrelated to this change).

Deploy-order proof: this PR's Vercel preview serves /api/v1/benchmarks against the un-migrated production DB with the two keys null — the live demonstration of the tolerant-read design. The next stage-results/ingest run applies migration 014 and new ingests populate the columns.

Review

Two independent review passes ran over the implementation; one approved with no findings, the other approved with two nits, both addressed:

  1. Supplemental ingest lane dropped provenance (CONFIRMED, nit)ingest-supplemental.ts participates in the power publication contract (PLAN-03's normalize + scrub) but built its persistence input without powerInvalidReasons/powerAudit, so a supplemental entry carrying the fields would persist NULL columns silently. Fixed in the follow-up commit: the lane now extracts both via the shared narrowers (entry-level fields sibling to metrics, with a metrics-nested fallback since power_valid rides in metrics in that format) and deletes the keys from metrics so the persisted jsonb stays a flat numeric record; the call sequence is pinned next to the PLAN-03 supplemental tests.
  2. Vercel-preview live proof not independently probed (PLAUSIBLE, nit) — the preview deployment is auth-protected (302), so the reviewer could not hit /api/v1/benchmarks directly. Indirect evidence is strong (green Vercel check, to_jsonb reads pinned by a negative-regex test, Postgres jsonb semantics), but a human with Vercel access should hit the preview once and confirm 200 with power_invalid_reasons/power_audit null before merging the stack.

中文说明

堆叠 PR——请勿乱序合并。 基础分支为 klaud/powerx-04-api-contract#921),后者又堆叠在 #909 之上。合并顺序:#909#921 → 本 PR

实现 PLAN-07:摄取并公开生产端(InferenceX aggregate_power.py,PLAN-06)随 power_valid 判定一同产出的功耗审计溯源字段(power_invalid_reasonspower_audit)。PLAN-04(#921)已在 OpenAPI 中预留这两个字段;本 PR 将其全链路转正,并对字段缺失(旧产物、早于 PLAN-06 的运行、历史数据行)保持完全兼容。

  • 存储:迁移 014 在 benchmark_results 上新增两个专用 jsonb 列(沿用迁移 006 workers 的先例),并按迁移 012 的定义原样重建 latest_benchmarks,使 br.* 覆盖新列。
  • ETLmapBenchmarkRow 防御性收窄两个字段(snake_case 原因码去重限长限量;审计对象固定 8 键、剔除非法数值、sha 归一化为 string|null、丢弃未知键;空结果映射为 undefined 以存储 SQL NULL);两个键加入 NON_METRIC_KEYS,避免 Number(['5']) === 5 生成伪数值指标。
  • 摄取:批量插入新增两条 jsonb 通道,缺失即 NULL,冲突时以新产物为准刷新。
  • 读取(部署顺序安全,feat(power): measured-power multinode support (workers[] + per-stage joules) #405/fix(db): tolerate missing workers column in benchmark read queries / 基准测试读取查询兼容缺失的 workers 列 #407 教训):四条读取路径一律使用 to_jsonb(...) -> 'col' 容错形式,列尚未迁移时读取为 NULL,迁移后逐字节等价,合并与部署顺序均无关;回归测试用反向正则钉死该形式。
  • API 与前端:公开 API 原样返回字段;计算器视图剥离;非官方叠加行同样携带;图表提示框仅在存在原因码时渲染双语"实测功耗未采信"行,且在 HTML 插值前再次校验原因码;OpenAPI 文案由预留改为正式,契约摘要已刷新。

测试:db 包 650 项、app 包 4491 项全部通过;typecheck / lint / fmt 干净。(packages/mcpserver.test.ts 在本地环境于基础分支上即失败,与本变更无关。)

评审:两轮独立评审均通过,其中一轮提出两条次要意见——补充数据摄取通道(ingest-supplemental.ts)此前未持久化溯源字段,已在后续提交中修复并以单测钉住调用序列;Vercel 预览部署受访问保护,评审无法直接探测,请有权限的同事在合并前访问预览的 /api/v1/benchmarks 确认返回 200 且两个新键为 null。

🤖 Generated with Claude Code


Note

Medium Risk
Touches benchmark ingest, matview recreation, and all benchmark read SQL; deploy-order tolerance mitigates migration timing, but a bad matview rebuild or ingest regression could affect latest-curve queries and power tooltip HTML.

Overview
PLAN-07 turns power_invalid_reasons and power_audit from reserved API placeholders into live end-to-end provenance for measured-power verdicts from aggregate_power.py.

Storage & ingest: Migration 014 adds dedicated JSONB columns on benchmark_results (same pattern as workers) and rebuilds latest_benchmarks so the matview includes them. mapBenchmarkRow extracts and narrows both fields (extractPowerInvalidReasons / extractPowerAudit), keeps them out of flat metrics via NON_METRIC_KEYS, and bulk ingest writes them with NULL lanes for legacy rows.

Reads: All benchmark query paths select via to_jsonb(...) -> 'power_*' so deploys stay safe before migration 014 is applied on ingest (avoids the #405/#407 plan-time failure mode).

API & UI: Public benchmark rows and unofficial-run overlays surface the fields; the calculator trim strips them. Chart tooltips add a bilingual “Measured power withheld” line when reason codes exist, with snake_case re-validation before raw HTML. OpenAPI/docs drop “forthcoming” wording.

Reviewed by Cursor Bugbot for commit 8a6a0ee. Bugbot is set up for automated code reviews on this repo. Configure here.

Wenyao Gao and others added 2 commits August 27, 2026 16:35
Turn the reserved power_invalid_reasons / power_audit contract fields live
end-to-end:

- migration 014 adds dedicated jsonb columns on benchmark_results and
  recreates latest_benchmarks with the migration-012 definition verbatim so
  br.* picks up the new columns
- mapBenchmarkRow narrows both fields defensively (snake_case reason codes,
  fixed 8-key audit shape, empty -> undefined so the columns store SQL NULL)
  and both keys join NON_METRIC_KEYS so Number(['5']) === 5 can never mint a
  bogus numeric metric
- bulkIngestBenchmarkRows persists both as jsonb lanes, NULL when absent,
  refreshed on conflict like workers
- all four read paths select the columns via to_jsonb(...) -> 'col' (the
  PR #405/#407 deploy-order lesson: bare references fail at plan time until
  the next ingest applies the migration; the jsonb lookup degrades to NULL)
- rowToAggDataEntry passes reasons through, and both chart tooltips render a
  bilingual "Measured power withheld" line with re-sanitized codes
- unofficial-run overlay rows carry both fields; the calculator view strips
  them; OpenAPI wording moves from reserved to live

The persistence input is built by spreading (ingest-ci-run.ts {...row,
configId}; run-overrides.ts applyBenchmarkPointBackfill {...point}), so the
new BenchmarkParams fields flow through with no changes there.

中文:将预留的 power_invalid_reasons / power_audit 契约字段全链路转正:迁移 014
为 benchmark_results 增加两个专用 jsonb 列并按迁移 012 的定义原样重建
latest_benchmarks;mapBenchmarkRow 对两个字段做防御性收窄(snake_case 原因码、
固定 8 键审计对象、空值映射为 undefined 以存储 SQL NULL);批量摄取以 jsonb 通道
持久化并在冲突时刷新;四条读取路径均用 to_jsonb(...) -> 'col' 容错读取(PR
#405/#407 的部署顺序教训);图表提示框新增双语"实测功耗未采信"行;非官方叠加行
携带同样字段;计算器视图剥离;OpenAPI 文案由预留改为正式。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ies, and UI | 测试:覆盖功耗审计溯源在映射、摄取、查询与界面各层的行为

- extractPowerInvalidReasons: snake_case validation, dedupe, 32-code cap,
  64-char boundary, empty/non-array/all-invalid -> undefined (never [])
- extractPowerAudit: 8-field round-trip, Infinity/NaN/junk numerics omitted,
  negative and non-safe-integer counts rejected, sha trimming and null
  collapse, unknown keys dropped, empty husk -> undefined
- mapBenchmarkRow lands the fields on BenchmarkParams for v1/v2/agentic rows,
  stores audits from valid rows too, and never mints a numeric metric from a
  malformed ['5'] reasons array (Number(['5']) === 5)
- bulkIngestBenchmarkRows recording-mock: both columns in the INSERT list,
  two extra ::jsonb[] lanes, null lanes for absent fields, excluded.* refresh
  on conflict
- all four read paths pin the tolerant to_jsonb(...) -> 'col' form and a
  negative regex guards against bare br./lb. references (the #407 lesson)
- rowToAggDataEntry narrows null/[]/absent to undefined; tooltips render the
  bilingual withheld line only for sanitized codes (en + zh, overlay parity);
  calculator view strips both fields alongside workers

中文:为溯源字段新增全链路测试——提取函数的收窄规则(原因码校验、去重、上限、
空值处理;审计对象 8 字段往返、异常数值剔除、sha 归一化)、映射到
BenchmarkParams、批量摄取的 jsonb 通道与冲突刷新、四条读取路径的 to_jsonb
容错形式(并用反向正则钉死 #407 教训)、前端窄化与双语提示框行为、计算器
视图剥离。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@edwingao28
edwingao28 requested a review from adibarra as a code owner August 27, 2026 23:46
@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
inferencemax-app Ready Ready Preview Aug 28, 2026 12:06am

Request Review

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @edwingao28's task in 3m 26s —— View job


Review: PLAN-07 power audit provenance ingest

🟢 LGTM — no blocking issues found.

Reviewed the full stack against origin/klaud/powerx-04-api-contract. This is a careful, well-tested end-to-end wiring of power_invalid_reasons / power_audit that stays tolerant of the fields' absence in every direction.

  • ETL mapper extractors (benchmark-mapper.ts)
  • Ingest lanes (benchmark-ingest.ts)
  • Read paths (queries/benchmarks.ts)
  • Migration 014_power_provenance.sql
  • API / transform / tooltip / catalog
  • Test coverage
  • CI status

What I verified

  • Extractors are correctly defensive. extractPowerInvalidReasons (snake_case regex, ≤64 chars, order-preserving dedup, cap 32, empty→undefined) and extractPowerAudit (finite-number/safe-integer narrowing, shas collapse to null, unknown keys dropped, empty-husk→undefined) both map empty results to undefined so the column stores SQL NULL, never []/{}. The hasNumericField snapshot is taken before the shas are attached, so the empty-husk check is correct.
  • NON_METRIC_KEYS guard is real, not cosmeticNumber(['5']) === 5 would otherwise capture a malformed single-element reasons array as a bogus numeric metric. Good catch, covered by a test.
  • Migration view body is byte-for-byte identical to migration 012 — confirmed with a diff (IDENTICAL VIEW BODY), including both indexes. Only the two alter table … add column statements are new.
  • Deploy-order tolerance is applied at both SQL SELECT sites (recursive branch + getLatestBenchmarks), the only two places that project workers. to_jsonb(br) -> 'power_*' degrades to NULL pre-migration and is identical post-migration — the feat(power): measured-power multinode support (workers[] + per-stage joules) #405/fix(db): tolerate missing workers column in benchmark read queries / 基准测试读取查询兼容缺失的 workers 列 #407 lesson, pinned by a negative-regex regression test.
  • Ingest parity with workers: NULL lanes keep the jsonb[] unnest homogeneous; excluded.* on conflict makes the fresh artifact authoritative. Persistence input flows through via spread, so no ingest-ci-run.ts / run-overrides.ts changes are needed — confirmed.
  • Frontend wiring is complete: AggDataEntry.power_invalid_reasonsInferenceData (via Partial<Omit<…>>, not in AggDataConflictKeys) → createChartDataPoint (...entry spread) → tooltip. Calculator view strips both provenance fields; overlay rows carry them.
  • No XSS in the tooltip. powerWithheldHTML re-validates each code against ^[a-z][a-z0-9_]*$ (+ length ≤64) before interpolating into the raw-HTML tooltip string, then only replaces _→space, so output is limited to [a-z0-9 ]. Defense-in-depth even though the mapper already validated. Renders only when reasons are present.
  • Test coverage is comprehensive — every touched source file has a colocated test updated in the same PR (mapper extractors incl. the ['5'] guard, ingest lane recording-mock, query-shape bare-reference guard, transform narrowing, calculator strip, tooltip en/zh + injection filtering + overlay parity, unofficial-run parity, OpenAPI docs + catalog digests).
  • Chinese copy (tooltip 实测功耗未采信 + the refreshed OpenAPI prose) reads natural and faithful — no findings.
  • CI: Vercel and Cursor Bugbot pass; no failing checks. The tooltip surface can't be exercised against the read-only production DB (fields are NULL until migration 014 + a fresh ingest), which is the intended tolerant-read behavior — the to_jsonb(...) form means the API serves those rows with the keys null rather than erroring, exactly the deploy-order proof described in the PR.

Nice attention to the stacked-PR merge order and the deploy-timing invariant throughout.
· klaud/powerx-07-provenance-ingest

…| ETL:补充数据摄取通道同样持久化功耗溯源字段

Review found ingest-supplemental.ts participates in the power
publication contract (normalize + scrub, PLAN-03) but silently dropped
power_invalid_reasons / power_audit: the persistence input carried no
provenance fields, so a supplemental entry with them would persist NULL
columns. Extract both via the shared narrowers — entry-level fields
sibling to metrics (mirroring artifact rows), with a metrics-nested
fallback since power_valid rides in metrics in this format — and delete
the keys from metrics so the persisted jsonb stays a flat numeric
record. Pin the call sequence next to the PLAN-03 supplemental tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Claude finished @edwingao28's task in 1m 49s —— View job


Re-review: PLAN-07 — new commit 04e01988 (supplemental ingest lane)

The prior review (here) covered 8a6a0eeb and was 🟢 LGTM. This re-review looks only at the one new commit since then.

  • Diff new commit vs previously-reviewed 8a6a0eeb
  • Review ingest-supplemental.ts change + test coverage
  • Check CI status
  • Post findings

🟢 LGTM — no blocking issues.

The delta closes a real gap: ingest-supplemental.ts bypasses mapBenchmarkRow, so before this commit the two provenance columns would always persist as NULL on the supplemental lane even when the producer emitted them. The fix reuses the shared narrowers (extractPowerInvalidReasons / extractPowerAudit) rather than re-implementing, which is the right call.

What I verified against origin/klaud/powerx-04-api-contract:

  • Extraction order is safe. normalizePowerContractMetricsscrubWithheldPowerMetrics run first, then the provenance is extracted. scrubWithheldPowerMetrics only deletes MEASURED_POWER_METRIC_KEYS (13 numeric power/energy/telemetry keys); power_invalid_reasons/power_audit are not in that set, so the reasons survive scrub even when power_valid === 0 — which is exactly the case where reasons exist. Correct.
  • metrics jsonb stays flat. Both keys are deleted from entry.metrics before the row is pushed, upholding the same NON_METRIC_KEYS invariant the mapper path enforces. The persisted metrics never carries the structured companions.
  • Nesting fallback is sound. entry.power_invalid_reasons ?? entry.metrics.power_invalid_reasons prefers the top-level field, falling back to the metrics-nested form (where power_valid also rides in this format). Nullish coalescing means a present-but-empty top-level array still wins, which is the intended precedence.
  • Extraction is unconditional on power_valid — tolerant in both directions, matching the artifact path.
  • Type-safety. entry.metrics is Record<string, number> but the narrowers take unknown and re-validate structurally, so the runtime array/object values pass through safely; typecheck is clean per CI.
  • Test coverage present. The new mapper test (recovers provenance companions nested under metrics and leaves the record flat) pins the exact normalize→scrub→extract→delete sequence, including dedup, <img src=x> injection-code rejection, unknown-key drop, and the flat-record assertion. Consistent with the existing scrubWithheldPowerMetrics (direct — supplemental ingest path) proxy-test pattern (the real ingestSupplementalBmk needs a live DB).

No UI surface on this commit, so browser verification doesn't apply. CI: Vercel green; Cursor Bugbot / Claude (review) still pending at review time — no failing test/lint runs.
· klaud/powerx-07-provenance-ingest

@edwingao28

Copy link
Copy Markdown
Collaborator Author

Superseded by #939 (branch renamed to feat/power-provenance-ingest); identical diff.

@edwingao28 edwingao28 closed this Aug 28, 2026
@edwingao28
edwingao28 deleted the klaud/powerx-07-provenance-ingest branch August 28, 2026 02:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant