Pipeline Apps

Metrics Notebook

A gallery app that saves named metric queries and runs them through signed, parameterized pipelines on the shared host.

Metrics Notebook keeps a list of saved, named metric queries and shows the result of running one as a table. It ships no dedicated host page, renderer component or app-specific runtime branch. Its source is apps/metrics-notebook/app.toml; the native records are in crates/shared/schema/app-metrics-notebook/src/lib.rs.

Supported workflow

The notebook starts empty. New query opens a dialog that takes a name, a target metric and an optional description. Each saved query has two run buttons:

  • Run summary groups the matching samples by service and shows the count, average, minimum and maximum.
  • Latest samples shows the 50 most recent matching samples.

Edit opens the query page. There you can change the name, metric and description, or delete the query.

Queries read the app's own MetricSample dataset. The Record a sample form on the notebook page appends a sample (metric, service and value), and the server sets its timestamp. This keeps the app self-contained, so it needs no cross-app dataset grant.

One package, existing host

Package declarationShared implementation
Native MetricQuery and MetricSample schemasDescriptor validation, typed CBOR records and per-field merge
metric-query-list pipelineAuthorized dataset read, filters, deterministic sort and projection
metric-summary and metric-recent pipelinesThe invoke_pipeline action with one signed input binding, applied as an overlay filter
Create, edit and delete actionsDurable entity operations, optimistic projection and explicit field masks
Bound table viewsThe shared data-view host
foreground_snapshot_v1The installed host's bounded foreground refresh scheduler

The release requests read and write access to app.metrics_notebook.MetricQuery and app.metrics_notebook.MetricSample, and invocation of the three pipelines. It requests no network, secret, notification or infrastructure capability.

Author, build and install

rac app validate apps/metrics-notebook/app.toml
rac app build apps/metrics-notebook/app.toml
rac des spec publish --manifest apps/metrics-notebook/pipelines/metric-query-list.toml
rac des spec publish --manifest apps/metrics-notebook/pipelines/metric-summary.toml
rac des spec publish --manifest apps/metrics-notebook/pipelines/metric-recent.toml
rac app publish apps/metrics-notebook/app.toml
rac app install metrics-notebook --version 0.1.0 \
  --installation-id metrics-notebook-example \
  --grant notebook-read,notebook-write,notebook-pipelines

Publication requires the same registry authority and grant review described in the release lifecycle. Only build/app-release.json is checked in.

Bounds

The query list returns at most 200 live queries, sorted by name. The summary reads at most 200 grouped rows and the recent view returns 50 rows. The only user-chosen part of a query is the metric it filters on. The grouping (metric, service), the aggregates and the row limits are fixed when the pipelines are signed.

Friction log

These are the problems we hit while building the app. We describe them here without filing tickets.

  • No delete operation. Entity operations are only create, set_fields, add_set_element and remove_set_element. Delete is a soft delete: a deleted bool written through a deleted field mask, which the list pipeline filters out. Deleted records stay in storage.
  • One parameter per pipeline invocation. invoke_pipeline binds a single input value, and its column, operator and value kind are fixed at signing. Users therefore cannot choose a column, operator, aggregate or limit. The "query" is a saved filter value plus a choice between two pre-signed shapes.
  • The overlay filter runs after the base stages. The parameter filter is appended after the pipeline's own stages. The summary fold must therefore keep metric in group_by, or the filter column would no longer exist. We have not verified whether the 200-row source limit applies before or after the fold.
  • Table columns are static. A table view descriptor is a literal string, so the two result shapes need two tables, chosen with v-if flags.
  • Collections need a fixed group. Optimistic and hydration config requires a group, so the list is v-for group in $state.queries around a list. There are no index paths such as queries[0].items.
  • v-if takes only booleans and lacks v-else-if. We set the derived flags (show_summary, show_recent, sample_recorded) with literal action steps. The "no result yet" state is a nested negated v-if.
  • Prop enums are checked only at validate time. variant="ghost" and tone="success" passed authoring and failed rac app validate. A text tone also propagates onto inline children.
  • Async table in host tests. The table node renders through the async DataViewHost. The host test therefore checks the compiled rows binding rather than rendered cells.
  • Observation: remoteInstalledRuntimeAppSource.ts still contains media-specific branches in shared host code.
Copyright © 2026