Skip to content

refactor(emit): drop aggregationId from readouts wire (PR 5 alignment) - #246

Merged
zzylol merged 1 commit into
mainfrom
retire-readouts-aggregation-id
May 15, 2026
Merged

zzylol merged 1 commit into
mainfrom
retire-readouts-aggregation-id

Conversation

@zzylol

@zzylol zzylol commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Mirror of #244's aggregation-side cleanup, now for readouts.

The backend's StreamingConfig::from_yaml_data doesn't consume the readouts list at all today, so this is wire-shape hygiene rather than a fix — but it keeps the controller's emit consistent with the content-addressing convention PR 5 established: no controller-allocated string IDs on the wire.

When the backend eventually starts consuming readouts, the cross-reference from a readout to its source aggregation will be content-shaped (metric / sketch_kind / params) — derivable from the aggregations list by the same PolicyFingerprint recipe. The BackendReadout.aggregation_id struct field stays in source as emit-time bookkeeping (populated by resolve_descendant_agg_id_via_edges during the DAG walk; not consumed by anything downstream today).

What changed

  • build_backend_readout_json: 4 match arms (Quantile / Cardinality / PointCount / TopK) each drop their aggregationId key. Doc-comment expanded with the content-addressing rationale.
  • phase_b_backend_json_aggregation_readout_alias_snapshot: readouts assertion flipped from ”aggregationId present” → ”absent”.

Test plan

  • cargo check clean
  • cargo test --lib: 687 passed; 0 failed
  • cargo test --tests --bins: 27 passed; 0 failed

🤖 Generated with Claude Code

Mirror of #244's aggregation-side cleanup, now for readouts. The
backend's `StreamingConfig::from_yaml_data` doesn't consume the
`readouts` list at all today, so this is wire-shape hygiene rather
than a fix — but it keeps the controller's emit consistent with the
content-addressing convention PR 5 established: no controller-allocated
string IDs on the wire.

When the backend eventually starts consuming readouts, the cross-
reference from a readout to its source aggregation will be content-
shaped (metric / sketch_kind / params) — derivable from the
`aggregations` list by the same `PolicyFingerprint` recipe. The
`BackendReadout.aggregation_id` struct field stays in source as
emit-time bookkeeping (populated by `resolve_descendant_agg_id_via_edges`
during the DAG walk; not consumed by anything downstream today).

Touches:
- `build_backend_readout_json`: 4 match arms each drop their
  `aggregationId` key. Doc-comment expanded to explain the
  content-addressing convention.
- `phase_b_backend_json_aggregation_readout_alias_snapshot`: flips
  the readouts assertion from "aggregationId present" → "absent".

Build clean. 687 lib + 27 binary tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit 23d378e into main May 15, 2026
zzylol added a commit that referenced this pull request May 15, 2026
…g plumb (#247)

First end-to-end test of the gateway-less control-plane ↔ data-plane
contract. Lives in `data_plane/tests/` since data_plane already owns
the OTLP/precompute/HTTP wiring; control_plane is already a
workspace dep so no Cargo.toml changes were needed.

## What it tests

Two `#[tokio::test]`s:

1. `controller_plans_ddsketch_quantile_and_backend_parses_streaming_config`
   — workload `{ metric: http_latency_ms, agg: Quantile, sla: 0.01,
   window: 60s }` flows through `bind_workload_typed` →
   `split_typed_three_stage` → `emit_backend_streaming_config_json`,
   the JSON is POSTed to the in-process backend's
   `/api/v1/streaming-config`, and the GET endpoint reflects the
   parsed aggregation. Asserts the emitted JSON's shape (#244 content
   fields present, #244/#246 `aggregationId` absent).

2. `controller_plans_with_grouping_and_backend_parses_grouping_labels`
   — same shape but with `group_by_labels: ["zone"]`. Asserts the
   emitter surfaces `labels.grouping: ["zone"]` (#245), the POST
   succeeds, and the active-config snapshot contains `"zone"` in the
   parsed `AggregationConfig.grouping_labels`.

## Findings the test caught

`bind_workload_typed`-produced PhysicalExpr doesn't surface
`metric_name` or `window_secs` to `BackendAggregation` via
`extract_edge_facts`. The underlying cause is likely that the
allocator paints the `Logical(Scan{...})` chain at a stage other than
Edge in some binder outputs, so the walk's `(Logical, Edge)` match arm
doesn't fire — Step γ open-set label work (also blocking grouping
fidelity) probably needs a coordinated fix. Pragmatic workaround
mirroring the #245 grouping patch:

  control_plane/src/main.rs (handle_plan, Backend stage match arm)
    for agg in &mut be.aggregations {
        if agg.metric_name.is_empty() {
            agg.metric_name = workload.metric_name.clone();
        }
        if agg.window_secs == 0 {
            agg.window_secs = workload.time_window.as_secs();
        }
        agg.grouping = workload.group_by_labels.clone();
    }

This is belt-and-braces — if the L5 walk DID surface the field,
the conditional `if … is_empty()` / `== 0` checks preserve it;
otherwise the workload spec wins. Closes a real bug where fresh-deploy
`handle_plan` POSTs would have failed the backend parser on
`Missing metric` / `Missing windowSize` (PR #244 caught the JSON shape
gap; this test caught the value-population gap).

## Other change

`HttpServer::start_test_server` had `#[cfg(test)]` gating it to the
lib's own unit tests — integration tests under `data_plane/tests/`
are compiled separately and couldn't see it. Dropped the gate; the
method's name + doc-comment make the test-only intent explicit, and
production code uses `start()` regardless.

## Test plan

- [x] `cargo test --test e2e_controller_plans_and_backend_serves`:
  **2 passed**.
- [x] `cargo test --lib -p control_plane`: **687 passed**.
- [x] `cargo test --tests --bins -p control_plane`: **27 passed**.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 15, 2026
…re (#251)

Mirrors #244 / #246 / #250's PR-5 wire-cleanup pattern, now for the
edge-side runtime emitters. The patched asap-otel processors (any
runtime — OTel collector / OTAP / Telegraf) don't consume the
controller-allocated `aggregation_id` string: sid identity is
content-addressed at the backend via `(metric_name,
attrs_fingerprint, agg_kind_canonical)`, and `policy_fp` content-
matches via `(metric, sketch_kind, config, group_by_keys)`. The
controller-allocated string is dead weight on the wire.

The OTel-collector YAML emitter (`emit_edge_yaml`) was already
clean — there's an existing test at stage_config.rs asserting the
agent YAML does NOT contain `aggregation_id:`. This PR catches the
two remaining edge-runtime emitters that still spelled it out:

- `emit/otap.rs::build_asap_sketches_config` (line ~287): drop
  the `aggregation_id` map entry; keep `sketch_kind` etc.
- `emit/telegraf.rs::emit_processors_allsketches` (line ~158):
  drop the `aggregation_id = "…"` TOML line; keep `sketch_kind`.

`EdgeSketchProcessor.aggregation_id` stays on the struct as
internal emitter plumbing for cross-stage references during the
DAG walk (`SketchAgg → BackendAggregation → BackendReadout`); it
just doesn't reach the wire from any runtime emitter anymore.

Tests: 690 lib + 27 binary tests pass. (No test asserted the field
was present in OTAP / Telegraf output, so nothing to flip.)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the retire-readouts-aggregation-id branch July 17, 2026 20:06
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