0039: Address feedback (#7400)
diff --git a/0039-track-grouping-dimensions.md b/0039-track-grouping-dimensions.md
index 5d98200..d576a02 100644
--- a/0039-track-grouping-dimensions.md
+++ b/0039-track-grouping-dimensions.md
@@ -46,58 +46,80 @@
 1. Adopt trace_processor's **dimension** terminology as the single vocabulary for
    machine, GPU, process/thread, and custom identifiers.
 2. Give producers a first-class surface to declare **custom dimensions**.
-3. Turn the collapse-and-label behavior into **shared label helpers** — so
-   machine and custom dimensions use them directly, and GPU's existing hierarchy
-   grouping calls the same helpers for its labels instead of re-implementing them.
+3. Expose every dimension through one typed, queryable trace_processor surface,
+   independent of whether it came from a typed descriptor, import context, or a
+   producer-declared custom value.
+4. Turn collapse-and-label behavior into **shared presentation helpers** — so
+   machine and custom dimensions use them directly, GPU's existing hierarchy
+   consumes the same label metadata, and details panels show the same values.
 
-Surfacing a dimension means attaching a **label**; it never inserts hierarchy.
-Hierarchy for system-wide concepts (GPU today) comes from trace_processor merging,
-not a UI grouping mode (see Two kinds of scoping and Alternatives).
+A dimension is first a trace_processor/query concept. Track subtitles are its
+normal timeline presentation, but not its only surface: dimensions are also
+available to SQL, details panels, and specialized UI. A well-known dimension can
+have specialized presentation — GPU hierarchy today — without becoming a
+special data model. Hierarchy for system-wide concepts comes from
+trace_processor identity/merging, not a generic UI grouping mode (see
+Well-known identity vs producer-local structure and Alternatives).
 
 Throughout this RFC, **"custom dimension"** means a producer-declared,
 workload-specific dimension; **rank** is used only as a concrete example of one.
 
 ## Decision
 
-Pending
+Adopt `Dimension` / `TrackDescriptor.dimensions` as a producer surface and
+normalize custom and well-known dimensions into one typed, track-keyed
+trace_processor relation. Dimensions declared on process/thread tracks resolve
+through existing `upid`/`utid` association; dimensions on ordinary tracks resolve
+through `parent_uuid`. Conflicting child overrides are invalid. The standard UI
+presentation is a multi-label track subtitle, details tabs show the same resolved
+values, and specialized consumers such as GPU hierarchy use the same TP data and
+label helpers. The initial well-known set is `machine`, `gpu`, `cpu`, `process`,
+and `thread`; custom string data is interned; numbering is per source trace and
+machine.
 
 ## Design
 
 ### Terminology: dimensions
 
-We align on trace_processor's existing concept. A **dimension** is a named
-key/value attached to tracks (living in the track's dimension arg set, or, for
-process/thread, keyed off `upid`/`utid`). There is one vocabulary, matching what
-trace_processor already exposes. Process and thread fit this model conceptually,
-but this RFC does not restructure them; modeling them as dimensions rides along
-with the merging work (see Future work).
+We align on trace_processor's existing concept. A **dimension** is a named,
+typed key/value in a track's identity. There is one vocabulary and one resolved
+trace_processor representation regardless of whether the value originated in a
+typed descriptor, import context, or `TrackDescriptor.dimensions`.
 
 Dimensions fall into two categories:
 
 - **Well-known dimensions** — recognized by trace_processor and shared across data
-  sources: `machine`, `gpu`, `cpu`, and conceptually `process`/`thread`. Their
-  defining property is that the *same value in different data sources refers to the
-  same real thing*, so trace_processor can merge tracks that carry it (see below).
+  sources: initially `machine`, `gpu`, `cpu`, `process`, and `thread`. Their
+  defining property is that the same canonical value in different data sources
+  refers to the same real thing. This permits validation, merging, and specialized
+  presentation. The set is declared centrally; custom producers cannot redefine
+  these reserved names.
 - **Custom dimensions** — producer-declared and workload-specific (`rank`,
-  `shard`, `stage`, …). Standalone: their structure is the producer's, and they
-  are surfaced as labels, not merged.
+  `shard`, `stage`, …). Their identity is local to the producer/source trace and
+  they are not cross-data-source merge keys.
 
-A dimension carries:
+A dimension declaration carries:
 
-- **name** — the dimension key (`machine`, `gpu`, `rank`, …).
-- **scope** — whose value it carries: a process, a thread, or an individual track.
-  Machine and (typically) custom dimensions are process-scoped; GPU is
-  track-scoped.
-- **display** — a per-dimension label template (`machine %d`, `GPU %d`, `rank %d`)
-  plus an optional per-value override string (a machine's or GPU's name, a
-  producer's `display_name`) that replaces the numbered default.
-- **numbering** — how a stable, gap-free index is assigned to values for the
-  default label (generalizes the existing per-machine index in the stdlib).
+- **name** — the canonical dimension key (`rank`, `shard`, …).
+- **typed value** — initially an integer or an interned string.
+- **display name** — an optional interned per-value label such as `worker-east`.
 
-### Two kinds of scoping
+A dimension has no independent `scope` enum. It is declared on a track. Existing
+process/thread association and explicit `parent_uuid` relationships determine
+which other tracks resolve that declaration as part of their effective dimension
+set. “Process-scoped rank” is shorthand for “rank declared on the process track,”
+not a separate storage or importer concept.
+
+Presentation metadata adds a label template (`machine %d`, `GPU %d`, `rank %d`)
+and stable numbering. Numbering is deterministic and gap-free within a
+`(source trace, machine, dimension name)` partition. A producer-supplied
+`display_name` overrides only the rendered numbered label; it does not replace
+the canonical typed value used by SQL or merging.
+
+### Well-known identity vs producer-local structure
 
 There is a useful distinction in how a producer's track event relates to the rest
-of the trace. It shapes what "surfacing a dimension" should mean:
+of the trace. It shapes how dimensions participate in identity and presentation:
 
 - **(a) Intersects a system-wide concept — needs merging.** The dimension names
   something the trace already knows about globally, so track-event tracks want to
@@ -111,227 +133,273 @@
   are here. Grouping is the producer's; we only *label*.
 
 Hierarchy that comes from merging system-wide concepts belongs in trace_processor,
-not in a UI mode layered over arbitrary producer trees. **This RFC builds only the
-labeling layer (b-style, plus labels for well-known dimensions), and leaves
-merging-based hierarchy as it is today.** Generalizing merging to more well-known
-dimensions is the future direction (below), not this change.
+not in a UI mode layered over arbitrary producer trees. This RFC builds the
+shared producer and trace_processor dimension model plus its subtitle/details
+presentation. Existing GPU hierarchy remains a specialized UI consumer of the
+well-known `gpu` dimension. Generalizing identity merging to more well-known
+dimensions remains future work.
 
-### Presentation: labels only, as subtitles
+### Presentation: subtitles, details, and specialized UI
 
-The generic system surfaces a dimension in exactly one way: a **label**. A label
-is a *secondary annotation on the affected node — not a mutation of its name*. The
-target UI affordance is a track **subtitle** (secondary text under the track
-name), modeled on Chrome's existing behavior; this decouples the
-dimension from the name entirely and lets multiple dimensions coexist cleanly.
+The normal timeline presentation of a dimension is a **label**: a secondary
+annotation on the affected node, not a mutation of its canonical name. This RFC
+adds a first-class track/group **subtitle** affordance (secondary text under the
+name), modeled on Chrome's existing behavior. It supports multiple labels in a
+stable order, separated by `·`, with truncation and the complete values available
+through tooltip/accessibility text.
 
-The one shared rule is unchanged: *if the trace has a single distinct value for a
-dimension, it is invisible; if it has more than one, its label is shown.*
+The shared collapse rule is unchanged: if the applicable tracks have a single
+distinct value for a dimension, its subtitle label is invisible; if they have
+more than one, the label is shown. Dimensions collapse independently. Labeling
+never reparents tracks.
 
-Several dimensions on one scope simply produce several labels (e.g. `machine 1`
-and `rank 3` on a process), each collapsed independently. There is no nesting and
-no re-parenting; labeling never touches the track tree.
-
-A process `trainer` on machine 1 with a custom `rank 3` in a multi-machine,
+A process `trainer` on machine 1 with custom `rank 3` in a multi-machine,
 multi-rank trace:
 
 ```text
 workspace
 └── trainer                        (process group; name unchanged)
-    ⤷ machine 1 · rank 3           (labels shown as subtitle)
+    ⤷ machine 1 · rank 3           (subtitle labels)
     └── <threads / tracks>
 ```
 
-**No generic LEVEL mode.** GPU hierarchy is unaffected by this: it continues to
-use its existing grouping in `dev.perfetto.Gpu` / `dev.perfetto.GpuByProcess`,
-including its own hardcoded "more than one GPU" gate. What changes for GPU is only
-that its `GPU N` / name **label** string is produced by the shared label helper
-(display template + numbering) instead of a GPU-private copy; GPU applies that
-string as its group-node name rather than as a subtitle:
+The same effective dimensions are shown in details tabs as canonical name, raw
+typed value, and optional display name. SQL exposes them independently of UI
+presentation.
+
+**No generic LEVEL mode.** GPU hierarchy remains specialized presentation in
+`dev.perfetto.Gpu` / `dev.perfetto.GpuByProcess`. At the trace_processor level,
+`gpu` is indistinguishable in shape from another dimension. The GPU UI consumes
+the shared dimension metadata and label helper, applies the resulting `GPU N` /
+name as its existing group-node title, and keeps its hierarchy behavior:
 
 ```text
 GPU
-└── GPU 0                          (GPU's existing hierarchy grouping, unchanged)
-    ⤷ machine 1                    (label via shared helper)
+└── GPU 0                          (specialized presentation of gpu dimension)
+    ⤷ machine 1                    (shared subtitle label)
     └── <gpu tracks>
 ```
 
-### Interaction with `parent_uuid` and the process/thread hierarchy
+### Resolution through `parent_uuid` and process/thread association
 
-Because labeling never re-parents anything, its interaction with the existing tree
-is simple: labels attach at the nodes that already exist and never reach inside a
-producer `parent_uuid` subtree.
+Labeling never reparents anything, but effective dimension resolution is explicit
+and shared by SQL, subtitles, details, and specialized UI:
 
-**Value resolution.** A dimension's value is resolved at its scope
-(process / thread / subtree-root track); the whole subtree inherits it, so
-producers tag only the scope root, not every child track. A value attached
-*deeper* inside a producer subtree does not change grouping — grouping is the
-producer's, via `parent_uuid`; we only read the scope root's value for the label
-(conflicting deeper values are an open question; proposed default "root wins"). A
-**global** track (no process/thread) inherits no process/thread-scoped dimension;
-it participates only in track-scoped dimensions it carries directly. Machine is
-the exception — its value is a per-track column present on *every* track, including
-global ones — which is why machine labels apply universally (see Querying).
+1. A dimension declared on a process descriptor applies to tracks associated with
+   the same `upid`, including its threads, process tracks, and per-process GPU
+   tracks.
+2. A dimension declared on a thread descriptor applies to tracks associated with
+   the same `utid`.
+3. A dimension declared on any other track applies to that track and its explicit
+   `parent_uuid` descendants.
+4. A global track with no process/thread association receives only dimensions
+   declared on itself or its explicit ancestors, plus synthesized well-known
+   dimensions such as machine.
+5. A descendant can add a dimension name not provided by its effective ancestors.
+   Repeating the same name and typed value is redundant and deduplicated. A
+   different value for an inherited name is invalid producer input: trace_processor
+   records an import error and does not apply the override.
+
+For example:
+
+```text
+Process track: process=trainer, rank=3
+├── Thread track                         effective: rank=3
+├── CPU annotation track                effective: rank=3
+└── Per-process GPU track, gpu=1         effective: rank=3, gpu=1
+```
+
+But:
+
+```text
+Process track: rank=3
+└── Child track: rank=4                  ERROR: inherited value override
+```
+
+Sibling subtrees may carry different values when their common parent did not
+declare that dimension. This keeps producer-defined grouping in `parent_uuid`
+while making inheritance deterministic and preventing a child from silently
+breaking the identity model.
 
 ### Data model and pipeline
 
-1. **Producer surface (new) — `TrackDescriptor`.** Add a repeated generic
-   dimension to `TrackDescriptor` (next free field is 21):
+1. **Producer surface (new) — `TrackDescriptor`.** Add repeated custom
+   dimensions to `TrackDescriptor` (field numbers are illustrative until the
+   proto change is prepared):
 
    ```proto
    message TrackDescriptor {
      // ... existing fields (uuid, parent_uuid, name, process, thread, counter,
      // state, ordering, ...) ...
 
-     // Custom dimensions declared by the producer, e.g. {name: "rank",
-     // int_value: 3}. Scope is implied by this descriptor's kind (see below).
-     // Repeated so a track can carry more than one.
+     // Custom dimensions declared on this track. Repeated so a track can carry
+     // more than one independent dimension.
      repeated Dimension dimensions = 21;
    }
 
    message Dimension {
-     optional string name = 1;                 // dimension key, e.g. "rank"
+     optional uint64 name_iid = 1;           // e.g. interned "rank"
      oneof value {
        int64 int_value = 2;
-       string string_value = 3;
+       uint64 string_value_iid = 3;
      }
-     optional string display_name = 4;         // optional per-value label,
-                                               // e.g. "worker-3"; else numbered
+     optional uint64 display_name_iid = 4;   // e.g. interned "worker-east"
    }
    ```
 
-   **Scope is implicit in which descriptor carries the field** — no separate scope
-   enum needed:
-   - on the **process** `TrackDescriptor` (the one with a `process{}`
-     sub-descriptor) → *process-scoped*: declared once per process and inherited
-     by all its threads/tracks. This is how a training workload sets `rank` once.
-   - on a **thread** `TrackDescriptor` (`thread{}`) → *thread-scoped*.
-   - on any **other** track's descriptor → *track-scoped* (that track/subtree).
+   Dimension names, string values, and display names are interned in the
+   packet-sequence incremental state. The exact `InternedData` entry or shared
+   interned-string namespace is an implementation detail to settle with the
+   proto change. IIDs are sequence-local transport details: trace_processor
+   resolves them immediately to canonical typed values, and unknown IIDs produce
+   an import diagnostic. Integer values remain inline.
 
-   **trace_processor mapping.** Track-scoped dimensions map onto ordinary track
-   dimensions via the existing blueprint path — **no storage schema change** — and
-   are read via `extract_arg(dimension_arg_set_id, 'rank')`. Process/thread-scoped
-   dimensions are recorded against the `upid`/`utid` (surfaced in the `viz`
-   grouping as a per-scope property, the same way machine is a process property
-   today, e.g. a small `_process_dimension(upid, name, value, display_name)`
-   view). Derived dimensions (machine, GPU) keep being set by the importer as
-   today.
+   The message intentionally contains no scope. The dimension is declared on
+   the track described by the containing `TrackDescriptor`; the resolution rules
+   above determine its effective descendants. Producers use typed descriptors
+   for well-known dimensions and cannot declare a custom dimension with a
+   reserved well-known name.
 
-2. **trace_processor.** A small stdlib registry enumerating known dimensions and
-   their metadata (name, scope, `display_name_template`), with machine and GPU
-   seeded and custom dimensions discovered from the tracks that carry them.
-   Generalize the existing per-machine index into a reusable "dense index per
-   dimension value within scope". Expose a scope's dimension values from the `viz`
-   grouping so the UI reads them in one query.
+2. **trace_processor import and resolution.** All dimensions use one generic
+   track-dimension path. Typed process/thread descriptors, GPU descriptors, and
+   machine import context synthesize the same canonical rows as custom
+   declarations. A post-import resolver applies process/thread association and
+   `parent_id` inheritance, deduplicates repeated identical values, and reports a
+   producer error for a conflicting inherited value. There are no separate
+   custom process-, thread-, or GPU-dimension storage paths.
 
-3. **UI.** Factor the behavior into two reusable pieces: (i) a **label helper**
-   that resolves a dimension value to its display string (registry template,
-   per-value override, generic numbering, stable sort/dedup key), and (ii) a
-   **generic label pass** that applies the collapse-when-single gate and renders
-   the string as a subtitle. Machine's ~10 call sites and custom dimensions use
-   the full pass. GPU's existing grouping keeps its hierarchy and its own hardcoded
-   "more than one GPU" gate, but calls the label helper (i) for its `GPU N` string
-   and applies it as its group-node name — dropping only its private
-   label/numbering copy.
+3. **trace_processor query surface.** Expose effective dimensions through one
+   typed long-form relation (public name to be finalized):
+
+   ```text
+   track_dimension(
+     track_id,
+     name,
+     value_type,
+     int_value,
+     string_value,
+     display_name,
+     declaring_track_id,
+     is_inherited,
+     is_well_known
+   )
+   ```
+
+   A small registry exposes dimension metadata such as the well-known bit and
+   label template. The generic relation is keyed by `track_id`, so slices,
+   counters, GPU work, and other track-backed domains use the same join. Raw
+   IIDs never escape into SQL.
+
+4. **Stable numbering.** Generalize the existing dense machine index into a
+   deterministic index per `(source trace, machine, dimension name)`. Values are
+   ordered by canonical type and value rather than packet arrival. Tracks with
+   no machine use a source-trace-global synthetic machine bucket. Merged input
+   traces retain their source-trace identity for numbering unless future merging
+   explicitly unifies that dimension.
+
+5. **UI.** Factor presentation into: (i) a label helper that maps a canonical
+   value to its template/numbering/display override; (ii) a generic collapse pass
+   that emits subtitle labels; and (iii) a details renderer for all effective
+   dimensions. Machine and custom dimensions use all three. GPU uses the same TP
+   rows and label helper but retains its specialized group presentation.
 
 ### Custom dimensions and GPU work
 
-GPU tracks are importer-created and do not go through the `TrackDescriptor` path,
-so they are not tagged with custom dimensions directly. In scope for this RFC, GPU
-work picks up custom labels *implicitly through its owning process*:
-`dev.perfetto.GpuByProcess` already associates GPU tracks with a `upid`, so a
-process's process-scoped dimensions (e.g. `rank`) label that process's GPU tracks
-with no GPU-specific work. The global, cross-process `dev.perfetto.Gpu` view
-groups by GPU across processes, where a per-process value has no single meaning,
-so tagging GPU tracks there is out of scope.
+GPU is a well-known dimension with the same trace_processor shape as machine,
+CPU, process, thread, or a custom dimension. Its special behavior is limited to
+identity merging and UI presentation.
+
+A per-process GPU track is associated with a `upid`, so a custom dimension
+attached to that process track — for example `rank` — resolves onto the GPU track
+through the ordinary process-association rule. No GPU-specific custom-dimension
+code or `_process_dimension` join is needed. A cross-process GPU group does not
+receive a process dimension when its children disagree: `rank` remains on the
+per-process child tracks while the group carries only dimensions that have one
+unambiguous value, such as `gpu` and `machine`.
 
 ### Querying by a dimension in trace_processor
 
-Making these dimensions first-class also makes them a **query axis**, not just UI
-labeling — useful for ad-hoc SQL and for batch analysis across many traces
-(`batch_trace_processor`, e.g. a per-rank metric over a whole job). Two additions
-to the table surface:
-
-- **A registry** so tools can discover a trace's dimensions:
-  `dimensions(name, scope, display_name_template)`.
-- **Per-scope value tables** keyed by the scope's id, so a value joins to
-  processes/threads/tracks. Process- and thread-scoped dimensions (which don't
-  live on a track) get a long table, e.g.
-  `_process_dimension(upid, name, value, display_name)` (and a thread
-  equivalent). Track-scoped dimensions need no new table — they are already on the
-  track, read via `extract_arg(track.dimension_arg_set_id, '<name>')`.
-
-Example extractions for a process-scoped custom dimension `rank`. Any per-domain
-view that exposes `upid` joins the same way — `thread_slice` for thread slices,
-`gpu_slice` for GPU slices (it carries its own `upid`), process-scoped counter
-tracks, etc.:
+Dimensions are a first-class query axis for ad-hoc SQL and batch analysis. The
+long-form `track_dimension` relation preserves value types, so joins do not rely
+on string coercion:
 
 ```sql
--- Thread slices for rank 3.
+-- Any slices on tracks whose effective rank is integer 3.
 SELECT s.*
-FROM thread_slice s
-JOIN _process_dimension d USING (upid)
-WHERE d.name = 'rank' AND d.value = 3;
+FROM slice AS s
+JOIN track_dimension AS d USING (track_id)
+WHERE d.name = 'rank' AND d.int_value = 3;
 
--- GPU busy time per rank (gpu_slice carries upid via the GpuByProcess
--- association).
-SELECT d.value AS rank, SUM(s.dur) AS gpu_busy
-FROM gpu_slice s
-JOIN _process_dimension d USING (upid)
+-- GPU busy time per rank. GPU work uses the same track_id relation.
+SELECT d.int_value AS rank, sum(s.dur) AS gpu_busy
+FROM gpu_slice AS s
+JOIN track_dimension AS d USING (track_id)
 WHERE d.name = 'rank'
 GROUP BY rank;
 ```
 
-Machine works this way today too, with one difference worth calling out: machine's
-value is a `machine_id` column on **every** `track` (stamped per import context),
-so it also covers global tracks that have no process. A process-scoped custom
-dimension instead attributes via `upid`, so it does not reach a global track — such
-a track carries a custom dimension only if the producer tagged it track-scoped. A
-universal helper that resolves a track's `upid` (and thus its process-scoped
-dimensions) across all track types would let one query span thread, GPU and other
-slice domains instead of joining per domain (see open questions).
+Process and thread remain typed top-level Perfetto concepts. Their canonical
+tracks declare or synthesize dimensions, and the resolver propagates those
+values to tracks with matching `upid`/`utid`; they do not require parallel
+per-scope dimension tables. Machine continues to reach global tracks because TP
+synthesizes it from each track's import context. A global track receives a custom
+dimension only from its own descriptor or an explicit `parent_uuid` ancestor.
+
+### Details and non-track surfaces
+
+When a selected slice, counter, or other row has a backing `track_id`, the details
+panel resolves and displays that track's effective dimensions. Process/thread
+details use their canonical tracks. Each entry shows the canonical name, raw
+typed value, and optional display name; well-known dimensions such as machine no
+longer need a separate raw-ID-only presentation. This avoids copying custom
+columns into every event table while making dimensions consistently visible in
+both SQL and details UI.
 
 ### Migration
 
-- **Machine** → a process-scoped dimension surfaced by the shared label helper
-  (its universal per-track value already reaches global tracks). The machine table
-  and name stay. Behavior change: the label moves from a name suffix to a
-  subtitle, so machine no longer mutates the track name (see open questions).
-- **GPU** → keeps its existing track-scoped hierarchy grouping unchanged, including
-  its own hardcoded "more than one GPU" gate; both GPU plugins produce their
-  `GPU N` label string via the shared label helper, dropping only their private
-  label/numbering copy.
-- **Custom** → producers emit the new dimension at the process scope; it renders as
-  a subtitle label; the process's GPU work is labeled implicitly via
-  `GpuByProcess`. Adding a further custom dimension is then data/configuration
-  only.
+- **Machine** → a well-known dimension synthesized on every track. Its timeline
+  label moves from a name suffix to the new subtitle immediately; the machine
+  table and canonical track names remain.
+- **GPU** → a well-known dimension in the generic TP relation. Existing GPU
+  hierarchy and the hardcoded multi-GPU presentation gate remain, but both GPU
+  plugins consume the shared dimension metadata and label helper.
+- **Process/thread** → remain typed top-level concepts and provide association
+  edges for effective-dimension resolution. This RFC does not replace `upid` or
+  `utid` storage.
+- **Custom** → a producer declares a dimension on the appropriate process,
+  thread, or ordinary track. Adding another custom dimension is then data rather
+  than new TP/UI code.
 
-Traces render identically by default (labels appear only with >1 value), except
-for the machine suffix→subtitle move, which is a deliberate presentation change.
+Labels still collapse when only one distinct value is present. The deliberate
+presentation change is that machine labels move from name suffixes to subtitles.
 
 ## Alternatives considered
 
-### Option 1 — Labeling layer + shared helpers; GPU hierarchy untouched (recommended)
+### Option 1 — Generic TP dimensions + shared presentation; GPU hierarchy untouched (recommended)
 
 Adopt trace_processor's dimension vocabulary, add the producer surface for custom
-dimensions, and extract collapse-and-label into shared helpers that machine,
-custom, and GPU's label all use. Do **not** add a generic hierarchy mode; leave
-GPU's existing merging-based grouping as is.
+dimensions, resolve all effective values into one typed track-keyed relation, and
+extract subtitle/details/label helpers shared by machine, custom dimensions, and
+GPU presentation. Do **not** add a generic hierarchy mode; leave GPU's existing
+merging-based grouping as specialized presentation.
 
 Pro:
 
-- One implementation of collapse-and-label; the custom case is configuration.
-- No generic LEVEL semantics, so no risky interaction with the track-event
-  surface / producer `parent_uuid` trees.
-- Producers get a first-class way to express workload structure; labels are
-  decoupled from the name.
-- Aligned with the trace_processor data model; process/thread retrofit cleanly.
+- One query model for custom, process/thread-associated, machine, CPU, and GPU
+  dimensions; no per-domain custom-dimension tables.
+- One implementation of collapse-and-label; the custom case is data.
+- No generic LEVEL semantics, so no risky interaction with producer
+  `parent_uuid` trees.
+- Producers get a first-class way to express workload identity; labels are
+  decoupled from canonical names and details UI reads the same source.
 
 Con:
 
-- Does not yet unify GPU-style hierarchy under a single mechanism — GPU grouping
-  stays special-cased (addressed by the merging future work, not here).
-- Subtitle rendering is new UI surface that must be built.
-- Moving machine off its name suffix needs a diff-test sweep.
+- Does not yet unify GPU-style hierarchy under a single presentation mechanism —
+  GPU grouping stays specialized until merging work is generalized.
+- Effective-dimension resolution and inheritance validation add TP work.
+- Subtitle rendering is a new UI surface, and moving machine off its name suffix
+  needs a diff-test sweep.
 
 ### Option 2 — Generic LEVEL presentation mode
 
@@ -366,42 +434,35 @@
 ## Future work (non-goals of this RFC)
 
 - **Merging well-known dimensions in trace_processor.** Extend the mechanism that
-  already merges process/thread across data sources to other well-known dimensions
-  (gpu, machine, …), so that hierarchy for system-wide concepts falls out of
-  trace_processor merging rather than any UI grouping mode. This is the path to
-  eventually folding GPU's bespoke grouping into the shared model. Modeling
-  process/thread themselves as dimensions (rather than the current parallel typed
-  hierarchy) belongs with this work. Requires defining which dimensions are
-  "well-known / mergeable" and how tracks from different data sources reconcile.
+  already merges process/thread across data sources to the initial well-known set
+  (`machine`, `gpu`, `cpu`, `process`, `thread`), so hierarchy for system-wide
+  concepts falls out of trace_processor identity rather than any UI grouping mode.
+  This is the path to eventually folding GPU's specialized grouping into a shared
+  presentation. Replacing the current typed process/thread storage with the generic
+  identity model is not required by this RFC.
 - **User-configurable presentation.** Letting the user change how a dimension is
   surfaced (e.g. promote a label to its own subtree) and reorder dimensions at
   runtime.
 
-## Open questions
+## Remaining implementation questions
 
-- **Subtitle affordance.** Track subtitles do not exist in the Perfetto UI today.
-  Scope of the new affordance (multiple labels per node, styling, per-domain track
-  support) and whether machine moves to it immediately or keeps its suffix until
-  subtitles land (staging + diff-test).
-- **Terminology / proto naming.** Confirm `Dimension` / `dimensions` on
-  `TrackDescriptor` vs a more specific name, given `process`/`thread`/`counter`
-  are also dimensions conceptually; and how the producer surface relates to any
-  existing annotation surface.
-- **Well-known vs custom boundary.** Which dimensions are treated as well-known /
-  mergeable (machine, gpu, cpu, process, thread) and how that set is declared —
-  relevant to the merging future work.
-- **Conflicting values within a producer subtree.** When a producer sets a
-  dimension with differing values on tracks inside one `parent_uuid` subtree:
-  "root wins" (proposed), ignore-below-root, or flag as an import error?
-- **Producer proto shape.** The `Dimension` value set (`int`/`string` only vs more
-  types) and interning for high-cardinality string values.
-- **Query surface shape.** The stdlib shape: a long per-scope table
-  (`_process_dimension(upid, name, value, display_name)`) + a resolve-one macro vs
-  a wider/pivoted view; value typing (int vs string) in join predicates; and
-  whether to add a universal track→`upid` resolver so one query can span thread,
-  GPU and other slice domains rather than joining per domain.
-- **Numbering stability & scope.** Stable indices across merged traces and across
-  scopes (per-machine vs global), and how a producer-supplied `display_name`
-  overrides numbering.
-- **Non-track surfaces.** Whether the same dimensions should also annotate details
-  tabs / SQL tables (machine id is shown raw there today).
+The review resolves the model-level questions: use `Dimension` / `dimensions`,
+add subtitles now, reserve `machine`/`gpu`/`cpu`/`process`/`thread`, reject
+conflicting inherited values, intern string data, number per source trace and
+machine, and expose dimensions in SQL and details UI. Implementation still needs
+to settle:
+
+- **Interned-data layout.** Use dedicated dimension-name/value/display-name IID
+  namespaces or an existing generic interned-string entry; define incremental
+  state reset behavior and validation for unknown IIDs.
+- **Public query names.** Finalize the public table/view and registry names, and
+  whether `declaring_track_id` / inheritance provenance are public or internal.
+- **Import error behavior.** A conflicting child never overrides its inherited
+  value. Follow TP conventions to decide whether to reject only the declaration
+  or packet while recording the diagnostic; do not fail the whole trace merely
+  because one producer emitted an invalid dimension.
+- **Source-trace numbering key.** Define how archive/manifest imports persist a
+  component source-trace identifier used with `machine_id` for stable numbering.
+- **Subtitle layout.** Finalize height, truncation, styling, and accessibility for
+  multiple labels; this is UI implementation detail rather than an optional
+  feature.