“What did this product cost?” has two honest stories. Assay prints both and names which.

Tree product

Placement on the objective tree. What the work was for. Spend follows the first episode at the winning source. See Objectives and Framing. Dashboard → Products. Each trunk is a product. Unknown sittings are their own card, not a fake root named other.

Path product

Derived from path mentions in the harness’s own session documents. Not from project (that field is seating).
assay products
assay attribute products    # write the overlay (sweep already does this)
Every surface that prints this story shows two cost columns:
ReadingWhat it is
InclusiveWhole spend of every sitting that mentioned the product. Upper bound. Sessions may overlap. Does not partition.
WeightedSitting spend × share of that sitting’s workspace refs. Does partition. Residuals must reconcile to priced spend.
A breakdown you cannot tie back to the total is a breakdown nobody should trust. The weighted column plus every residual population adds up.

Residuals (named, never merged)

BucketMeans
No evidenceThe sitting was read. It referenced no workspace path.
UnscannedThere was no source document to read.
UnknownAnything else that did not land on a product.
A measured zero is not folded into unknown. Every figure names its counting unit. Default is line-distinct (one line that names a component is one reference). An older unit stays reproducible and is never mixed into the default.

The products map

A product name is a business fact. Config groups workspace folders:
{
  "products": {
    "workspaceRoots": ["~/code"],
    "groups": {
      "website": ["marketing-site", "docs-site"]
    }
  }
}
KeyMeaning
workspaceRootsWhere a component lives. The next path segment under a root is the component. Default ["~/code"].
groupsProduct name → the components it is made of. Unnamed components stay products of their own.
A component may belong to at most one product. Overlap would count the same refs twice and weighted would stop being a partition. Assay refuses that config at load. groups is a read-time roll-up. The overlay is stored at component grain. Edit groups and every report relabels immediately. Nothing is re-derived. Changing workspaceRoots changes the measurement. The next attribute products pass writes a new measurement. With no products block the feature still works: each component is its own product.

Filter

The console’s ?product= scope is the inclusive reading. Panels that do not apply it say so in words.

CLI

assay products
assay products --product website